hazo_files 1.0.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.
- package/.cursor/rules/db_schema.mdc +0 -0
- package/.cursor/rules/design.mdc +0 -0
- package/.cursor/rules/general.mdc +0 -0
- package/CHANGE_LOG.md +341 -0
- package/CLAUDE.md +926 -0
- package/README.md +929 -0
- package/SETUP_CHECKLIST.md +931 -0
- package/TECHDOC.md +325 -0
- package/dist/index.d.mts +1031 -0
- package/dist/index.d.ts +1031 -0
- package/dist/index.js +2457 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2333 -0
- package/dist/index.mjs.map +1 -0
- package/dist/ui/index.js +4054 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/index.mjs +3982 -0
- package/dist/ui/index.mjs.map +1 -0
- package/docs/ADDING_MODULES.md +964 -0
- package/hazo_files_config.ini +31 -0
- package/package.json +83 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2457 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
ALL_SYSTEM_VARIABLES: () => ALL_SYSTEM_VARIABLES,
|
|
34
|
+
AuthenticationError: () => AuthenticationError,
|
|
35
|
+
ConfigurationError: () => ConfigurationError,
|
|
36
|
+
DEFAULT_DATE_FORMATS: () => DEFAULT_DATE_FORMATS,
|
|
37
|
+
DirectoryExistsError: () => DirectoryExistsError,
|
|
38
|
+
DirectoryNotEmptyError: () => DirectoryNotEmptyError,
|
|
39
|
+
DirectoryNotFoundError: () => DirectoryNotFoundError,
|
|
40
|
+
FileExistsError: () => FileExistsError,
|
|
41
|
+
FileManager: () => FileManager,
|
|
42
|
+
FileNotFoundError: () => FileNotFoundError,
|
|
43
|
+
FileTooLargeError: () => FileTooLargeError,
|
|
44
|
+
GoogleDriveAuth: () => GoogleDriveAuth,
|
|
45
|
+
GoogleDriveModule: () => GoogleDriveModule,
|
|
46
|
+
HazoFilesError: () => HazoFilesError,
|
|
47
|
+
InvalidExtensionError: () => InvalidExtensionError,
|
|
48
|
+
InvalidPathError: () => InvalidPathError,
|
|
49
|
+
LocalStorageModule: () => LocalStorageModule,
|
|
50
|
+
OperationError: () => OperationError,
|
|
51
|
+
PermissionDeniedError: () => PermissionDeniedError,
|
|
52
|
+
SYSTEM_COUNTER_VARIABLES: () => SYSTEM_COUNTER_VARIABLES,
|
|
53
|
+
SYSTEM_DATE_VARIABLES: () => SYSTEM_DATE_VARIABLES,
|
|
54
|
+
SYSTEM_FILE_VARIABLES: () => SYSTEM_FILE_VARIABLES,
|
|
55
|
+
clonePattern: () => clonePattern,
|
|
56
|
+
createAndInitializeModule: () => createAndInitializeModule,
|
|
57
|
+
createEmptyNamingRuleSchema: () => createEmptyNamingRuleSchema,
|
|
58
|
+
createFileItem: () => createFileItem,
|
|
59
|
+
createFileManager: () => createFileManager,
|
|
60
|
+
createFolderItem: () => createFolderItem,
|
|
61
|
+
createGoogleDriveAuth: () => createGoogleDriveAuth,
|
|
62
|
+
createGoogleDriveModule: () => createGoogleDriveModule,
|
|
63
|
+
createInitializedFileManager: () => createInitializedFileManager,
|
|
64
|
+
createLiteralSegment: () => createLiteralSegment,
|
|
65
|
+
createLocalModule: () => createLocalModule,
|
|
66
|
+
createModule: () => createModule,
|
|
67
|
+
createVariableSegment: () => createVariableSegment,
|
|
68
|
+
errorResult: () => errorResult,
|
|
69
|
+
filterItems: () => filterItems,
|
|
70
|
+
formatBytes: () => formatBytes,
|
|
71
|
+
formatCounter: () => formatCounter,
|
|
72
|
+
formatDateToken: () => formatDateToken,
|
|
73
|
+
generateId: () => generateId,
|
|
74
|
+
generatePreviewName: () => generatePreviewName,
|
|
75
|
+
generateSampleConfig: () => generateSampleConfig,
|
|
76
|
+
generateSegmentId: () => generateSegmentId,
|
|
77
|
+
getBaseName: () => getBaseName,
|
|
78
|
+
getBreadcrumbs: () => getBreadcrumbs,
|
|
79
|
+
getDirName: () => getDirName,
|
|
80
|
+
getExtension: () => getExtension,
|
|
81
|
+
getExtensionFromMime: () => getExtensionFromMime,
|
|
82
|
+
getFileCategory: () => getFileCategory,
|
|
83
|
+
getFileMetadataValues: () => getFileMetadataValues,
|
|
84
|
+
getMimeType: () => getMimeType,
|
|
85
|
+
getNameWithoutExtension: () => getNameWithoutExtension,
|
|
86
|
+
getParentPath: () => getParentPath,
|
|
87
|
+
getPathSegments: () => getPathSegments,
|
|
88
|
+
getRegisteredProviders: () => getRegisteredProviders,
|
|
89
|
+
getRelativePath: () => getRelativePath,
|
|
90
|
+
getSystemVariablePreviewValues: () => getSystemVariablePreviewValues,
|
|
91
|
+
hasExtension: () => hasExtension,
|
|
92
|
+
hazo_files_generate_file_name: () => hazo_files_generate_file_name,
|
|
93
|
+
hazo_files_generate_folder_name: () => hazo_files_generate_folder_name,
|
|
94
|
+
isAudio: () => isAudio,
|
|
95
|
+
isChildPath: () => isChildPath,
|
|
96
|
+
isCounterVariable: () => isCounterVariable,
|
|
97
|
+
isDateVariable: () => isDateVariable,
|
|
98
|
+
isDocument: () => isDocument,
|
|
99
|
+
isFile: () => isFile,
|
|
100
|
+
isFileMetadataVariable: () => isFileMetadataVariable,
|
|
101
|
+
isFolder: () => isFolder,
|
|
102
|
+
isImage: () => isImage,
|
|
103
|
+
isPreviewable: () => isPreviewable,
|
|
104
|
+
isProviderRegistered: () => isProviderRegistered,
|
|
105
|
+
isText: () => isText,
|
|
106
|
+
isVideo: () => isVideo,
|
|
107
|
+
joinPath: () => joinPath,
|
|
108
|
+
loadConfig: () => loadConfig,
|
|
109
|
+
loadConfigAsync: () => loadConfigAsync,
|
|
110
|
+
normalizePath: () => normalizePath,
|
|
111
|
+
parseConfig: () => parseConfig,
|
|
112
|
+
parsePatternString: () => parsePatternString,
|
|
113
|
+
patternToString: () => patternToString,
|
|
114
|
+
registerModule: () => registerModule,
|
|
115
|
+
sanitizeFilename: () => sanitizeFilename,
|
|
116
|
+
saveConfig: () => saveConfig,
|
|
117
|
+
sortItems: () => sortItems,
|
|
118
|
+
successResult: () => successResult,
|
|
119
|
+
validateNamingRuleSchema: () => validateNamingRuleSchema,
|
|
120
|
+
validatePath: () => validatePath
|
|
121
|
+
});
|
|
122
|
+
module.exports = __toCommonJS(index_exports);
|
|
123
|
+
|
|
124
|
+
// src/config/index.ts
|
|
125
|
+
var ini = __toESM(require("ini"));
|
|
126
|
+
var fs = __toESM(require("fs"));
|
|
127
|
+
var path = __toESM(require("path"));
|
|
128
|
+
var DEFAULT_CONFIG_FILENAME = "hazo_files_config.ini";
|
|
129
|
+
function parseConfig(configContent) {
|
|
130
|
+
const parsed = ini.parse(configContent);
|
|
131
|
+
const provider = parsed.general?.provider || "local";
|
|
132
|
+
const config = {
|
|
133
|
+
provider
|
|
134
|
+
};
|
|
135
|
+
if (parsed.local) {
|
|
136
|
+
config.local = {
|
|
137
|
+
basePath: parsed.local.base_path || "./files",
|
|
138
|
+
allowedExtensions: parsed.local.allowed_extensions ? parsed.local.allowed_extensions.split(",").map((ext) => ext.trim()) : void 0,
|
|
139
|
+
maxFileSize: parsed.local.max_file_size ? parseInt(parsed.local.max_file_size, 10) : void 0
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
if (parsed.google_drive) {
|
|
143
|
+
config.google_drive = {
|
|
144
|
+
clientId: parsed.google_drive.client_id || process.env.GOOGLE_DRIVE_CLIENT_ID || "",
|
|
145
|
+
clientSecret: parsed.google_drive.client_secret || process.env.GOOGLE_DRIVE_CLIENT_SECRET || "",
|
|
146
|
+
redirectUri: parsed.google_drive.redirect_uri || process.env.GOOGLE_DRIVE_REDIRECT_URI || "",
|
|
147
|
+
refreshToken: parsed.google_drive.refresh_token || process.env.GOOGLE_DRIVE_REFRESH_TOKEN,
|
|
148
|
+
accessToken: parsed.google_drive.access_token || process.env.GOOGLE_DRIVE_ACCESS_TOKEN,
|
|
149
|
+
rootFolderId: parsed.google_drive.root_folder_id || process.env.GOOGLE_DRIVE_ROOT_FOLDER_ID
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
return config;
|
|
153
|
+
}
|
|
154
|
+
function loadConfig(configPath) {
|
|
155
|
+
const resolvedPath = configPath || path.join(process.cwd(), DEFAULT_CONFIG_FILENAME);
|
|
156
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
157
|
+
console.warn(`Config file not found at ${resolvedPath}, using defaults`);
|
|
158
|
+
return {
|
|
159
|
+
provider: "local",
|
|
160
|
+
local: {
|
|
161
|
+
basePath: "./files"
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
const content = fs.readFileSync(resolvedPath, "utf-8");
|
|
166
|
+
return parseConfig(content);
|
|
167
|
+
}
|
|
168
|
+
async function loadConfigAsync(configPath) {
|
|
169
|
+
const resolvedPath = configPath || path.join(process.cwd(), DEFAULT_CONFIG_FILENAME);
|
|
170
|
+
try {
|
|
171
|
+
const content = await fs.promises.readFile(resolvedPath, "utf-8");
|
|
172
|
+
return parseConfig(content);
|
|
173
|
+
} catch (error) {
|
|
174
|
+
if (error.code === "ENOENT") {
|
|
175
|
+
console.warn(`Config file not found at ${resolvedPath}, using defaults`);
|
|
176
|
+
return {
|
|
177
|
+
provider: "local",
|
|
178
|
+
local: {
|
|
179
|
+
basePath: "./files"
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
throw error;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function generateSampleConfig() {
|
|
187
|
+
return `; Hazo Files Configuration
|
|
188
|
+
; This file configures the file management system
|
|
189
|
+
|
|
190
|
+
[general]
|
|
191
|
+
; Available providers: local, google_drive
|
|
192
|
+
provider = local
|
|
193
|
+
|
|
194
|
+
[local]
|
|
195
|
+
; Base path for local file storage (relative or absolute)
|
|
196
|
+
base_path = ./files
|
|
197
|
+
; Comma-separated list of allowed extensions (optional, empty = all allowed)
|
|
198
|
+
allowed_extensions =
|
|
199
|
+
; Maximum file size in bytes (optional, 0 = unlimited)
|
|
200
|
+
max_file_size = 0
|
|
201
|
+
|
|
202
|
+
[google_drive]
|
|
203
|
+
; Google Drive OAuth credentials
|
|
204
|
+
; These can also be set via environment variables:
|
|
205
|
+
; GOOGLE_DRIVE_CLIENT_ID, GOOGLE_DRIVE_CLIENT_SECRET, etc.
|
|
206
|
+
client_id =
|
|
207
|
+
client_secret =
|
|
208
|
+
redirect_uri = http://localhost:3000/api/auth/callback/google
|
|
209
|
+
refresh_token =
|
|
210
|
+
access_token =
|
|
211
|
+
; Optional: Root folder ID to use as base (empty = root of Drive)
|
|
212
|
+
root_folder_id =
|
|
213
|
+
`;
|
|
214
|
+
}
|
|
215
|
+
async function saveConfig(config, configPath) {
|
|
216
|
+
const resolvedPath = configPath || path.join(process.cwd(), DEFAULT_CONFIG_FILENAME);
|
|
217
|
+
const iniConfig = {
|
|
218
|
+
general: {
|
|
219
|
+
provider: config.provider
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
if (config.local) {
|
|
223
|
+
iniConfig.local = {
|
|
224
|
+
base_path: config.local.basePath,
|
|
225
|
+
allowed_extensions: config.local.allowedExtensions?.join(", ") || "",
|
|
226
|
+
max_file_size: config.local.maxFileSize?.toString() || "0"
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
if (config.google_drive) {
|
|
230
|
+
iniConfig.google_drive = {
|
|
231
|
+
client_id: config.google_drive.clientId || "",
|
|
232
|
+
client_secret: config.google_drive.clientSecret || "",
|
|
233
|
+
redirect_uri: config.google_drive.redirectUri || "",
|
|
234
|
+
refresh_token: config.google_drive.refreshToken || "",
|
|
235
|
+
access_token: config.google_drive.accessToken || "",
|
|
236
|
+
root_folder_id: config.google_drive.rootFolderId || ""
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
const content = ini.stringify(iniConfig);
|
|
240
|
+
await fs.promises.writeFile(resolvedPath, content, "utf-8");
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// src/modules/local/index.ts
|
|
244
|
+
var fs2 = __toESM(require("fs"));
|
|
245
|
+
var path2 = __toESM(require("path"));
|
|
246
|
+
var import_stream = require("stream");
|
|
247
|
+
var import_promises = require("stream/promises");
|
|
248
|
+
|
|
249
|
+
// src/common/errors.ts
|
|
250
|
+
var HazoFilesError = class extends Error {
|
|
251
|
+
constructor(message, code, details) {
|
|
252
|
+
super(message);
|
|
253
|
+
this.code = code;
|
|
254
|
+
this.details = details;
|
|
255
|
+
this.name = "HazoFilesError";
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
var FileNotFoundError = class extends HazoFilesError {
|
|
259
|
+
constructor(path3) {
|
|
260
|
+
super(`File not found: ${path3}`, "FILE_NOT_FOUND", { path: path3 });
|
|
261
|
+
this.name = "FileNotFoundError";
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
var DirectoryNotFoundError = class extends HazoFilesError {
|
|
265
|
+
constructor(path3) {
|
|
266
|
+
super(`Directory not found: ${path3}`, "DIRECTORY_NOT_FOUND", { path: path3 });
|
|
267
|
+
this.name = "DirectoryNotFoundError";
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
var FileExistsError = class extends HazoFilesError {
|
|
271
|
+
constructor(path3) {
|
|
272
|
+
super(`File already exists: ${path3}`, "FILE_EXISTS", { path: path3 });
|
|
273
|
+
this.name = "FileExistsError";
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
var DirectoryExistsError = class extends HazoFilesError {
|
|
277
|
+
constructor(path3) {
|
|
278
|
+
super(`Directory already exists: ${path3}`, "DIRECTORY_EXISTS", { path: path3 });
|
|
279
|
+
this.name = "DirectoryExistsError";
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
var DirectoryNotEmptyError = class extends HazoFilesError {
|
|
283
|
+
constructor(path3) {
|
|
284
|
+
super(`Directory is not empty: ${path3}`, "DIRECTORY_NOT_EMPTY", { path: path3 });
|
|
285
|
+
this.name = "DirectoryNotEmptyError";
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
var PermissionDeniedError = class extends HazoFilesError {
|
|
289
|
+
constructor(path3, operation) {
|
|
290
|
+
super(`Permission denied for ${operation} on: ${path3}`, "PERMISSION_DENIED", { path: path3, operation });
|
|
291
|
+
this.name = "PermissionDeniedError";
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
var InvalidPathError = class extends HazoFilesError {
|
|
295
|
+
constructor(path3, reason) {
|
|
296
|
+
super(`Invalid path "${path3}": ${reason}`, "INVALID_PATH", { path: path3, reason });
|
|
297
|
+
this.name = "InvalidPathError";
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
var FileTooLargeError = class extends HazoFilesError {
|
|
301
|
+
constructor(path3, size, maxSize) {
|
|
302
|
+
super(
|
|
303
|
+
`File "${path3}" is too large (${size} bytes). Maximum allowed: ${maxSize} bytes`,
|
|
304
|
+
"FILE_TOO_LARGE",
|
|
305
|
+
{ path: path3, size, maxSize }
|
|
306
|
+
);
|
|
307
|
+
this.name = "FileTooLargeError";
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
var InvalidExtensionError = class extends HazoFilesError {
|
|
311
|
+
constructor(path3, extension, allowedExtensions) {
|
|
312
|
+
super(
|
|
313
|
+
`File extension "${extension}" is not allowed. Allowed: ${allowedExtensions.join(", ")}`,
|
|
314
|
+
"INVALID_EXTENSION",
|
|
315
|
+
{ path: path3, extension, allowedExtensions }
|
|
316
|
+
);
|
|
317
|
+
this.name = "InvalidExtensionError";
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
var AuthenticationError = class extends HazoFilesError {
|
|
321
|
+
constructor(provider, message) {
|
|
322
|
+
super(`Authentication failed for ${provider}: ${message}`, "AUTHENTICATION_ERROR", { provider });
|
|
323
|
+
this.name = "AuthenticationError";
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
var ConfigurationError = class extends HazoFilesError {
|
|
327
|
+
constructor(message) {
|
|
328
|
+
super(`Configuration error: ${message}`, "CONFIGURATION_ERROR");
|
|
329
|
+
this.name = "ConfigurationError";
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
var OperationError = class extends HazoFilesError {
|
|
333
|
+
constructor(operation, message, details) {
|
|
334
|
+
super(`${operation} failed: ${message}`, "OPERATION_ERROR", details);
|
|
335
|
+
this.name = "OperationError";
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
// src/common/utils.ts
|
|
340
|
+
function successResult(data) {
|
|
341
|
+
return {
|
|
342
|
+
success: true,
|
|
343
|
+
data
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
function errorResult(error) {
|
|
347
|
+
return {
|
|
348
|
+
success: false,
|
|
349
|
+
error
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
function generateId() {
|
|
353
|
+
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
354
|
+
}
|
|
355
|
+
function formatBytes(bytes, decimals = 2) {
|
|
356
|
+
if (bytes === 0) return "0 Bytes";
|
|
357
|
+
const k = 1024;
|
|
358
|
+
const dm = decimals < 0 ? 0 : decimals;
|
|
359
|
+
const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB"];
|
|
360
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
361
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
|
|
362
|
+
}
|
|
363
|
+
function isFile(item) {
|
|
364
|
+
return !item.isDirectory;
|
|
365
|
+
}
|
|
366
|
+
function isFolder(item) {
|
|
367
|
+
return item.isDirectory;
|
|
368
|
+
}
|
|
369
|
+
function sortItems(items) {
|
|
370
|
+
return [...items].sort((a, b) => {
|
|
371
|
+
if (a.isDirectory && !b.isDirectory) return -1;
|
|
372
|
+
if (!a.isDirectory && b.isDirectory) return 1;
|
|
373
|
+
return a.name.localeCompare(b.name);
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
function filterItems(items, searchTerm) {
|
|
377
|
+
const term = searchTerm.toLowerCase();
|
|
378
|
+
return items.filter((item) => item.name.toLowerCase().includes(term));
|
|
379
|
+
}
|
|
380
|
+
function createFileItem(params) {
|
|
381
|
+
return {
|
|
382
|
+
id: params.id,
|
|
383
|
+
name: params.name,
|
|
384
|
+
path: params.path,
|
|
385
|
+
size: params.size,
|
|
386
|
+
mimeType: params.mimeType,
|
|
387
|
+
createdAt: params.createdAt || /* @__PURE__ */ new Date(),
|
|
388
|
+
modifiedAt: params.modifiedAt || /* @__PURE__ */ new Date(),
|
|
389
|
+
isDirectory: false,
|
|
390
|
+
parentId: params.parentId,
|
|
391
|
+
metadata: params.metadata
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
function createFolderItem(params) {
|
|
395
|
+
return {
|
|
396
|
+
id: params.id,
|
|
397
|
+
name: params.name,
|
|
398
|
+
path: params.path,
|
|
399
|
+
createdAt: params.createdAt || /* @__PURE__ */ new Date(),
|
|
400
|
+
modifiedAt: params.modifiedAt || /* @__PURE__ */ new Date(),
|
|
401
|
+
isDirectory: true,
|
|
402
|
+
parentId: params.parentId,
|
|
403
|
+
children: params.children,
|
|
404
|
+
metadata: params.metadata
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// src/common/path-utils.ts
|
|
409
|
+
function normalizePath(inputPath) {
|
|
410
|
+
if (!inputPath) return "/";
|
|
411
|
+
let normalized = inputPath.replace(/\\/g, "/");
|
|
412
|
+
normalized = normalized.replace(/\/+/g, "/");
|
|
413
|
+
const parts = normalized.split("/");
|
|
414
|
+
const result = [];
|
|
415
|
+
for (const part of parts) {
|
|
416
|
+
if (part === "." || part === "") continue;
|
|
417
|
+
if (part === "..") {
|
|
418
|
+
result.pop();
|
|
419
|
+
} else {
|
|
420
|
+
result.push(part);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
normalized = "/" + result.join("/");
|
|
424
|
+
return normalized;
|
|
425
|
+
}
|
|
426
|
+
function joinPath(...segments) {
|
|
427
|
+
const joined = segments.map((s) => s.replace(/^\/+|\/+$/g, "")).filter(Boolean).join("/");
|
|
428
|
+
return normalizePath("/" + joined);
|
|
429
|
+
}
|
|
430
|
+
function getParentPath(inputPath) {
|
|
431
|
+
const normalized = normalizePath(inputPath);
|
|
432
|
+
if (normalized === "/") return "/";
|
|
433
|
+
const lastSlash = normalized.lastIndexOf("/");
|
|
434
|
+
if (lastSlash === 0) return "/";
|
|
435
|
+
return normalized.slice(0, lastSlash) || "/";
|
|
436
|
+
}
|
|
437
|
+
function getBaseName(inputPath) {
|
|
438
|
+
const normalized = normalizePath(inputPath);
|
|
439
|
+
if (normalized === "/") return "";
|
|
440
|
+
const lastSlash = normalized.lastIndexOf("/");
|
|
441
|
+
return normalized.slice(lastSlash + 1);
|
|
442
|
+
}
|
|
443
|
+
function getDirName(inputPath) {
|
|
444
|
+
return getParentPath(inputPath);
|
|
445
|
+
}
|
|
446
|
+
function getPathSegments(inputPath) {
|
|
447
|
+
const normalized = normalizePath(inputPath);
|
|
448
|
+
if (normalized === "/") return [];
|
|
449
|
+
return normalized.slice(1).split("/");
|
|
450
|
+
}
|
|
451
|
+
function isChildPath(parentPath, childPath) {
|
|
452
|
+
const normalizedParent = normalizePath(parentPath);
|
|
453
|
+
const normalizedChild = normalizePath(childPath);
|
|
454
|
+
if (normalizedParent === "/") {
|
|
455
|
+
return normalizedChild !== "/";
|
|
456
|
+
}
|
|
457
|
+
return normalizedChild.startsWith(normalizedParent + "/");
|
|
458
|
+
}
|
|
459
|
+
function getRelativePath(basePath, targetPath) {
|
|
460
|
+
const baseSegments = getPathSegments(basePath);
|
|
461
|
+
const targetSegments = getPathSegments(targetPath);
|
|
462
|
+
let commonLength = 0;
|
|
463
|
+
while (commonLength < baseSegments.length && commonLength < targetSegments.length && baseSegments[commonLength] === targetSegments[commonLength]) {
|
|
464
|
+
commonLength++;
|
|
465
|
+
}
|
|
466
|
+
const upCount = baseSegments.length - commonLength;
|
|
467
|
+
const remaining = targetSegments.slice(commonLength);
|
|
468
|
+
const parts = [];
|
|
469
|
+
for (let i = 0; i < upCount; i++) {
|
|
470
|
+
parts.push("..");
|
|
471
|
+
}
|
|
472
|
+
parts.push(...remaining);
|
|
473
|
+
return parts.join("/") || ".";
|
|
474
|
+
}
|
|
475
|
+
function validatePath(inputPath, basePath) {
|
|
476
|
+
normalizePath(inputPath);
|
|
477
|
+
if (inputPath.includes("\0")) {
|
|
478
|
+
throw new InvalidPathError(inputPath, "Path contains null bytes");
|
|
479
|
+
}
|
|
480
|
+
if (basePath) {
|
|
481
|
+
const normalizedBase = normalizePath(basePath);
|
|
482
|
+
const fullPath = joinPath(normalizedBase, inputPath);
|
|
483
|
+
if (!fullPath.startsWith(normalizedBase)) {
|
|
484
|
+
throw new InvalidPathError(inputPath, "Path traversal detected");
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
function sanitizeFilename(filename) {
|
|
489
|
+
let sanitized = filename.replace(/[<>:"/\\|?*\x00-\x1F]/g, "_").replace(/^\.+/, "").trim();
|
|
490
|
+
if (!sanitized) {
|
|
491
|
+
sanitized = "unnamed";
|
|
492
|
+
}
|
|
493
|
+
if (sanitized.length > 255) {
|
|
494
|
+
const ext = getExtension(sanitized);
|
|
495
|
+
const name = sanitized.slice(0, 255 - ext.length);
|
|
496
|
+
sanitized = name + ext;
|
|
497
|
+
}
|
|
498
|
+
return sanitized;
|
|
499
|
+
}
|
|
500
|
+
function getExtension(filename) {
|
|
501
|
+
const lastDot = filename.lastIndexOf(".");
|
|
502
|
+
if (lastDot === -1 || lastDot === 0) return "";
|
|
503
|
+
return filename.slice(lastDot);
|
|
504
|
+
}
|
|
505
|
+
function getNameWithoutExtension(filename) {
|
|
506
|
+
const lastDot = filename.lastIndexOf(".");
|
|
507
|
+
if (lastDot === -1 || lastDot === 0) return filename;
|
|
508
|
+
return filename.slice(0, lastDot);
|
|
509
|
+
}
|
|
510
|
+
function hasExtension(filename, extension) {
|
|
511
|
+
const ext = getExtension(filename).toLowerCase();
|
|
512
|
+
const targetExt = extension.startsWith(".") ? extension.toLowerCase() : "." + extension.toLowerCase();
|
|
513
|
+
return ext === targetExt;
|
|
514
|
+
}
|
|
515
|
+
function getBreadcrumbs(inputPath) {
|
|
516
|
+
const segments = getPathSegments(inputPath);
|
|
517
|
+
const breadcrumbs = [
|
|
518
|
+
{ name: "Root", path: "/" }
|
|
519
|
+
];
|
|
520
|
+
let currentPath = "";
|
|
521
|
+
for (const segment of segments) {
|
|
522
|
+
currentPath += "/" + segment;
|
|
523
|
+
breadcrumbs.push({
|
|
524
|
+
name: segment,
|
|
525
|
+
path: currentPath
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
return breadcrumbs;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// src/common/base-module.ts
|
|
532
|
+
var BaseStorageModule = class {
|
|
533
|
+
constructor() {
|
|
534
|
+
this.config = null;
|
|
535
|
+
this._initialized = false;
|
|
536
|
+
// Utility methods available to subclasses
|
|
537
|
+
this.normalizePath = normalizePath;
|
|
538
|
+
this.joinPath = joinPath;
|
|
539
|
+
this.getBaseName = getBaseName;
|
|
540
|
+
this.getParentPath = getParentPath;
|
|
541
|
+
this.successResult = successResult;
|
|
542
|
+
this.errorResult = errorResult;
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Check if the module is initialized
|
|
546
|
+
*/
|
|
547
|
+
get isInitialized() {
|
|
548
|
+
return this._initialized;
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Initialize the module with configuration.
|
|
552
|
+
* Subclasses should call super.initialize(config) first.
|
|
553
|
+
*/
|
|
554
|
+
async initialize(config) {
|
|
555
|
+
this.config = config;
|
|
556
|
+
this._initialized = true;
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Ensure the module is initialized before operations
|
|
560
|
+
*/
|
|
561
|
+
ensureInitialized() {
|
|
562
|
+
if (!this._initialized || !this.config) {
|
|
563
|
+
throw new ConfigurationError("Module not initialized. Call initialize() first.");
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Get the provider-specific configuration
|
|
568
|
+
*/
|
|
569
|
+
getProviderConfig() {
|
|
570
|
+
this.ensureInitialized();
|
|
571
|
+
const providerConfig = this.config[this.provider];
|
|
572
|
+
if (!providerConfig) {
|
|
573
|
+
throw new ConfigurationError(`No configuration found for provider: ${this.provider}`);
|
|
574
|
+
}
|
|
575
|
+
return providerConfig;
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Get folder tree structure.
|
|
579
|
+
* Default implementation that can be overridden by subclasses for optimization.
|
|
580
|
+
*/
|
|
581
|
+
async getFolderTree(path3 = "/", depth = 3) {
|
|
582
|
+
this.ensureInitialized();
|
|
583
|
+
try {
|
|
584
|
+
const result = await this.buildTree(path3, depth, 0);
|
|
585
|
+
return successResult(result);
|
|
586
|
+
} catch (error) {
|
|
587
|
+
return errorResult(`Failed to get folder tree: ${error.message}`);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Recursively build folder tree
|
|
592
|
+
*/
|
|
593
|
+
async buildTree(path3, maxDepth, currentDepth) {
|
|
594
|
+
if (currentDepth >= maxDepth) {
|
|
595
|
+
return [];
|
|
596
|
+
}
|
|
597
|
+
const listResult = await this.listDirectory(path3, { recursive: false });
|
|
598
|
+
if (!listResult.success || !listResult.data) {
|
|
599
|
+
return [];
|
|
600
|
+
}
|
|
601
|
+
const folders = listResult.data.filter((item) => item.isDirectory);
|
|
602
|
+
const nodes = [];
|
|
603
|
+
for (const folder of folders) {
|
|
604
|
+
const children = await this.buildTree(folder.path, maxDepth, currentDepth + 1);
|
|
605
|
+
nodes.push({
|
|
606
|
+
id: folder.id,
|
|
607
|
+
name: folder.name,
|
|
608
|
+
path: folder.path,
|
|
609
|
+
children
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
return nodes;
|
|
613
|
+
}
|
|
614
|
+
};
|
|
615
|
+
|
|
616
|
+
// src/common/mime-types.ts
|
|
617
|
+
var MIME_TYPES = {
|
|
618
|
+
// Text
|
|
619
|
+
".txt": "text/plain",
|
|
620
|
+
".html": "text/html",
|
|
621
|
+
".htm": "text/html",
|
|
622
|
+
".css": "text/css",
|
|
623
|
+
".csv": "text/csv",
|
|
624
|
+
".xml": "text/xml",
|
|
625
|
+
".json": "application/json",
|
|
626
|
+
".js": "application/javascript",
|
|
627
|
+
".ts": "application/typescript",
|
|
628
|
+
".jsx": "text/jsx",
|
|
629
|
+
".tsx": "text/tsx",
|
|
630
|
+
".md": "text/markdown",
|
|
631
|
+
".yaml": "text/yaml",
|
|
632
|
+
".yml": "text/yaml",
|
|
633
|
+
// Images
|
|
634
|
+
".png": "image/png",
|
|
635
|
+
".jpg": "image/jpeg",
|
|
636
|
+
".jpeg": "image/jpeg",
|
|
637
|
+
".gif": "image/gif",
|
|
638
|
+
".bmp": "image/bmp",
|
|
639
|
+
".webp": "image/webp",
|
|
640
|
+
".svg": "image/svg+xml",
|
|
641
|
+
".ico": "image/x-icon",
|
|
642
|
+
// Documents
|
|
643
|
+
".pdf": "application/pdf",
|
|
644
|
+
".doc": "application/msword",
|
|
645
|
+
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
646
|
+
".xls": "application/vnd.ms-excel",
|
|
647
|
+
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
648
|
+
".ppt": "application/vnd.ms-powerpoint",
|
|
649
|
+
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
650
|
+
// Archives
|
|
651
|
+
".zip": "application/zip",
|
|
652
|
+
".rar": "application/x-rar-compressed",
|
|
653
|
+
".7z": "application/x-7z-compressed",
|
|
654
|
+
".tar": "application/x-tar",
|
|
655
|
+
".gz": "application/gzip",
|
|
656
|
+
// Audio
|
|
657
|
+
".mp3": "audio/mpeg",
|
|
658
|
+
".wav": "audio/wav",
|
|
659
|
+
".ogg": "audio/ogg",
|
|
660
|
+
".m4a": "audio/mp4",
|
|
661
|
+
".flac": "audio/flac",
|
|
662
|
+
// Video
|
|
663
|
+
".mp4": "video/mp4",
|
|
664
|
+
".webm": "video/webm",
|
|
665
|
+
".avi": "video/x-msvideo",
|
|
666
|
+
".mov": "video/quicktime",
|
|
667
|
+
".wmv": "video/x-ms-wmv",
|
|
668
|
+
".mkv": "video/x-matroska",
|
|
669
|
+
// Fonts
|
|
670
|
+
".ttf": "font/ttf",
|
|
671
|
+
".otf": "font/otf",
|
|
672
|
+
".woff": "font/woff",
|
|
673
|
+
".woff2": "font/woff2",
|
|
674
|
+
// Other
|
|
675
|
+
".exe": "application/x-msdownload",
|
|
676
|
+
".dmg": "application/x-apple-diskimage",
|
|
677
|
+
".bin": "application/octet-stream"
|
|
678
|
+
};
|
|
679
|
+
var EXTENSION_BY_MIME = Object.entries(MIME_TYPES).reduce(
|
|
680
|
+
(acc, [ext, mime]) => {
|
|
681
|
+
if (!acc[mime]) {
|
|
682
|
+
acc[mime] = ext;
|
|
683
|
+
}
|
|
684
|
+
return acc;
|
|
685
|
+
},
|
|
686
|
+
{}
|
|
687
|
+
);
|
|
688
|
+
function getMimeType(filename) {
|
|
689
|
+
const ext = getExtension(filename).toLowerCase();
|
|
690
|
+
return MIME_TYPES[ext] || "application/octet-stream";
|
|
691
|
+
}
|
|
692
|
+
function getExtensionFromMime(mimeType) {
|
|
693
|
+
return EXTENSION_BY_MIME[mimeType] || "";
|
|
694
|
+
}
|
|
695
|
+
function isImage(filenameOrMime) {
|
|
696
|
+
const mime = filenameOrMime.includes("/") ? filenameOrMime : getMimeType(filenameOrMime);
|
|
697
|
+
return mime.startsWith("image/");
|
|
698
|
+
}
|
|
699
|
+
function isVideo(filenameOrMime) {
|
|
700
|
+
const mime = filenameOrMime.includes("/") ? filenameOrMime : getMimeType(filenameOrMime);
|
|
701
|
+
return mime.startsWith("video/");
|
|
702
|
+
}
|
|
703
|
+
function isAudio(filenameOrMime) {
|
|
704
|
+
const mime = filenameOrMime.includes("/") ? filenameOrMime : getMimeType(filenameOrMime);
|
|
705
|
+
return mime.startsWith("audio/");
|
|
706
|
+
}
|
|
707
|
+
function isText(filenameOrMime) {
|
|
708
|
+
const mime = filenameOrMime.includes("/") ? filenameOrMime : getMimeType(filenameOrMime);
|
|
709
|
+
return mime.startsWith("text/") || mime === "application/json" || mime === "application/javascript";
|
|
710
|
+
}
|
|
711
|
+
function isDocument(filenameOrMime) {
|
|
712
|
+
const mime = filenameOrMime.includes("/") ? filenameOrMime : getMimeType(filenameOrMime);
|
|
713
|
+
return mime === "application/pdf" || mime.includes("document") || mime.includes("spreadsheet") || mime.includes("presentation");
|
|
714
|
+
}
|
|
715
|
+
function isPreviewable(filenameOrMime) {
|
|
716
|
+
return isImage(filenameOrMime) || isText(filenameOrMime) || isVideo(filenameOrMime) || isAudio(filenameOrMime);
|
|
717
|
+
}
|
|
718
|
+
function getFileCategory(filenameOrMime) {
|
|
719
|
+
if (isImage(filenameOrMime)) return "image";
|
|
720
|
+
if (isVideo(filenameOrMime)) return "video";
|
|
721
|
+
if (isAudio(filenameOrMime)) return "audio";
|
|
722
|
+
if (isDocument(filenameOrMime)) return "document";
|
|
723
|
+
if (isText(filenameOrMime)) return "text";
|
|
724
|
+
return "other";
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// src/modules/local/index.ts
|
|
728
|
+
var LocalStorageModule = class extends BaseStorageModule {
|
|
729
|
+
constructor() {
|
|
730
|
+
super(...arguments);
|
|
731
|
+
this.provider = "local";
|
|
732
|
+
this.basePath = "";
|
|
733
|
+
this.allowedExtensions = [];
|
|
734
|
+
this.maxFileSize = 0;
|
|
735
|
+
}
|
|
736
|
+
async initialize(config) {
|
|
737
|
+
await super.initialize(config);
|
|
738
|
+
const localConfig = this.getProviderConfig();
|
|
739
|
+
this.basePath = path2.resolve(localConfig.basePath);
|
|
740
|
+
this.allowedExtensions = localConfig.allowedExtensions || [];
|
|
741
|
+
this.maxFileSize = localConfig.maxFileSize || 0;
|
|
742
|
+
await fs2.promises.mkdir(this.basePath, { recursive: true });
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* Resolve a virtual path to an absolute file system path
|
|
746
|
+
*/
|
|
747
|
+
resolveFullPath(virtualPath) {
|
|
748
|
+
const normalized = this.normalizePath(virtualPath);
|
|
749
|
+
const relativePath = normalized.startsWith("/") ? normalized.slice(1) : normalized;
|
|
750
|
+
return path2.join(this.basePath, relativePath);
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* Convert absolute path back to virtual path
|
|
754
|
+
*/
|
|
755
|
+
toVirtualPath(absolutePath) {
|
|
756
|
+
const relative2 = path2.relative(this.basePath, absolutePath);
|
|
757
|
+
return this.normalizePath("/" + relative2.replace(/\\/g, "/"));
|
|
758
|
+
}
|
|
759
|
+
/**
|
|
760
|
+
* Validate file extension against allowed list
|
|
761
|
+
*/
|
|
762
|
+
validateExtension(filename) {
|
|
763
|
+
if (this.allowedExtensions.length === 0) return;
|
|
764
|
+
const ext = getExtension(filename).toLowerCase().slice(1);
|
|
765
|
+
if (!this.allowedExtensions.includes(ext)) {
|
|
766
|
+
throw new InvalidExtensionError(filename, ext, this.allowedExtensions);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* Validate file size against maximum
|
|
771
|
+
*/
|
|
772
|
+
validateFileSize(size, filename) {
|
|
773
|
+
if (this.maxFileSize > 0 && size > this.maxFileSize) {
|
|
774
|
+
throw new FileTooLargeError(filename, size, this.maxFileSize);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* Create file/folder stats to FileSystemItem
|
|
779
|
+
*/
|
|
780
|
+
async statToItem(fullPath, stats) {
|
|
781
|
+
const virtualPath = this.toVirtualPath(fullPath);
|
|
782
|
+
const name = path2.basename(fullPath);
|
|
783
|
+
const id = generateId();
|
|
784
|
+
if (stats.isDirectory()) {
|
|
785
|
+
return createFolderItem({
|
|
786
|
+
id,
|
|
787
|
+
name,
|
|
788
|
+
path: virtualPath,
|
|
789
|
+
createdAt: stats.birthtime,
|
|
790
|
+
modifiedAt: stats.mtime
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
return createFileItem({
|
|
794
|
+
id,
|
|
795
|
+
name,
|
|
796
|
+
path: virtualPath,
|
|
797
|
+
size: stats.size,
|
|
798
|
+
mimeType: getMimeType(name),
|
|
799
|
+
createdAt: stats.birthtime,
|
|
800
|
+
modifiedAt: stats.mtime
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
async createDirectory(virtualPath) {
|
|
804
|
+
this.ensureInitialized();
|
|
805
|
+
try {
|
|
806
|
+
const fullPath = this.resolveFullPath(virtualPath);
|
|
807
|
+
try {
|
|
808
|
+
const stats2 = await fs2.promises.stat(fullPath);
|
|
809
|
+
if (stats2.isDirectory()) {
|
|
810
|
+
throw new DirectoryExistsError(virtualPath);
|
|
811
|
+
}
|
|
812
|
+
throw new FileExistsError(virtualPath);
|
|
813
|
+
} catch (error) {
|
|
814
|
+
if (error.code !== "ENOENT") {
|
|
815
|
+
throw error;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
await fs2.promises.mkdir(fullPath, { recursive: true });
|
|
819
|
+
const stats = await fs2.promises.stat(fullPath);
|
|
820
|
+
const item = await this.statToItem(fullPath, stats);
|
|
821
|
+
return this.successResult(item);
|
|
822
|
+
} catch (error) {
|
|
823
|
+
if (error instanceof DirectoryExistsError || error instanceof FileExistsError) {
|
|
824
|
+
return this.errorResult(error.message);
|
|
825
|
+
}
|
|
826
|
+
return this.errorResult(`Failed to create directory: ${error.message}`);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
async removeDirectory(virtualPath, recursive = false) {
|
|
830
|
+
this.ensureInitialized();
|
|
831
|
+
try {
|
|
832
|
+
const fullPath = this.resolveFullPath(virtualPath);
|
|
833
|
+
const stats = await fs2.promises.stat(fullPath).catch(() => null);
|
|
834
|
+
if (!stats) {
|
|
835
|
+
throw new DirectoryNotFoundError(virtualPath);
|
|
836
|
+
}
|
|
837
|
+
if (!stats.isDirectory()) {
|
|
838
|
+
throw new DirectoryNotFoundError(virtualPath);
|
|
839
|
+
}
|
|
840
|
+
if (!recursive) {
|
|
841
|
+
const contents = await fs2.promises.readdir(fullPath);
|
|
842
|
+
if (contents.length > 0) {
|
|
843
|
+
throw new DirectoryNotEmptyError(virtualPath);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
await fs2.promises.rm(fullPath, { recursive, force: true });
|
|
847
|
+
return this.successResult();
|
|
848
|
+
} catch (error) {
|
|
849
|
+
if (error instanceof DirectoryNotFoundError || error instanceof DirectoryNotEmptyError) {
|
|
850
|
+
return this.errorResult(error.message);
|
|
851
|
+
}
|
|
852
|
+
return this.errorResult(`Failed to remove directory: ${error.message}`);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
async uploadFile(source, remotePath, options = {}) {
|
|
856
|
+
this.ensureInitialized();
|
|
857
|
+
try {
|
|
858
|
+
const fullPath = this.resolveFullPath(remotePath);
|
|
859
|
+
const filename = path2.basename(fullPath);
|
|
860
|
+
this.validateExtension(filename);
|
|
861
|
+
if (!options.overwrite) {
|
|
862
|
+
try {
|
|
863
|
+
await fs2.promises.stat(fullPath);
|
|
864
|
+
throw new FileExistsError(remotePath);
|
|
865
|
+
} catch (error) {
|
|
866
|
+
if (error.code !== "ENOENT") {
|
|
867
|
+
if (error instanceof FileExistsError) throw error;
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
const parentDir = path2.dirname(fullPath);
|
|
872
|
+
await fs2.promises.mkdir(parentDir, { recursive: true });
|
|
873
|
+
if (typeof source === "string") {
|
|
874
|
+
const stats2 = await fs2.promises.stat(source);
|
|
875
|
+
this.validateFileSize(stats2.size, filename);
|
|
876
|
+
if (options.onProgress) {
|
|
877
|
+
const totalBytes = stats2.size;
|
|
878
|
+
let bytesTransferred = 0;
|
|
879
|
+
const readStream = fs2.createReadStream(source);
|
|
880
|
+
const writeStream = fs2.createWriteStream(fullPath);
|
|
881
|
+
readStream.on("data", (chunk) => {
|
|
882
|
+
bytesTransferred += chunk.length;
|
|
883
|
+
const progress = bytesTransferred / totalBytes * 100;
|
|
884
|
+
options.onProgress(progress, bytesTransferred, totalBytes);
|
|
885
|
+
});
|
|
886
|
+
await (0, import_promises.pipeline)(readStream, writeStream);
|
|
887
|
+
} else {
|
|
888
|
+
await fs2.promises.copyFile(source, fullPath);
|
|
889
|
+
}
|
|
890
|
+
} else if (Buffer.isBuffer(source)) {
|
|
891
|
+
this.validateFileSize(source.length, filename);
|
|
892
|
+
await fs2.promises.writeFile(fullPath, source);
|
|
893
|
+
if (options.onProgress) {
|
|
894
|
+
options.onProgress(100, source.length, source.length);
|
|
895
|
+
}
|
|
896
|
+
} else {
|
|
897
|
+
const writeStream = fs2.createWriteStream(fullPath);
|
|
898
|
+
const readable = import_stream.Readable.fromWeb(source);
|
|
899
|
+
await (0, import_promises.pipeline)(readable, writeStream);
|
|
900
|
+
}
|
|
901
|
+
const stats = await fs2.promises.stat(fullPath);
|
|
902
|
+
const item = await this.statToItem(fullPath, stats);
|
|
903
|
+
return this.successResult(item);
|
|
904
|
+
} catch (error) {
|
|
905
|
+
if (error instanceof FileExistsError || error instanceof FileTooLargeError || error instanceof InvalidExtensionError) {
|
|
906
|
+
return this.errorResult(error.message);
|
|
907
|
+
}
|
|
908
|
+
return this.errorResult(`Failed to upload file: ${error.message}`);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
async downloadFile(remotePath, localPath, options = {}) {
|
|
912
|
+
this.ensureInitialized();
|
|
913
|
+
try {
|
|
914
|
+
const fullPath = this.resolveFullPath(remotePath);
|
|
915
|
+
const stats = await fs2.promises.stat(fullPath).catch(() => null);
|
|
916
|
+
if (!stats || stats.isDirectory()) {
|
|
917
|
+
throw new FileNotFoundError(remotePath);
|
|
918
|
+
}
|
|
919
|
+
if (localPath) {
|
|
920
|
+
const destDir = path2.dirname(localPath);
|
|
921
|
+
await fs2.promises.mkdir(destDir, { recursive: true });
|
|
922
|
+
if (options.onProgress) {
|
|
923
|
+
const totalBytes = stats.size;
|
|
924
|
+
let bytesTransferred = 0;
|
|
925
|
+
const readStream = fs2.createReadStream(fullPath);
|
|
926
|
+
const writeStream = fs2.createWriteStream(localPath);
|
|
927
|
+
readStream.on("data", (chunk) => {
|
|
928
|
+
bytesTransferred += chunk.length;
|
|
929
|
+
const progress = bytesTransferred / totalBytes * 100;
|
|
930
|
+
options.onProgress(progress, bytesTransferred, totalBytes);
|
|
931
|
+
});
|
|
932
|
+
await (0, import_promises.pipeline)(readStream, writeStream);
|
|
933
|
+
} else {
|
|
934
|
+
await fs2.promises.copyFile(fullPath, localPath);
|
|
935
|
+
}
|
|
936
|
+
return this.successResult(localPath);
|
|
937
|
+
} else {
|
|
938
|
+
const buffer = await fs2.promises.readFile(fullPath);
|
|
939
|
+
if (options.onProgress) {
|
|
940
|
+
options.onProgress(100, buffer.length, buffer.length);
|
|
941
|
+
}
|
|
942
|
+
return this.successResult(buffer);
|
|
943
|
+
}
|
|
944
|
+
} catch (error) {
|
|
945
|
+
if (error instanceof FileNotFoundError) {
|
|
946
|
+
return this.errorResult(error.message);
|
|
947
|
+
}
|
|
948
|
+
return this.errorResult(`Failed to download file: ${error.message}`);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
async moveItem(sourcePath, destinationPath, options = {}) {
|
|
952
|
+
this.ensureInitialized();
|
|
953
|
+
try {
|
|
954
|
+
const sourceFullPath = this.resolveFullPath(sourcePath);
|
|
955
|
+
const destFullPath = this.resolveFullPath(destinationPath);
|
|
956
|
+
const sourceStats = await fs2.promises.stat(sourceFullPath).catch(() => null);
|
|
957
|
+
if (!sourceStats) {
|
|
958
|
+
throw new FileNotFoundError(sourcePath);
|
|
959
|
+
}
|
|
960
|
+
if (!options.overwrite) {
|
|
961
|
+
const destStats = await fs2.promises.stat(destFullPath).catch(() => null);
|
|
962
|
+
if (destStats) {
|
|
963
|
+
throw new FileExistsError(destinationPath);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
const destParent = path2.dirname(destFullPath);
|
|
967
|
+
await fs2.promises.mkdir(destParent, { recursive: true });
|
|
968
|
+
await fs2.promises.rename(sourceFullPath, destFullPath);
|
|
969
|
+
const newStats = await fs2.promises.stat(destFullPath);
|
|
970
|
+
const item = await this.statToItem(destFullPath, newStats);
|
|
971
|
+
return this.successResult(item);
|
|
972
|
+
} catch (error) {
|
|
973
|
+
if (error instanceof FileNotFoundError || error instanceof FileExistsError) {
|
|
974
|
+
return this.errorResult(error.message);
|
|
975
|
+
}
|
|
976
|
+
return this.errorResult(`Failed to move item: ${error.message}`);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
async deleteFile(virtualPath) {
|
|
980
|
+
this.ensureInitialized();
|
|
981
|
+
try {
|
|
982
|
+
const fullPath = this.resolveFullPath(virtualPath);
|
|
983
|
+
const stats = await fs2.promises.stat(fullPath).catch(() => null);
|
|
984
|
+
if (!stats) {
|
|
985
|
+
throw new FileNotFoundError(virtualPath);
|
|
986
|
+
}
|
|
987
|
+
if (stats.isDirectory()) {
|
|
988
|
+
throw new FileNotFoundError(virtualPath);
|
|
989
|
+
}
|
|
990
|
+
await fs2.promises.unlink(fullPath);
|
|
991
|
+
return this.successResult();
|
|
992
|
+
} catch (error) {
|
|
993
|
+
if (error instanceof FileNotFoundError) {
|
|
994
|
+
return this.errorResult(error.message);
|
|
995
|
+
}
|
|
996
|
+
return this.errorResult(`Failed to delete file: ${error.message}`);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
async renameFile(virtualPath, newName, options = {}) {
|
|
1000
|
+
this.ensureInitialized();
|
|
1001
|
+
try {
|
|
1002
|
+
const fullPath = this.resolveFullPath(virtualPath);
|
|
1003
|
+
const stats = await fs2.promises.stat(fullPath).catch(() => null);
|
|
1004
|
+
if (!stats || stats.isDirectory()) {
|
|
1005
|
+
throw new FileNotFoundError(virtualPath);
|
|
1006
|
+
}
|
|
1007
|
+
this.validateExtension(newName);
|
|
1008
|
+
const parentDir = path2.dirname(fullPath);
|
|
1009
|
+
const newFullPath = path2.join(parentDir, newName);
|
|
1010
|
+
if (!options.overwrite) {
|
|
1011
|
+
const destStats = await fs2.promises.stat(newFullPath).catch(() => null);
|
|
1012
|
+
if (destStats) {
|
|
1013
|
+
throw new FileExistsError(newName);
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
await fs2.promises.rename(fullPath, newFullPath);
|
|
1017
|
+
const newStats = await fs2.promises.stat(newFullPath);
|
|
1018
|
+
const item = await this.statToItem(newFullPath, newStats);
|
|
1019
|
+
return this.successResult(item);
|
|
1020
|
+
} catch (error) {
|
|
1021
|
+
if (error instanceof FileNotFoundError || error instanceof FileExistsError || error instanceof InvalidExtensionError) {
|
|
1022
|
+
return this.errorResult(error.message);
|
|
1023
|
+
}
|
|
1024
|
+
return this.errorResult(`Failed to rename file: ${error.message}`);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
async renameFolder(virtualPath, newName, options = {}) {
|
|
1028
|
+
this.ensureInitialized();
|
|
1029
|
+
try {
|
|
1030
|
+
const fullPath = this.resolveFullPath(virtualPath);
|
|
1031
|
+
const stats = await fs2.promises.stat(fullPath).catch(() => null);
|
|
1032
|
+
if (!stats || !stats.isDirectory()) {
|
|
1033
|
+
throw new DirectoryNotFoundError(virtualPath);
|
|
1034
|
+
}
|
|
1035
|
+
const parentDir = path2.dirname(fullPath);
|
|
1036
|
+
const newFullPath = path2.join(parentDir, newName);
|
|
1037
|
+
if (!options.overwrite) {
|
|
1038
|
+
const destStats = await fs2.promises.stat(newFullPath).catch(() => null);
|
|
1039
|
+
if (destStats) {
|
|
1040
|
+
throw new DirectoryExistsError(newName);
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
await fs2.promises.rename(fullPath, newFullPath);
|
|
1044
|
+
const newStats = await fs2.promises.stat(newFullPath);
|
|
1045
|
+
const item = await this.statToItem(newFullPath, newStats);
|
|
1046
|
+
return this.successResult(item);
|
|
1047
|
+
} catch (error) {
|
|
1048
|
+
if (error instanceof DirectoryNotFoundError || error instanceof DirectoryExistsError) {
|
|
1049
|
+
return this.errorResult(error.message);
|
|
1050
|
+
}
|
|
1051
|
+
return this.errorResult(`Failed to rename folder: ${error.message}`);
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
async listDirectory(virtualPath, options = {}) {
|
|
1055
|
+
this.ensureInitialized();
|
|
1056
|
+
try {
|
|
1057
|
+
const fullPath = this.resolveFullPath(virtualPath);
|
|
1058
|
+
const stats = await fs2.promises.stat(fullPath).catch(() => null);
|
|
1059
|
+
if (!stats || !stats.isDirectory()) {
|
|
1060
|
+
throw new DirectoryNotFoundError(virtualPath);
|
|
1061
|
+
}
|
|
1062
|
+
const entries = await fs2.promises.readdir(fullPath, { withFileTypes: true });
|
|
1063
|
+
const items = [];
|
|
1064
|
+
for (const entry of entries) {
|
|
1065
|
+
if (!options.includeHidden && entry.name.startsWith(".")) {
|
|
1066
|
+
continue;
|
|
1067
|
+
}
|
|
1068
|
+
const entryPath = path2.join(fullPath, entry.name);
|
|
1069
|
+
const entryStats = await fs2.promises.stat(entryPath);
|
|
1070
|
+
const item = await this.statToItem(entryPath, entryStats);
|
|
1071
|
+
if (options.filter && !options.filter(item)) {
|
|
1072
|
+
continue;
|
|
1073
|
+
}
|
|
1074
|
+
items.push(item);
|
|
1075
|
+
if (options.recursive && entry.isDirectory()) {
|
|
1076
|
+
const subResult = await this.listDirectory(
|
|
1077
|
+
this.toVirtualPath(entryPath),
|
|
1078
|
+
options
|
|
1079
|
+
);
|
|
1080
|
+
if (subResult.success && subResult.data) {
|
|
1081
|
+
items.push(...subResult.data);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
items.sort((a, b) => {
|
|
1086
|
+
if (a.isDirectory && !b.isDirectory) return -1;
|
|
1087
|
+
if (!a.isDirectory && b.isDirectory) return 1;
|
|
1088
|
+
return a.name.localeCompare(b.name);
|
|
1089
|
+
});
|
|
1090
|
+
return this.successResult(items);
|
|
1091
|
+
} catch (error) {
|
|
1092
|
+
if (error instanceof DirectoryNotFoundError) {
|
|
1093
|
+
return this.errorResult(error.message);
|
|
1094
|
+
}
|
|
1095
|
+
return this.errorResult(`Failed to list directory: ${error.message}`);
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
async getItem(virtualPath) {
|
|
1099
|
+
this.ensureInitialized();
|
|
1100
|
+
try {
|
|
1101
|
+
const fullPath = this.resolveFullPath(virtualPath);
|
|
1102
|
+
const stats = await fs2.promises.stat(fullPath).catch(() => null);
|
|
1103
|
+
if (!stats) {
|
|
1104
|
+
throw new FileNotFoundError(virtualPath);
|
|
1105
|
+
}
|
|
1106
|
+
const item = await this.statToItem(fullPath, stats);
|
|
1107
|
+
return this.successResult(item);
|
|
1108
|
+
} catch (error) {
|
|
1109
|
+
if (error instanceof FileNotFoundError) {
|
|
1110
|
+
return this.errorResult(error.message);
|
|
1111
|
+
}
|
|
1112
|
+
return this.errorResult(`Failed to get item: ${error.message}`);
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
async exists(virtualPath) {
|
|
1116
|
+
this.ensureInitialized();
|
|
1117
|
+
try {
|
|
1118
|
+
const fullPath = this.resolveFullPath(virtualPath);
|
|
1119
|
+
await fs2.promises.stat(fullPath);
|
|
1120
|
+
return true;
|
|
1121
|
+
} catch {
|
|
1122
|
+
return false;
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
};
|
|
1126
|
+
function createLocalModule() {
|
|
1127
|
+
return new LocalStorageModule();
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
// src/modules/google-drive/index.ts
|
|
1131
|
+
var import_googleapis2 = require("googleapis");
|
|
1132
|
+
var import_stream2 = require("stream");
|
|
1133
|
+
|
|
1134
|
+
// src/modules/google-drive/auth.ts
|
|
1135
|
+
var import_googleapis = require("googleapis");
|
|
1136
|
+
var GOOGLE_DRIVE_SCOPES = [
|
|
1137
|
+
"https://www.googleapis.com/auth/drive.file",
|
|
1138
|
+
"https://www.googleapis.com/auth/drive.metadata.readonly",
|
|
1139
|
+
"https://www.googleapis.com/auth/userinfo.email",
|
|
1140
|
+
"https://www.googleapis.com/auth/userinfo.profile"
|
|
1141
|
+
];
|
|
1142
|
+
var GoogleDriveAuth = class {
|
|
1143
|
+
constructor(config, callbacks = {}) {
|
|
1144
|
+
this.tokens = null;
|
|
1145
|
+
this.oauth2Client = new import_googleapis.google.auth.OAuth2(
|
|
1146
|
+
config.clientId,
|
|
1147
|
+
config.clientSecret,
|
|
1148
|
+
config.redirectUri
|
|
1149
|
+
);
|
|
1150
|
+
this.callbacks = callbacks;
|
|
1151
|
+
this.oauth2Client.on("tokens", async (tokens) => {
|
|
1152
|
+
if (this.tokens) {
|
|
1153
|
+
const updatedTokens = {
|
|
1154
|
+
...this.tokens,
|
|
1155
|
+
accessToken: tokens.access_token || this.tokens.accessToken,
|
|
1156
|
+
expiryDate: tokens.expiry_date ?? this.tokens.expiryDate
|
|
1157
|
+
};
|
|
1158
|
+
if (tokens.refresh_token) {
|
|
1159
|
+
updatedTokens.refreshToken = tokens.refresh_token;
|
|
1160
|
+
}
|
|
1161
|
+
this.tokens = updatedTokens;
|
|
1162
|
+
if (this.callbacks.onTokensUpdated) {
|
|
1163
|
+
await this.callbacks.onTokensUpdated(updatedTokens);
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
/**
|
|
1169
|
+
* Get the OAuth2 client instance
|
|
1170
|
+
*/
|
|
1171
|
+
getClient() {
|
|
1172
|
+
return this.oauth2Client;
|
|
1173
|
+
}
|
|
1174
|
+
/**
|
|
1175
|
+
* Generate the authorization URL for OAuth consent
|
|
1176
|
+
*/
|
|
1177
|
+
getAuthUrl(state) {
|
|
1178
|
+
return this.oauth2Client.generateAuthUrl({
|
|
1179
|
+
access_type: "offline",
|
|
1180
|
+
scope: GOOGLE_DRIVE_SCOPES,
|
|
1181
|
+
prompt: "consent",
|
|
1182
|
+
state
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1185
|
+
/**
|
|
1186
|
+
* Exchange authorization code for tokens
|
|
1187
|
+
*/
|
|
1188
|
+
async exchangeCodeForTokens(code) {
|
|
1189
|
+
const { tokens } = await this.oauth2Client.getToken(code);
|
|
1190
|
+
this.tokens = {
|
|
1191
|
+
accessToken: tokens.access_token,
|
|
1192
|
+
refreshToken: tokens.refresh_token,
|
|
1193
|
+
expiryDate: tokens.expiry_date || void 0,
|
|
1194
|
+
scope: tokens.scope || void 0
|
|
1195
|
+
};
|
|
1196
|
+
this.oauth2Client.setCredentials(tokens);
|
|
1197
|
+
if (this.callbacks.onTokensUpdated) {
|
|
1198
|
+
await this.callbacks.onTokensUpdated(this.tokens);
|
|
1199
|
+
}
|
|
1200
|
+
return this.tokens;
|
|
1201
|
+
}
|
|
1202
|
+
/**
|
|
1203
|
+
* Set tokens directly (e.g., from stored tokens)
|
|
1204
|
+
*/
|
|
1205
|
+
async setTokens(tokens) {
|
|
1206
|
+
this.tokens = tokens;
|
|
1207
|
+
const credentials = {
|
|
1208
|
+
access_token: tokens.accessToken,
|
|
1209
|
+
refresh_token: tokens.refreshToken,
|
|
1210
|
+
expiry_date: tokens.expiryDate
|
|
1211
|
+
};
|
|
1212
|
+
this.oauth2Client.setCredentials(credentials);
|
|
1213
|
+
}
|
|
1214
|
+
/**
|
|
1215
|
+
* Load tokens from storage using callback
|
|
1216
|
+
*/
|
|
1217
|
+
async loadStoredTokens() {
|
|
1218
|
+
if (!this.callbacks.getStoredTokens) {
|
|
1219
|
+
return false;
|
|
1220
|
+
}
|
|
1221
|
+
const tokens = await this.callbacks.getStoredTokens();
|
|
1222
|
+
if (tokens) {
|
|
1223
|
+
await this.setTokens(tokens);
|
|
1224
|
+
return true;
|
|
1225
|
+
}
|
|
1226
|
+
return false;
|
|
1227
|
+
}
|
|
1228
|
+
/**
|
|
1229
|
+
* Check if authenticated
|
|
1230
|
+
*/
|
|
1231
|
+
isAuthenticated() {
|
|
1232
|
+
return this.tokens !== null && !!this.tokens.accessToken;
|
|
1233
|
+
}
|
|
1234
|
+
/**
|
|
1235
|
+
* Get current tokens
|
|
1236
|
+
*/
|
|
1237
|
+
getTokens() {
|
|
1238
|
+
return this.tokens;
|
|
1239
|
+
}
|
|
1240
|
+
/**
|
|
1241
|
+
* Refresh the access token
|
|
1242
|
+
*/
|
|
1243
|
+
async refreshAccessToken() {
|
|
1244
|
+
if (!this.tokens?.refreshToken) {
|
|
1245
|
+
throw new Error("No refresh token available");
|
|
1246
|
+
}
|
|
1247
|
+
const { credentials } = await this.oauth2Client.refreshAccessToken();
|
|
1248
|
+
this.tokens = {
|
|
1249
|
+
...this.tokens,
|
|
1250
|
+
accessToken: credentials.access_token,
|
|
1251
|
+
expiryDate: credentials.expiry_date || void 0
|
|
1252
|
+
};
|
|
1253
|
+
if (this.callbacks.onTokensUpdated) {
|
|
1254
|
+
await this.callbacks.onTokensUpdated(this.tokens);
|
|
1255
|
+
}
|
|
1256
|
+
return this.tokens;
|
|
1257
|
+
}
|
|
1258
|
+
/**
|
|
1259
|
+
* Revoke access (disconnect)
|
|
1260
|
+
*/
|
|
1261
|
+
async revokeAccess() {
|
|
1262
|
+
if (this.tokens?.accessToken) {
|
|
1263
|
+
await this.oauth2Client.revokeToken(this.tokens.accessToken);
|
|
1264
|
+
}
|
|
1265
|
+
this.tokens = null;
|
|
1266
|
+
this.oauth2Client.setCredentials({});
|
|
1267
|
+
}
|
|
1268
|
+
/**
|
|
1269
|
+
* Check if token is expired or will expire soon
|
|
1270
|
+
*/
|
|
1271
|
+
isTokenExpired(bufferSeconds = 300) {
|
|
1272
|
+
if (!this.tokens?.expiryDate) {
|
|
1273
|
+
return false;
|
|
1274
|
+
}
|
|
1275
|
+
const now = Date.now();
|
|
1276
|
+
const expiry = this.tokens.expiryDate;
|
|
1277
|
+
return now >= expiry - bufferSeconds * 1e3;
|
|
1278
|
+
}
|
|
1279
|
+
/**
|
|
1280
|
+
* Ensure valid access token (refresh if needed)
|
|
1281
|
+
*/
|
|
1282
|
+
async ensureValidToken() {
|
|
1283
|
+
if (this.isTokenExpired()) {
|
|
1284
|
+
await this.refreshAccessToken();
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
};
|
|
1288
|
+
function createGoogleDriveAuth(config, callbacks) {
|
|
1289
|
+
return new GoogleDriveAuth(config, callbacks);
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
// src/modules/google-drive/index.ts
|
|
1293
|
+
var FOLDER_MIME_TYPE = "application/vnd.google-apps.folder";
|
|
1294
|
+
var GoogleDriveModule = class extends BaseStorageModule {
|
|
1295
|
+
constructor() {
|
|
1296
|
+
super(...arguments);
|
|
1297
|
+
this.provider = "google_drive";
|
|
1298
|
+
this.auth = null;
|
|
1299
|
+
this.drive = null;
|
|
1300
|
+
this.rootFolderId = "root";
|
|
1301
|
+
this.authCallbacks = {};
|
|
1302
|
+
}
|
|
1303
|
+
/**
|
|
1304
|
+
* Set authentication callbacks for token persistence
|
|
1305
|
+
*/
|
|
1306
|
+
setAuthCallbacks(callbacks) {
|
|
1307
|
+
this.authCallbacks = callbacks;
|
|
1308
|
+
}
|
|
1309
|
+
async initialize(config) {
|
|
1310
|
+
await super.initialize(config);
|
|
1311
|
+
const driveConfig = this.getProviderConfig();
|
|
1312
|
+
if (!driveConfig.clientId || !driveConfig.clientSecret) {
|
|
1313
|
+
throw new AuthenticationError("google_drive", "Missing client ID or client secret");
|
|
1314
|
+
}
|
|
1315
|
+
this.auth = createGoogleDriveAuth(
|
|
1316
|
+
{
|
|
1317
|
+
clientId: driveConfig.clientId,
|
|
1318
|
+
clientSecret: driveConfig.clientSecret,
|
|
1319
|
+
redirectUri: driveConfig.redirectUri
|
|
1320
|
+
},
|
|
1321
|
+
this.authCallbacks
|
|
1322
|
+
);
|
|
1323
|
+
if (driveConfig.refreshToken) {
|
|
1324
|
+
await this.auth.setTokens({
|
|
1325
|
+
accessToken: driveConfig.accessToken || "",
|
|
1326
|
+
refreshToken: driveConfig.refreshToken
|
|
1327
|
+
});
|
|
1328
|
+
}
|
|
1329
|
+
this.drive = import_googleapis2.google.drive({ version: "v3", auth: this.auth.getClient() });
|
|
1330
|
+
this.rootFolderId = driveConfig.rootFolderId || "root";
|
|
1331
|
+
}
|
|
1332
|
+
/**
|
|
1333
|
+
* Get the auth instance for OAuth flow
|
|
1334
|
+
*/
|
|
1335
|
+
getAuth() {
|
|
1336
|
+
if (!this.auth) {
|
|
1337
|
+
throw new AuthenticationError("google_drive", "Module not initialized");
|
|
1338
|
+
}
|
|
1339
|
+
return this.auth;
|
|
1340
|
+
}
|
|
1341
|
+
/**
|
|
1342
|
+
* Check if user is authenticated
|
|
1343
|
+
*/
|
|
1344
|
+
isAuthenticated() {
|
|
1345
|
+
return this.auth?.isAuthenticated() ?? false;
|
|
1346
|
+
}
|
|
1347
|
+
/**
|
|
1348
|
+
* Authenticate with provided tokens
|
|
1349
|
+
*/
|
|
1350
|
+
async authenticate(tokens) {
|
|
1351
|
+
if (!this.auth) {
|
|
1352
|
+
throw new AuthenticationError("google_drive", "Module not initialized");
|
|
1353
|
+
}
|
|
1354
|
+
await this.auth.setTokens(tokens);
|
|
1355
|
+
}
|
|
1356
|
+
/**
|
|
1357
|
+
* Ensure authenticated before operations
|
|
1358
|
+
*/
|
|
1359
|
+
async ensureAuthenticated() {
|
|
1360
|
+
this.ensureInitialized();
|
|
1361
|
+
if (!this.isAuthenticated()) {
|
|
1362
|
+
throw new AuthenticationError("google_drive", "Not authenticated. Please connect your Google Drive.");
|
|
1363
|
+
}
|
|
1364
|
+
await this.auth.ensureValidToken();
|
|
1365
|
+
}
|
|
1366
|
+
/**
|
|
1367
|
+
* Get folder ID from path (creates folders if needed for certain operations)
|
|
1368
|
+
*/
|
|
1369
|
+
async getIdFromPath(virtualPath, createIfMissing = false) {
|
|
1370
|
+
const normalized = this.normalizePath(virtualPath);
|
|
1371
|
+
if (normalized === "/") {
|
|
1372
|
+
return this.rootFolderId;
|
|
1373
|
+
}
|
|
1374
|
+
const segments = normalized.split("/").filter(Boolean);
|
|
1375
|
+
let currentParentId = this.rootFolderId;
|
|
1376
|
+
for (const segment of segments) {
|
|
1377
|
+
const query = `name='${segment}' and '${currentParentId}' in parents and trashed=false`;
|
|
1378
|
+
const response = await this.drive.files.list({
|
|
1379
|
+
q: query,
|
|
1380
|
+
fields: "files(id, name, mimeType)",
|
|
1381
|
+
pageSize: 1
|
|
1382
|
+
});
|
|
1383
|
+
if (response.data.files && response.data.files.length > 0) {
|
|
1384
|
+
currentParentId = response.data.files[0].id;
|
|
1385
|
+
} else if (createIfMissing) {
|
|
1386
|
+
const createResponse = await this.drive.files.create({
|
|
1387
|
+
requestBody: {
|
|
1388
|
+
name: segment,
|
|
1389
|
+
mimeType: FOLDER_MIME_TYPE,
|
|
1390
|
+
parents: [currentParentId]
|
|
1391
|
+
},
|
|
1392
|
+
fields: "id"
|
|
1393
|
+
});
|
|
1394
|
+
currentParentId = createResponse.data.id;
|
|
1395
|
+
} else {
|
|
1396
|
+
return null;
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
return currentParentId;
|
|
1400
|
+
}
|
|
1401
|
+
/**
|
|
1402
|
+
* Convert Drive file to FileSystemItem
|
|
1403
|
+
*/
|
|
1404
|
+
driveFileToItem(file, virtualPath) {
|
|
1405
|
+
const isFolder2 = file.mimeType === FOLDER_MIME_TYPE;
|
|
1406
|
+
const path3 = virtualPath || "";
|
|
1407
|
+
if (isFolder2) {
|
|
1408
|
+
return createFolderItem({
|
|
1409
|
+
id: file.id,
|
|
1410
|
+
name: file.name,
|
|
1411
|
+
path: path3,
|
|
1412
|
+
createdAt: file.createdTime ? new Date(file.createdTime) : /* @__PURE__ */ new Date(),
|
|
1413
|
+
modifiedAt: file.modifiedTime ? new Date(file.modifiedTime) : /* @__PURE__ */ new Date(),
|
|
1414
|
+
metadata: {
|
|
1415
|
+
driveId: file.id,
|
|
1416
|
+
webViewLink: file.webViewLink
|
|
1417
|
+
}
|
|
1418
|
+
});
|
|
1419
|
+
}
|
|
1420
|
+
return createFileItem({
|
|
1421
|
+
id: file.id,
|
|
1422
|
+
name: file.name,
|
|
1423
|
+
path: path3,
|
|
1424
|
+
size: parseInt(file.size || "0", 10),
|
|
1425
|
+
mimeType: file.mimeType || "application/octet-stream",
|
|
1426
|
+
createdAt: file.createdTime ? new Date(file.createdTime) : /* @__PURE__ */ new Date(),
|
|
1427
|
+
modifiedAt: file.modifiedTime ? new Date(file.modifiedTime) : /* @__PURE__ */ new Date(),
|
|
1428
|
+
metadata: {
|
|
1429
|
+
driveId: file.id,
|
|
1430
|
+
webViewLink: file.webViewLink,
|
|
1431
|
+
thumbnailLink: file.thumbnailLink
|
|
1432
|
+
}
|
|
1433
|
+
});
|
|
1434
|
+
}
|
|
1435
|
+
async createDirectory(virtualPath) {
|
|
1436
|
+
try {
|
|
1437
|
+
await this.ensureAuthenticated();
|
|
1438
|
+
const normalized = this.normalizePath(virtualPath);
|
|
1439
|
+
const parentPath = this.getParentPath(normalized);
|
|
1440
|
+
const folderName = this.getBaseName(normalized);
|
|
1441
|
+
const parentId = await this.getIdFromPath(parentPath, true);
|
|
1442
|
+
if (!parentId) {
|
|
1443
|
+
throw new DirectoryNotFoundError(parentPath);
|
|
1444
|
+
}
|
|
1445
|
+
const existingQuery = `name='${folderName}' and '${parentId}' in parents and mimeType='${FOLDER_MIME_TYPE}' and trashed=false`;
|
|
1446
|
+
const existingResponse = await this.drive.files.list({
|
|
1447
|
+
q: existingQuery,
|
|
1448
|
+
fields: "files(id)",
|
|
1449
|
+
pageSize: 1
|
|
1450
|
+
});
|
|
1451
|
+
if (existingResponse.data.files && existingResponse.data.files.length > 0) {
|
|
1452
|
+
return this.errorResult(`Directory already exists: ${virtualPath}`);
|
|
1453
|
+
}
|
|
1454
|
+
const response = await this.drive.files.create({
|
|
1455
|
+
requestBody: {
|
|
1456
|
+
name: folderName,
|
|
1457
|
+
mimeType: FOLDER_MIME_TYPE,
|
|
1458
|
+
parents: [parentId]
|
|
1459
|
+
},
|
|
1460
|
+
fields: "id, name, mimeType, createdTime, modifiedTime, webViewLink"
|
|
1461
|
+
});
|
|
1462
|
+
const item = this.driveFileToItem(response.data, normalized);
|
|
1463
|
+
return this.successResult(item);
|
|
1464
|
+
} catch (error) {
|
|
1465
|
+
if (error instanceof AuthenticationError || error instanceof DirectoryNotFoundError) {
|
|
1466
|
+
return this.errorResult(error.message);
|
|
1467
|
+
}
|
|
1468
|
+
return this.errorResult(`Failed to create directory: ${error.message}`);
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
async removeDirectory(virtualPath, recursive = false) {
|
|
1472
|
+
try {
|
|
1473
|
+
await this.ensureAuthenticated();
|
|
1474
|
+
const folderId = await this.getIdFromPath(virtualPath);
|
|
1475
|
+
if (!folderId) {
|
|
1476
|
+
throw new DirectoryNotFoundError(virtualPath);
|
|
1477
|
+
}
|
|
1478
|
+
if (!recursive) {
|
|
1479
|
+
const childrenResponse = await this.drive.files.list({
|
|
1480
|
+
q: `'${folderId}' in parents and trashed=false`,
|
|
1481
|
+
fields: "files(id)",
|
|
1482
|
+
pageSize: 1
|
|
1483
|
+
});
|
|
1484
|
+
if (childrenResponse.data.files && childrenResponse.data.files.length > 0) {
|
|
1485
|
+
return this.errorResult(`Directory is not empty: ${virtualPath}`);
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
await this.drive.files.delete({ fileId: folderId });
|
|
1489
|
+
return this.successResult();
|
|
1490
|
+
} catch (error) {
|
|
1491
|
+
if (error instanceof DirectoryNotFoundError) {
|
|
1492
|
+
return this.errorResult(error.message);
|
|
1493
|
+
}
|
|
1494
|
+
return this.errorResult(`Failed to remove directory: ${error.message}`);
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
async uploadFile(source, remotePath, options = {}) {
|
|
1498
|
+
try {
|
|
1499
|
+
await this.ensureAuthenticated();
|
|
1500
|
+
const normalized = this.normalizePath(remotePath);
|
|
1501
|
+
const parentPath = this.getParentPath(normalized);
|
|
1502
|
+
const fileName = this.getBaseName(normalized);
|
|
1503
|
+
const parentId = await this.getIdFromPath(parentPath, true);
|
|
1504
|
+
if (!parentId) {
|
|
1505
|
+
throw new DirectoryNotFoundError(parentPath);
|
|
1506
|
+
}
|
|
1507
|
+
if (!options.overwrite) {
|
|
1508
|
+
const existingQuery = `name='${fileName}' and '${parentId}' in parents and trashed=false`;
|
|
1509
|
+
const existingResponse = await this.drive.files.list({
|
|
1510
|
+
q: existingQuery,
|
|
1511
|
+
fields: "files(id)",
|
|
1512
|
+
pageSize: 1
|
|
1513
|
+
});
|
|
1514
|
+
if (existingResponse.data.files && existingResponse.data.files.length > 0) {
|
|
1515
|
+
throw new FileExistsError(remotePath);
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
let media;
|
|
1519
|
+
if (typeof source === "string") {
|
|
1520
|
+
const fs3 = await import("fs");
|
|
1521
|
+
media = {
|
|
1522
|
+
mimeType: "application/octet-stream",
|
|
1523
|
+
body: fs3.createReadStream(source)
|
|
1524
|
+
};
|
|
1525
|
+
} else if (Buffer.isBuffer(source)) {
|
|
1526
|
+
media = {
|
|
1527
|
+
mimeType: "application/octet-stream",
|
|
1528
|
+
body: source
|
|
1529
|
+
};
|
|
1530
|
+
} else {
|
|
1531
|
+
media = {
|
|
1532
|
+
mimeType: "application/octet-stream",
|
|
1533
|
+
body: import_stream2.Readable.fromWeb(source)
|
|
1534
|
+
};
|
|
1535
|
+
}
|
|
1536
|
+
const response = await this.drive.files.create({
|
|
1537
|
+
requestBody: {
|
|
1538
|
+
name: fileName,
|
|
1539
|
+
parents: [parentId]
|
|
1540
|
+
},
|
|
1541
|
+
media,
|
|
1542
|
+
fields: "id, name, mimeType, size, createdTime, modifiedTime, webViewLink, thumbnailLink"
|
|
1543
|
+
});
|
|
1544
|
+
if (options.onProgress) {
|
|
1545
|
+
options.onProgress(100, parseInt(response.data.size || "0", 10), parseInt(response.data.size || "0", 10));
|
|
1546
|
+
}
|
|
1547
|
+
const item = this.driveFileToItem(response.data, normalized);
|
|
1548
|
+
return this.successResult(item);
|
|
1549
|
+
} catch (error) {
|
|
1550
|
+
if (error instanceof AuthenticationError || error instanceof DirectoryNotFoundError || error instanceof FileExistsError) {
|
|
1551
|
+
return this.errorResult(error.message);
|
|
1552
|
+
}
|
|
1553
|
+
return this.errorResult(`Failed to upload file: ${error.message}`);
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
async downloadFile(remotePath, localPath, options = {}) {
|
|
1557
|
+
try {
|
|
1558
|
+
await this.ensureAuthenticated();
|
|
1559
|
+
const fileId = await this.getIdFromPath(remotePath);
|
|
1560
|
+
if (!fileId) {
|
|
1561
|
+
throw new FileNotFoundError(remotePath);
|
|
1562
|
+
}
|
|
1563
|
+
const response = await this.drive.files.get(
|
|
1564
|
+
{ fileId, alt: "media" },
|
|
1565
|
+
{ responseType: "arraybuffer" }
|
|
1566
|
+
);
|
|
1567
|
+
const buffer = Buffer.from(response.data);
|
|
1568
|
+
if (options.onProgress) {
|
|
1569
|
+
options.onProgress(100, buffer.length, buffer.length);
|
|
1570
|
+
}
|
|
1571
|
+
if (localPath) {
|
|
1572
|
+
const fs3 = await import("fs");
|
|
1573
|
+
const path3 = await import("path");
|
|
1574
|
+
await fs3.promises.mkdir(path3.dirname(localPath), { recursive: true });
|
|
1575
|
+
await fs3.promises.writeFile(localPath, buffer);
|
|
1576
|
+
return this.successResult(localPath);
|
|
1577
|
+
}
|
|
1578
|
+
return this.successResult(buffer);
|
|
1579
|
+
} catch (error) {
|
|
1580
|
+
if (error instanceof FileNotFoundError) {
|
|
1581
|
+
return this.errorResult(error.message);
|
|
1582
|
+
}
|
|
1583
|
+
return this.errorResult(`Failed to download file: ${error.message}`);
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
async moveItem(sourcePath, destinationPath, _options = {}) {
|
|
1587
|
+
try {
|
|
1588
|
+
await this.ensureAuthenticated();
|
|
1589
|
+
const fileId = await this.getIdFromPath(sourcePath);
|
|
1590
|
+
if (!fileId) {
|
|
1591
|
+
throw new FileNotFoundError(sourcePath);
|
|
1592
|
+
}
|
|
1593
|
+
const file = await this.drive.files.get({
|
|
1594
|
+
fileId,
|
|
1595
|
+
fields: "parents"
|
|
1596
|
+
});
|
|
1597
|
+
const previousParents = file.data.parents?.join(",") || "";
|
|
1598
|
+
const destParentPath = this.getParentPath(destinationPath);
|
|
1599
|
+
const newName = this.getBaseName(destinationPath);
|
|
1600
|
+
const newParentId = await this.getIdFromPath(destParentPath, true);
|
|
1601
|
+
if (!newParentId) {
|
|
1602
|
+
throw new DirectoryNotFoundError(destParentPath);
|
|
1603
|
+
}
|
|
1604
|
+
const response = await this.drive.files.update({
|
|
1605
|
+
fileId,
|
|
1606
|
+
addParents: newParentId,
|
|
1607
|
+
removeParents: previousParents,
|
|
1608
|
+
requestBody: {
|
|
1609
|
+
name: newName
|
|
1610
|
+
},
|
|
1611
|
+
fields: "id, name, mimeType, size, createdTime, modifiedTime, webViewLink, thumbnailLink"
|
|
1612
|
+
});
|
|
1613
|
+
const item = this.driveFileToItem(response.data, destinationPath);
|
|
1614
|
+
return this.successResult(item);
|
|
1615
|
+
} catch (error) {
|
|
1616
|
+
if (error instanceof FileNotFoundError || error instanceof DirectoryNotFoundError) {
|
|
1617
|
+
return this.errorResult(error.message);
|
|
1618
|
+
}
|
|
1619
|
+
return this.errorResult(`Failed to move item: ${error.message}`);
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
async deleteFile(virtualPath) {
|
|
1623
|
+
try {
|
|
1624
|
+
await this.ensureAuthenticated();
|
|
1625
|
+
const fileId = await this.getIdFromPath(virtualPath);
|
|
1626
|
+
if (!fileId) {
|
|
1627
|
+
throw new FileNotFoundError(virtualPath);
|
|
1628
|
+
}
|
|
1629
|
+
await this.drive.files.delete({ fileId });
|
|
1630
|
+
return this.successResult();
|
|
1631
|
+
} catch (error) {
|
|
1632
|
+
if (error instanceof FileNotFoundError) {
|
|
1633
|
+
return this.errorResult(error.message);
|
|
1634
|
+
}
|
|
1635
|
+
return this.errorResult(`Failed to delete file: ${error.message}`);
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
async renameFile(virtualPath, newName, _options = {}) {
|
|
1639
|
+
try {
|
|
1640
|
+
await this.ensureAuthenticated();
|
|
1641
|
+
const fileId = await this.getIdFromPath(virtualPath);
|
|
1642
|
+
if (!fileId) {
|
|
1643
|
+
throw new FileNotFoundError(virtualPath);
|
|
1644
|
+
}
|
|
1645
|
+
const response = await this.drive.files.update({
|
|
1646
|
+
fileId,
|
|
1647
|
+
requestBody: {
|
|
1648
|
+
name: newName
|
|
1649
|
+
},
|
|
1650
|
+
fields: "id, name, mimeType, size, createdTime, modifiedTime, webViewLink, thumbnailLink"
|
|
1651
|
+
});
|
|
1652
|
+
const parentPath = this.getParentPath(virtualPath);
|
|
1653
|
+
const newPath = this.joinPath(parentPath, newName);
|
|
1654
|
+
const item = this.driveFileToItem(response.data, newPath);
|
|
1655
|
+
return this.successResult(item);
|
|
1656
|
+
} catch (error) {
|
|
1657
|
+
if (error instanceof FileNotFoundError) {
|
|
1658
|
+
return this.errorResult(error.message);
|
|
1659
|
+
}
|
|
1660
|
+
return this.errorResult(`Failed to rename file: ${error.message}`);
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
async renameFolder(virtualPath, newName, _options = {}) {
|
|
1664
|
+
try {
|
|
1665
|
+
await this.ensureAuthenticated();
|
|
1666
|
+
const folderId = await this.getIdFromPath(virtualPath);
|
|
1667
|
+
if (!folderId) {
|
|
1668
|
+
throw new DirectoryNotFoundError(virtualPath);
|
|
1669
|
+
}
|
|
1670
|
+
const response = await this.drive.files.update({
|
|
1671
|
+
fileId: folderId,
|
|
1672
|
+
requestBody: {
|
|
1673
|
+
name: newName
|
|
1674
|
+
},
|
|
1675
|
+
fields: "id, name, mimeType, createdTime, modifiedTime, webViewLink"
|
|
1676
|
+
});
|
|
1677
|
+
const parentPath = this.getParentPath(virtualPath);
|
|
1678
|
+
const newPath = this.joinPath(parentPath, newName);
|
|
1679
|
+
const item = this.driveFileToItem(response.data, newPath);
|
|
1680
|
+
return this.successResult(item);
|
|
1681
|
+
} catch (error) {
|
|
1682
|
+
if (error instanceof DirectoryNotFoundError) {
|
|
1683
|
+
return this.errorResult(error.message);
|
|
1684
|
+
}
|
|
1685
|
+
return this.errorResult(`Failed to rename folder: ${error.message}`);
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
async listDirectory(virtualPath, options = {}) {
|
|
1689
|
+
try {
|
|
1690
|
+
await this.ensureAuthenticated();
|
|
1691
|
+
const folderId = await this.getIdFromPath(virtualPath);
|
|
1692
|
+
if (!folderId) {
|
|
1693
|
+
throw new DirectoryNotFoundError(virtualPath);
|
|
1694
|
+
}
|
|
1695
|
+
const items = [];
|
|
1696
|
+
let pageToken;
|
|
1697
|
+
do {
|
|
1698
|
+
const response = await this.drive.files.list({
|
|
1699
|
+
q: `'${folderId}' in parents and trashed=false`,
|
|
1700
|
+
fields: "nextPageToken, files(id, name, mimeType, size, createdTime, modifiedTime, webViewLink, thumbnailLink)",
|
|
1701
|
+
pageSize: 100,
|
|
1702
|
+
pageToken,
|
|
1703
|
+
orderBy: "folder,name"
|
|
1704
|
+
});
|
|
1705
|
+
if (response.data.files) {
|
|
1706
|
+
for (const file of response.data.files) {
|
|
1707
|
+
if (!options.includeHidden && file.name?.startsWith(".")) {
|
|
1708
|
+
continue;
|
|
1709
|
+
}
|
|
1710
|
+
const itemPath = this.joinPath(virtualPath, file.name);
|
|
1711
|
+
const item = this.driveFileToItem(file, itemPath);
|
|
1712
|
+
if (options.filter && !options.filter(item)) {
|
|
1713
|
+
continue;
|
|
1714
|
+
}
|
|
1715
|
+
items.push(item);
|
|
1716
|
+
if (options.recursive && file.mimeType === FOLDER_MIME_TYPE) {
|
|
1717
|
+
const subResult = await this.listDirectory(itemPath, options);
|
|
1718
|
+
if (subResult.success && subResult.data) {
|
|
1719
|
+
items.push(...subResult.data);
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
pageToken = response.data.nextPageToken || void 0;
|
|
1725
|
+
} while (pageToken);
|
|
1726
|
+
return this.successResult(items);
|
|
1727
|
+
} catch (error) {
|
|
1728
|
+
if (error instanceof DirectoryNotFoundError) {
|
|
1729
|
+
return this.errorResult(error.message);
|
|
1730
|
+
}
|
|
1731
|
+
return this.errorResult(`Failed to list directory: ${error.message}`);
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
async getItem(virtualPath) {
|
|
1735
|
+
try {
|
|
1736
|
+
await this.ensureAuthenticated();
|
|
1737
|
+
const fileId = await this.getIdFromPath(virtualPath);
|
|
1738
|
+
if (!fileId) {
|
|
1739
|
+
throw new FileNotFoundError(virtualPath);
|
|
1740
|
+
}
|
|
1741
|
+
const response = await this.drive.files.get({
|
|
1742
|
+
fileId,
|
|
1743
|
+
fields: "id, name, mimeType, size, createdTime, modifiedTime, webViewLink, thumbnailLink"
|
|
1744
|
+
});
|
|
1745
|
+
const item = this.driveFileToItem(response.data, virtualPath);
|
|
1746
|
+
return this.successResult(item);
|
|
1747
|
+
} catch (error) {
|
|
1748
|
+
if (error instanceof FileNotFoundError) {
|
|
1749
|
+
return this.errorResult(error.message);
|
|
1750
|
+
}
|
|
1751
|
+
return this.errorResult(`Failed to get item: ${error.message}`);
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
async exists(virtualPath) {
|
|
1755
|
+
try {
|
|
1756
|
+
await this.ensureAuthenticated();
|
|
1757
|
+
const fileId = await this.getIdFromPath(virtualPath);
|
|
1758
|
+
return fileId !== null;
|
|
1759
|
+
} catch {
|
|
1760
|
+
return false;
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
async getFolderTree(path3 = "/", depth = 3) {
|
|
1764
|
+
try {
|
|
1765
|
+
await this.ensureAuthenticated();
|
|
1766
|
+
return super.getFolderTree(path3, depth);
|
|
1767
|
+
} catch (error) {
|
|
1768
|
+
return this.errorResult(`Failed to get folder tree: ${error.message}`);
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
};
|
|
1772
|
+
function createGoogleDriveModule() {
|
|
1773
|
+
return new GoogleDriveModule();
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
// src/modules/index.ts
|
|
1777
|
+
var moduleRegistry = {
|
|
1778
|
+
local: createLocalModule,
|
|
1779
|
+
google_drive: createGoogleDriveModule
|
|
1780
|
+
};
|
|
1781
|
+
function getRegisteredProviders() {
|
|
1782
|
+
return Object.keys(moduleRegistry);
|
|
1783
|
+
}
|
|
1784
|
+
function isProviderRegistered(provider) {
|
|
1785
|
+
return provider in moduleRegistry;
|
|
1786
|
+
}
|
|
1787
|
+
function createModule(provider) {
|
|
1788
|
+
const factory = moduleRegistry[provider];
|
|
1789
|
+
if (!factory) {
|
|
1790
|
+
throw new ConfigurationError(`Unknown storage provider: ${provider}`);
|
|
1791
|
+
}
|
|
1792
|
+
return factory();
|
|
1793
|
+
}
|
|
1794
|
+
async function createAndInitializeModule(config) {
|
|
1795
|
+
const module2 = createModule(config.provider);
|
|
1796
|
+
await module2.initialize(config);
|
|
1797
|
+
return module2;
|
|
1798
|
+
}
|
|
1799
|
+
function registerModule(provider, factory) {
|
|
1800
|
+
moduleRegistry[provider] = factory;
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
// src/services/file-manager.ts
|
|
1804
|
+
var FileManager = class {
|
|
1805
|
+
constructor(options = {}) {
|
|
1806
|
+
this.module = null;
|
|
1807
|
+
this.config = null;
|
|
1808
|
+
this.initialized = false;
|
|
1809
|
+
this.options = {
|
|
1810
|
+
autoInit: true,
|
|
1811
|
+
...options
|
|
1812
|
+
};
|
|
1813
|
+
}
|
|
1814
|
+
/**
|
|
1815
|
+
* Initialize the file manager with configuration
|
|
1816
|
+
*/
|
|
1817
|
+
async initialize(config) {
|
|
1818
|
+
if (this.initialized) {
|
|
1819
|
+
return;
|
|
1820
|
+
}
|
|
1821
|
+
if (config) {
|
|
1822
|
+
this.config = config;
|
|
1823
|
+
} else if (this.options.config) {
|
|
1824
|
+
this.config = this.options.config;
|
|
1825
|
+
} else {
|
|
1826
|
+
this.config = await loadConfigAsync(this.options.configPath);
|
|
1827
|
+
}
|
|
1828
|
+
this.module = await createAndInitializeModule(this.config);
|
|
1829
|
+
this.initialized = true;
|
|
1830
|
+
}
|
|
1831
|
+
/**
|
|
1832
|
+
* Initialize synchronously (uses sync config loading)
|
|
1833
|
+
*/
|
|
1834
|
+
initializeSync(config) {
|
|
1835
|
+
if (this.initialized) {
|
|
1836
|
+
return;
|
|
1837
|
+
}
|
|
1838
|
+
if (config) {
|
|
1839
|
+
this.config = config;
|
|
1840
|
+
} else if (this.options.config) {
|
|
1841
|
+
this.config = this.options.config;
|
|
1842
|
+
} else {
|
|
1843
|
+
this.config = loadConfig(this.options.configPath);
|
|
1844
|
+
}
|
|
1845
|
+
this.module = createModule(this.config.provider);
|
|
1846
|
+
}
|
|
1847
|
+
/**
|
|
1848
|
+
* Check if manager is initialized
|
|
1849
|
+
*/
|
|
1850
|
+
isInitialized() {
|
|
1851
|
+
return this.initialized;
|
|
1852
|
+
}
|
|
1853
|
+
/**
|
|
1854
|
+
* Get the current configuration
|
|
1855
|
+
*/
|
|
1856
|
+
getConfig() {
|
|
1857
|
+
return this.config;
|
|
1858
|
+
}
|
|
1859
|
+
/**
|
|
1860
|
+
* Get the current provider
|
|
1861
|
+
*/
|
|
1862
|
+
getProvider() {
|
|
1863
|
+
return this.config?.provider ?? null;
|
|
1864
|
+
}
|
|
1865
|
+
/**
|
|
1866
|
+
* Get the underlying storage module
|
|
1867
|
+
*/
|
|
1868
|
+
getModule() {
|
|
1869
|
+
this.ensureInitialized();
|
|
1870
|
+
return this.module;
|
|
1871
|
+
}
|
|
1872
|
+
/**
|
|
1873
|
+
* Ensure manager is initialized
|
|
1874
|
+
*/
|
|
1875
|
+
ensureInitialized() {
|
|
1876
|
+
if (!this.initialized || !this.module) {
|
|
1877
|
+
throw new Error("FileManager not initialized. Call initialize() first.");
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
// ============ Directory Operations ============
|
|
1881
|
+
/**
|
|
1882
|
+
* Create a directory at the specified path
|
|
1883
|
+
*/
|
|
1884
|
+
async createDirectory(path3) {
|
|
1885
|
+
this.ensureInitialized();
|
|
1886
|
+
return this.module.createDirectory(path3);
|
|
1887
|
+
}
|
|
1888
|
+
/**
|
|
1889
|
+
* Remove a directory
|
|
1890
|
+
* @param path - Directory path
|
|
1891
|
+
* @param recursive - If true, remove directory and all contents
|
|
1892
|
+
*/
|
|
1893
|
+
async removeDirectory(path3, recursive = false) {
|
|
1894
|
+
this.ensureInitialized();
|
|
1895
|
+
return this.module.removeDirectory(path3, recursive);
|
|
1896
|
+
}
|
|
1897
|
+
// ============ File Operations ============
|
|
1898
|
+
/**
|
|
1899
|
+
* Upload/save a file
|
|
1900
|
+
* @param source - File path, Buffer, or ReadableStream
|
|
1901
|
+
* @param remotePath - Destination path in storage
|
|
1902
|
+
* @param options - Upload options
|
|
1903
|
+
*/
|
|
1904
|
+
async uploadFile(source, remotePath, options) {
|
|
1905
|
+
this.ensureInitialized();
|
|
1906
|
+
return this.module.uploadFile(source, remotePath, options);
|
|
1907
|
+
}
|
|
1908
|
+
/**
|
|
1909
|
+
* Download a file
|
|
1910
|
+
* @param remotePath - Path in storage
|
|
1911
|
+
* @param localPath - Optional local destination path
|
|
1912
|
+
* @param options - Download options
|
|
1913
|
+
*/
|
|
1914
|
+
async downloadFile(remotePath, localPath, options) {
|
|
1915
|
+
this.ensureInitialized();
|
|
1916
|
+
return this.module.downloadFile(remotePath, localPath, options);
|
|
1917
|
+
}
|
|
1918
|
+
/**
|
|
1919
|
+
* Move a file or folder
|
|
1920
|
+
* @param sourcePath - Current path
|
|
1921
|
+
* @param destinationPath - New path
|
|
1922
|
+
* @param options - Move options
|
|
1923
|
+
*/
|
|
1924
|
+
async moveItem(sourcePath, destinationPath, options) {
|
|
1925
|
+
this.ensureInitialized();
|
|
1926
|
+
return this.module.moveItem(sourcePath, destinationPath, options);
|
|
1927
|
+
}
|
|
1928
|
+
/**
|
|
1929
|
+
* Delete a file
|
|
1930
|
+
*/
|
|
1931
|
+
async deleteFile(path3) {
|
|
1932
|
+
this.ensureInitialized();
|
|
1933
|
+
return this.module.deleteFile(path3);
|
|
1934
|
+
}
|
|
1935
|
+
/**
|
|
1936
|
+
* Rename a file
|
|
1937
|
+
* @param path - Current file path
|
|
1938
|
+
* @param newName - New filename (not full path)
|
|
1939
|
+
* @param options - Rename options
|
|
1940
|
+
*/
|
|
1941
|
+
async renameFile(path3, newName, options) {
|
|
1942
|
+
this.ensureInitialized();
|
|
1943
|
+
return this.module.renameFile(path3, newName, options);
|
|
1944
|
+
}
|
|
1945
|
+
/**
|
|
1946
|
+
* Rename a folder
|
|
1947
|
+
* @param path - Current folder path
|
|
1948
|
+
* @param newName - New folder name (not full path)
|
|
1949
|
+
* @param options - Rename options
|
|
1950
|
+
*/
|
|
1951
|
+
async renameFolder(path3, newName, options) {
|
|
1952
|
+
this.ensureInitialized();
|
|
1953
|
+
return this.module.renameFolder(path3, newName, options);
|
|
1954
|
+
}
|
|
1955
|
+
// ============ Query Operations ============
|
|
1956
|
+
/**
|
|
1957
|
+
* List contents of a directory
|
|
1958
|
+
* @param path - Directory path
|
|
1959
|
+
* @param options - List options
|
|
1960
|
+
*/
|
|
1961
|
+
async listDirectory(path3, options) {
|
|
1962
|
+
this.ensureInitialized();
|
|
1963
|
+
return this.module.listDirectory(path3, options);
|
|
1964
|
+
}
|
|
1965
|
+
/**
|
|
1966
|
+
* Get information about a file or folder
|
|
1967
|
+
*/
|
|
1968
|
+
async getItem(path3) {
|
|
1969
|
+
this.ensureInitialized();
|
|
1970
|
+
return this.module.getItem(path3);
|
|
1971
|
+
}
|
|
1972
|
+
/**
|
|
1973
|
+
* Check if a file or folder exists
|
|
1974
|
+
*/
|
|
1975
|
+
async exists(path3) {
|
|
1976
|
+
this.ensureInitialized();
|
|
1977
|
+
return this.module.exists(path3);
|
|
1978
|
+
}
|
|
1979
|
+
/**
|
|
1980
|
+
* Get folder tree structure
|
|
1981
|
+
* @param path - Starting path (default: root)
|
|
1982
|
+
* @param depth - Maximum depth to traverse
|
|
1983
|
+
*/
|
|
1984
|
+
async getFolderTree(path3 = "/", depth = 3) {
|
|
1985
|
+
this.ensureInitialized();
|
|
1986
|
+
return this.module.getFolderTree(path3, depth);
|
|
1987
|
+
}
|
|
1988
|
+
// ============ Convenience Methods ============
|
|
1989
|
+
/**
|
|
1990
|
+
* Create a file with string content
|
|
1991
|
+
*/
|
|
1992
|
+
async writeFile(path3, content, options) {
|
|
1993
|
+
const buffer = Buffer.from(content, "utf-8");
|
|
1994
|
+
return this.uploadFile(buffer, path3, options);
|
|
1995
|
+
}
|
|
1996
|
+
/**
|
|
1997
|
+
* Read a file as string
|
|
1998
|
+
*/
|
|
1999
|
+
async readFile(path3) {
|
|
2000
|
+
const result = await this.downloadFile(path3);
|
|
2001
|
+
if (!result.success) {
|
|
2002
|
+
return { success: false, error: result.error };
|
|
2003
|
+
}
|
|
2004
|
+
if (Buffer.isBuffer(result.data)) {
|
|
2005
|
+
return { success: true, data: result.data.toString("utf-8") };
|
|
2006
|
+
}
|
|
2007
|
+
const fs3 = await import("fs");
|
|
2008
|
+
const content = await fs3.promises.readFile(result.data, "utf-8");
|
|
2009
|
+
return { success: true, data: content };
|
|
2010
|
+
}
|
|
2011
|
+
/**
|
|
2012
|
+
* Copy a file to a new location
|
|
2013
|
+
*/
|
|
2014
|
+
async copyFile(sourcePath, destinationPath, options) {
|
|
2015
|
+
const downloadResult = await this.downloadFile(sourcePath);
|
|
2016
|
+
if (!downloadResult.success) {
|
|
2017
|
+
return { success: false, error: downloadResult.error };
|
|
2018
|
+
}
|
|
2019
|
+
const buffer = Buffer.isBuffer(downloadResult.data) ? downloadResult.data : await import("fs").then((fs3) => fs3.promises.readFile(downloadResult.data));
|
|
2020
|
+
return this.uploadFile(buffer, destinationPath, options);
|
|
2021
|
+
}
|
|
2022
|
+
/**
|
|
2023
|
+
* Ensure a directory exists (creates if needed)
|
|
2024
|
+
*/
|
|
2025
|
+
async ensureDirectory(path3) {
|
|
2026
|
+
const exists = await this.exists(path3);
|
|
2027
|
+
if (exists) {
|
|
2028
|
+
const item = await this.getItem(path3);
|
|
2029
|
+
if (item.success && item.data?.isDirectory) {
|
|
2030
|
+
return { success: true, data: item.data };
|
|
2031
|
+
}
|
|
2032
|
+
return { success: false, error: "Path exists but is not a directory" };
|
|
2033
|
+
}
|
|
2034
|
+
return this.createDirectory(path3);
|
|
2035
|
+
}
|
|
2036
|
+
};
|
|
2037
|
+
function createFileManager(options) {
|
|
2038
|
+
return new FileManager(options);
|
|
2039
|
+
}
|
|
2040
|
+
async function createInitializedFileManager(options) {
|
|
2041
|
+
const manager = new FileManager(options);
|
|
2042
|
+
await manager.initialize();
|
|
2043
|
+
return manager;
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2046
|
+
// src/common/naming-utils.ts
|
|
2047
|
+
var DEFAULT_DATE_FORMATS = [
|
|
2048
|
+
"YYYY",
|
|
2049
|
+
"YY",
|
|
2050
|
+
"MM",
|
|
2051
|
+
"M",
|
|
2052
|
+
"DD",
|
|
2053
|
+
"D",
|
|
2054
|
+
"MMM",
|
|
2055
|
+
"MMMM",
|
|
2056
|
+
"YYYY-MM-DD",
|
|
2057
|
+
"YYYY-MMM-DD",
|
|
2058
|
+
"DD-MM-YYYY",
|
|
2059
|
+
"MM-DD-YYYY"
|
|
2060
|
+
];
|
|
2061
|
+
var MONTH_NAMES_SHORT = [
|
|
2062
|
+
"Jan",
|
|
2063
|
+
"Feb",
|
|
2064
|
+
"Mar",
|
|
2065
|
+
"Apr",
|
|
2066
|
+
"May",
|
|
2067
|
+
"Jun",
|
|
2068
|
+
"Jul",
|
|
2069
|
+
"Aug",
|
|
2070
|
+
"Sep",
|
|
2071
|
+
"Oct",
|
|
2072
|
+
"Nov",
|
|
2073
|
+
"Dec"
|
|
2074
|
+
];
|
|
2075
|
+
var MONTH_NAMES_FULL = [
|
|
2076
|
+
"January",
|
|
2077
|
+
"February",
|
|
2078
|
+
"March",
|
|
2079
|
+
"April",
|
|
2080
|
+
"May",
|
|
2081
|
+
"June",
|
|
2082
|
+
"July",
|
|
2083
|
+
"August",
|
|
2084
|
+
"September",
|
|
2085
|
+
"October",
|
|
2086
|
+
"November",
|
|
2087
|
+
"December"
|
|
2088
|
+
];
|
|
2089
|
+
var SYSTEM_DATE_VARIABLES = [
|
|
2090
|
+
{ variable_name: "YYYY", description: "Full year (4 digits)", example_value: "2024", category: "date" },
|
|
2091
|
+
{ variable_name: "YY", description: "Year (2 digits)", example_value: "24", category: "date" },
|
|
2092
|
+
{ variable_name: "MM", description: "Month (2 digits, 01-12)", example_value: "01", category: "date" },
|
|
2093
|
+
{ variable_name: "M", description: "Month (1-12)", example_value: "1", category: "date" },
|
|
2094
|
+
{ variable_name: "DD", description: "Day (2 digits, 01-31)", example_value: "15", category: "date" },
|
|
2095
|
+
{ variable_name: "D", description: "Day (1-31)", example_value: "15", category: "date" },
|
|
2096
|
+
{ variable_name: "MMM", description: "Short month name", example_value: "Jan", category: "date" },
|
|
2097
|
+
{ variable_name: "MMMM", description: "Full month name", example_value: "January", category: "date" },
|
|
2098
|
+
{ variable_name: "YYYY-MM-DD", description: "ISO date format", example_value: "2024-01-15", category: "date" },
|
|
2099
|
+
{ variable_name: "YYYY-MMM-DD", description: "Date with month name", example_value: "2024-Jan-15", category: "date" },
|
|
2100
|
+
{ variable_name: "DD-MM-YYYY", description: "Day-Month-Year", example_value: "15-01-2024", category: "date" },
|
|
2101
|
+
{ variable_name: "MM-DD-YYYY", description: "Month-Day-Year", example_value: "01-15-2024", category: "date" }
|
|
2102
|
+
];
|
|
2103
|
+
var SYSTEM_FILE_VARIABLES = [
|
|
2104
|
+
{ variable_name: "original_name", description: "Original filename without extension", example_value: "document", category: "file" },
|
|
2105
|
+
{ variable_name: "extension", description: "Original file extension with dot", example_value: ".pdf", category: "file" },
|
|
2106
|
+
{ variable_name: "ext", description: "Extension without dot", example_value: "pdf", category: "file" }
|
|
2107
|
+
];
|
|
2108
|
+
var SYSTEM_COUNTER_VARIABLES = [
|
|
2109
|
+
{ variable_name: "counter", description: "Auto-incrementing number (3 digits)", example_value: "001", category: "counter" }
|
|
2110
|
+
];
|
|
2111
|
+
var ALL_SYSTEM_VARIABLES = [
|
|
2112
|
+
...SYSTEM_DATE_VARIABLES,
|
|
2113
|
+
...SYSTEM_FILE_VARIABLES,
|
|
2114
|
+
...SYSTEM_COUNTER_VARIABLES
|
|
2115
|
+
];
|
|
2116
|
+
function formatDateToken(date, format) {
|
|
2117
|
+
const year = date.getFullYear();
|
|
2118
|
+
const month = date.getMonth();
|
|
2119
|
+
const day = date.getDate();
|
|
2120
|
+
switch (format) {
|
|
2121
|
+
case "YYYY":
|
|
2122
|
+
return String(year);
|
|
2123
|
+
case "YY":
|
|
2124
|
+
return String(year).slice(-2);
|
|
2125
|
+
case "MM":
|
|
2126
|
+
return String(month + 1).padStart(2, "0");
|
|
2127
|
+
case "M":
|
|
2128
|
+
return String(month + 1);
|
|
2129
|
+
case "DD":
|
|
2130
|
+
return String(day).padStart(2, "0");
|
|
2131
|
+
case "D":
|
|
2132
|
+
return String(day);
|
|
2133
|
+
case "MMM":
|
|
2134
|
+
return MONTH_NAMES_SHORT[month];
|
|
2135
|
+
case "MMMM":
|
|
2136
|
+
return MONTH_NAMES_FULL[month];
|
|
2137
|
+
case "YYYY-MM-DD":
|
|
2138
|
+
return `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
|
|
2139
|
+
case "YYYY-MMM-DD":
|
|
2140
|
+
return `${year}-${MONTH_NAMES_SHORT[month]}-${String(day).padStart(2, "0")}`;
|
|
2141
|
+
case "DD-MM-YYYY":
|
|
2142
|
+
return `${String(day).padStart(2, "0")}-${String(month + 1).padStart(2, "0")}-${year}`;
|
|
2143
|
+
case "MM-DD-YYYY":
|
|
2144
|
+
return `${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}-${year}`;
|
|
2145
|
+
default:
|
|
2146
|
+
return format;
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
function isDateVariable(variableName, dateFormats = DEFAULT_DATE_FORMATS) {
|
|
2150
|
+
return dateFormats.includes(variableName);
|
|
2151
|
+
}
|
|
2152
|
+
function isFileMetadataVariable(variableName) {
|
|
2153
|
+
return ["original_name", "extension", "ext"].includes(variableName);
|
|
2154
|
+
}
|
|
2155
|
+
function isCounterVariable(variableName) {
|
|
2156
|
+
return variableName === "counter" || variableName.startsWith("counter:");
|
|
2157
|
+
}
|
|
2158
|
+
function formatCounter(value, digits = 3) {
|
|
2159
|
+
return String(value).padStart(digits, "0");
|
|
2160
|
+
}
|
|
2161
|
+
function getFileMetadataValues(filename) {
|
|
2162
|
+
if (!filename) {
|
|
2163
|
+
return {
|
|
2164
|
+
original_name: "",
|
|
2165
|
+
extension: "",
|
|
2166
|
+
ext: ""
|
|
2167
|
+
};
|
|
2168
|
+
}
|
|
2169
|
+
const extension = getExtension(filename);
|
|
2170
|
+
const originalName = getNameWithoutExtension(filename);
|
|
2171
|
+
return {
|
|
2172
|
+
original_name: originalName,
|
|
2173
|
+
extension,
|
|
2174
|
+
ext: extension.startsWith(".") ? extension.slice(1) : extension
|
|
2175
|
+
};
|
|
2176
|
+
}
|
|
2177
|
+
function generateNameFromPattern(pattern, variables, options = {}, originalFileName) {
|
|
2178
|
+
if (pattern.length === 0) {
|
|
2179
|
+
return { success: false, error: "Pattern is empty" };
|
|
2180
|
+
}
|
|
2181
|
+
const {
|
|
2182
|
+
dateFormats = DEFAULT_DATE_FORMATS,
|
|
2183
|
+
date = /* @__PURE__ */ new Date(),
|
|
2184
|
+
counterValue = 1,
|
|
2185
|
+
counterDigits = 3
|
|
2186
|
+
} = options;
|
|
2187
|
+
const fileMetadata = getFileMetadataValues(originalFileName);
|
|
2188
|
+
const parts = [];
|
|
2189
|
+
for (const segment of pattern) {
|
|
2190
|
+
if (segment.type === "literal") {
|
|
2191
|
+
parts.push(segment.value);
|
|
2192
|
+
} else if (segment.type === "variable") {
|
|
2193
|
+
const varName = segment.value;
|
|
2194
|
+
if (isDateVariable(varName, dateFormats)) {
|
|
2195
|
+
parts.push(formatDateToken(date, varName));
|
|
2196
|
+
} else if (isFileMetadataVariable(varName)) {
|
|
2197
|
+
parts.push(fileMetadata[varName] || "");
|
|
2198
|
+
} else if (isCounterVariable(varName)) {
|
|
2199
|
+
if (varName.startsWith("counter:")) {
|
|
2200
|
+
const customDigits = parseInt(varName.split(":")[1], 10);
|
|
2201
|
+
parts.push(formatCounter(counterValue, isNaN(customDigits) ? counterDigits : customDigits));
|
|
2202
|
+
} else {
|
|
2203
|
+
parts.push(formatCounter(counterValue, counterDigits));
|
|
2204
|
+
}
|
|
2205
|
+
} else if (varName in variables) {
|
|
2206
|
+
parts.push(variables[varName]);
|
|
2207
|
+
} else {
|
|
2208
|
+
return {
|
|
2209
|
+
success: false,
|
|
2210
|
+
error: `Missing value for variable: ${varName}`
|
|
2211
|
+
};
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
const name = parts.join("");
|
|
2216
|
+
if (!name.trim()) {
|
|
2217
|
+
return { success: false, error: "Generated name is empty" };
|
|
2218
|
+
}
|
|
2219
|
+
return { success: true, name };
|
|
2220
|
+
}
|
|
2221
|
+
function hazo_files_generate_folder_name(schema, variables, options) {
|
|
2222
|
+
const result = generateNameFromPattern(
|
|
2223
|
+
schema.folderPattern,
|
|
2224
|
+
variables,
|
|
2225
|
+
options
|
|
2226
|
+
);
|
|
2227
|
+
if (!result.success || !result.name) {
|
|
2228
|
+
return result;
|
|
2229
|
+
}
|
|
2230
|
+
const segments = result.name.split("/");
|
|
2231
|
+
const sanitizedSegments = segments.map((seg) => seg.trim()).filter((seg) => seg.length > 0).map((seg) => sanitizeFilename(seg));
|
|
2232
|
+
if (sanitizedSegments.length === 0) {
|
|
2233
|
+
return { success: false, error: "Generated folder name is empty" };
|
|
2234
|
+
}
|
|
2235
|
+
return {
|
|
2236
|
+
success: true,
|
|
2237
|
+
name: sanitizedSegments.join("/")
|
|
2238
|
+
};
|
|
2239
|
+
}
|
|
2240
|
+
function hazo_files_generate_file_name(schema, variables, originalFileName, options) {
|
|
2241
|
+
const { preserveExtension = true, ...restOptions } = options || {};
|
|
2242
|
+
const result = generateNameFromPattern(
|
|
2243
|
+
schema.filePattern,
|
|
2244
|
+
variables,
|
|
2245
|
+
restOptions,
|
|
2246
|
+
originalFileName
|
|
2247
|
+
);
|
|
2248
|
+
if (!result.success || !result.name) {
|
|
2249
|
+
return result;
|
|
2250
|
+
}
|
|
2251
|
+
let finalName = result.name;
|
|
2252
|
+
if (originalFileName && preserveExtension) {
|
|
2253
|
+
const originalExtension = getExtension(originalFileName);
|
|
2254
|
+
if (originalExtension) {
|
|
2255
|
+
const nameWithoutExt = getNameWithoutExtension(finalName);
|
|
2256
|
+
finalName = nameWithoutExt + originalExtension;
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
return {
|
|
2260
|
+
success: true,
|
|
2261
|
+
name: sanitizeFilename(finalName)
|
|
2262
|
+
};
|
|
2263
|
+
}
|
|
2264
|
+
function validateNamingRuleSchema(schema) {
|
|
2265
|
+
if (!schema || typeof schema !== "object") return false;
|
|
2266
|
+
const s = schema;
|
|
2267
|
+
if (typeof s.version !== "number") return false;
|
|
2268
|
+
if (!Array.isArray(s.filePattern) || !Array.isArray(s.folderPattern)) return false;
|
|
2269
|
+
const isValidSegment = (seg) => {
|
|
2270
|
+
if (!seg || typeof seg !== "object") return false;
|
|
2271
|
+
const segment = seg;
|
|
2272
|
+
return typeof segment.id === "string" && (segment.type === "variable" || segment.type === "literal") && typeof segment.value === "string";
|
|
2273
|
+
};
|
|
2274
|
+
return s.filePattern.every(isValidSegment) && s.folderPattern.every(isValidSegment);
|
|
2275
|
+
}
|
|
2276
|
+
function createEmptyNamingRuleSchema() {
|
|
2277
|
+
return {
|
|
2278
|
+
version: 1,
|
|
2279
|
+
filePattern: [],
|
|
2280
|
+
folderPattern: [],
|
|
2281
|
+
metadata: {
|
|
2282
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2283
|
+
}
|
|
2284
|
+
};
|
|
2285
|
+
}
|
|
2286
|
+
function generateSegmentId() {
|
|
2287
|
+
return `seg_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
|
|
2288
|
+
}
|
|
2289
|
+
function createVariableSegment(variableName) {
|
|
2290
|
+
return {
|
|
2291
|
+
id: generateSegmentId(),
|
|
2292
|
+
type: "variable",
|
|
2293
|
+
value: variableName
|
|
2294
|
+
};
|
|
2295
|
+
}
|
|
2296
|
+
function createLiteralSegment(text) {
|
|
2297
|
+
return {
|
|
2298
|
+
id: generateSegmentId(),
|
|
2299
|
+
type: "literal",
|
|
2300
|
+
value: text
|
|
2301
|
+
};
|
|
2302
|
+
}
|
|
2303
|
+
function patternToString(pattern) {
|
|
2304
|
+
return pattern.map((seg) => seg.type === "variable" ? `{${seg.value}}` : seg.value).join("");
|
|
2305
|
+
}
|
|
2306
|
+
function parsePatternString(patternStr) {
|
|
2307
|
+
const segments = [];
|
|
2308
|
+
const regex = /\{([^}]+)\}|([^{]+)/g;
|
|
2309
|
+
let match;
|
|
2310
|
+
while ((match = regex.exec(patternStr)) !== null) {
|
|
2311
|
+
if (match[1]) {
|
|
2312
|
+
segments.push(createVariableSegment(match[1]));
|
|
2313
|
+
} else if (match[2]) {
|
|
2314
|
+
segments.push(createLiteralSegment(match[2]));
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
return segments;
|
|
2318
|
+
}
|
|
2319
|
+
function clonePattern(pattern) {
|
|
2320
|
+
return pattern.map((seg) => ({
|
|
2321
|
+
...seg,
|
|
2322
|
+
id: generateSegmentId()
|
|
2323
|
+
}));
|
|
2324
|
+
}
|
|
2325
|
+
function getSystemVariablePreviewValues(date = /* @__PURE__ */ new Date(), options = {}) {
|
|
2326
|
+
const { counterValue = 1, counterDigits = 3, originalFileName } = options;
|
|
2327
|
+
const fileMetadata = getFileMetadataValues(originalFileName);
|
|
2328
|
+
const values = {};
|
|
2329
|
+
for (const format of DEFAULT_DATE_FORMATS) {
|
|
2330
|
+
values[format] = formatDateToken(date, format);
|
|
2331
|
+
}
|
|
2332
|
+
values.original_name = fileMetadata.original_name || "document";
|
|
2333
|
+
values.extension = fileMetadata.extension || ".pdf";
|
|
2334
|
+
values.ext = fileMetadata.ext || "pdf";
|
|
2335
|
+
values.counter = formatCounter(counterValue, counterDigits);
|
|
2336
|
+
return values;
|
|
2337
|
+
}
|
|
2338
|
+
function generatePreviewName(pattern, userVariables, options = {}) {
|
|
2339
|
+
if (pattern.length === 0) {
|
|
2340
|
+
return "(empty pattern)";
|
|
2341
|
+
}
|
|
2342
|
+
const systemValues = getSystemVariablePreviewValues(
|
|
2343
|
+
options.date,
|
|
2344
|
+
options
|
|
2345
|
+
);
|
|
2346
|
+
const userValues = {};
|
|
2347
|
+
for (const variable of userVariables) {
|
|
2348
|
+
userValues[variable.variable_name] = variable.example_value;
|
|
2349
|
+
}
|
|
2350
|
+
const allValues = { ...userValues, ...systemValues };
|
|
2351
|
+
const parts = [];
|
|
2352
|
+
for (const segment of pattern) {
|
|
2353
|
+
if (segment.type === "literal") {
|
|
2354
|
+
parts.push(segment.value);
|
|
2355
|
+
} else if (segment.type === "variable") {
|
|
2356
|
+
const value = allValues[segment.value];
|
|
2357
|
+
if (value !== void 0) {
|
|
2358
|
+
parts.push(value);
|
|
2359
|
+
} else {
|
|
2360
|
+
parts.push(`{${segment.value}}`);
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2364
|
+
return parts.join("") || "(empty)";
|
|
2365
|
+
}
|
|
2366
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
2367
|
+
0 && (module.exports = {
|
|
2368
|
+
ALL_SYSTEM_VARIABLES,
|
|
2369
|
+
AuthenticationError,
|
|
2370
|
+
ConfigurationError,
|
|
2371
|
+
DEFAULT_DATE_FORMATS,
|
|
2372
|
+
DirectoryExistsError,
|
|
2373
|
+
DirectoryNotEmptyError,
|
|
2374
|
+
DirectoryNotFoundError,
|
|
2375
|
+
FileExistsError,
|
|
2376
|
+
FileManager,
|
|
2377
|
+
FileNotFoundError,
|
|
2378
|
+
FileTooLargeError,
|
|
2379
|
+
GoogleDriveAuth,
|
|
2380
|
+
GoogleDriveModule,
|
|
2381
|
+
HazoFilesError,
|
|
2382
|
+
InvalidExtensionError,
|
|
2383
|
+
InvalidPathError,
|
|
2384
|
+
LocalStorageModule,
|
|
2385
|
+
OperationError,
|
|
2386
|
+
PermissionDeniedError,
|
|
2387
|
+
SYSTEM_COUNTER_VARIABLES,
|
|
2388
|
+
SYSTEM_DATE_VARIABLES,
|
|
2389
|
+
SYSTEM_FILE_VARIABLES,
|
|
2390
|
+
clonePattern,
|
|
2391
|
+
createAndInitializeModule,
|
|
2392
|
+
createEmptyNamingRuleSchema,
|
|
2393
|
+
createFileItem,
|
|
2394
|
+
createFileManager,
|
|
2395
|
+
createFolderItem,
|
|
2396
|
+
createGoogleDriveAuth,
|
|
2397
|
+
createGoogleDriveModule,
|
|
2398
|
+
createInitializedFileManager,
|
|
2399
|
+
createLiteralSegment,
|
|
2400
|
+
createLocalModule,
|
|
2401
|
+
createModule,
|
|
2402
|
+
createVariableSegment,
|
|
2403
|
+
errorResult,
|
|
2404
|
+
filterItems,
|
|
2405
|
+
formatBytes,
|
|
2406
|
+
formatCounter,
|
|
2407
|
+
formatDateToken,
|
|
2408
|
+
generateId,
|
|
2409
|
+
generatePreviewName,
|
|
2410
|
+
generateSampleConfig,
|
|
2411
|
+
generateSegmentId,
|
|
2412
|
+
getBaseName,
|
|
2413
|
+
getBreadcrumbs,
|
|
2414
|
+
getDirName,
|
|
2415
|
+
getExtension,
|
|
2416
|
+
getExtensionFromMime,
|
|
2417
|
+
getFileCategory,
|
|
2418
|
+
getFileMetadataValues,
|
|
2419
|
+
getMimeType,
|
|
2420
|
+
getNameWithoutExtension,
|
|
2421
|
+
getParentPath,
|
|
2422
|
+
getPathSegments,
|
|
2423
|
+
getRegisteredProviders,
|
|
2424
|
+
getRelativePath,
|
|
2425
|
+
getSystemVariablePreviewValues,
|
|
2426
|
+
hasExtension,
|
|
2427
|
+
hazo_files_generate_file_name,
|
|
2428
|
+
hazo_files_generate_folder_name,
|
|
2429
|
+
isAudio,
|
|
2430
|
+
isChildPath,
|
|
2431
|
+
isCounterVariable,
|
|
2432
|
+
isDateVariable,
|
|
2433
|
+
isDocument,
|
|
2434
|
+
isFile,
|
|
2435
|
+
isFileMetadataVariable,
|
|
2436
|
+
isFolder,
|
|
2437
|
+
isImage,
|
|
2438
|
+
isPreviewable,
|
|
2439
|
+
isProviderRegistered,
|
|
2440
|
+
isText,
|
|
2441
|
+
isVideo,
|
|
2442
|
+
joinPath,
|
|
2443
|
+
loadConfig,
|
|
2444
|
+
loadConfigAsync,
|
|
2445
|
+
normalizePath,
|
|
2446
|
+
parseConfig,
|
|
2447
|
+
parsePatternString,
|
|
2448
|
+
patternToString,
|
|
2449
|
+
registerModule,
|
|
2450
|
+
sanitizeFilename,
|
|
2451
|
+
saveConfig,
|
|
2452
|
+
sortItems,
|
|
2453
|
+
successResult,
|
|
2454
|
+
validateNamingRuleSchema,
|
|
2455
|
+
validatePath
|
|
2456
|
+
});
|
|
2457
|
+
//# sourceMappingURL=index.js.map
|