@hasna/files 0.2.16 → 0.2.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -131979,7 +131979,7 @@ var init_sync = __esm(() => {
131979
131979
 
131980
131980
  // node_modules/readdirp/esm/index.js
131981
131981
  import { stat, lstat, readdir, realpath } from "fs/promises";
131982
- import { Readable as Readable4 } from "stream";
131982
+ import { Readable as Readable5 } from "stream";
131983
131983
  import { resolve as presolve, relative as prelative, join as pjoin, sep as psep } from "path";
131984
131984
  function readdirp(root2, options = {}) {
131985
131985
  let type = options.entryType || options.type;
@@ -132048,7 +132048,7 @@ var init_esm2 = __esm(() => {
132048
132048
  EntryTypes.FILE_TYPE
132049
132049
  ]);
132050
132050
  wantBigintFsStats = process.platform === "win32";
132051
- ReaddirpStream = class ReaddirpStream extends Readable4 {
132051
+ ReaddirpStream = class ReaddirpStream extends Readable5 {
132052
132052
  constructor(options = {}) {
132053
132053
  super({
132054
132054
  objectMode: true,
@@ -134007,8 +134007,9 @@ async function walkDir(rootPath, dirPath, source, machine_id, seenPaths, stats,
134007
134007
  init_mime_types();
134008
134008
  init_machines();
134009
134009
  init_files();
134010
- import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync7 } from "fs";
134011
- import { basename as basename5, dirname as dirname5, extname as extname5, join as join8, posix } from "path";
134010
+ import { createWriteStream as createWriteStream2, mkdirSync as mkdirSync10, writeFileSync as writeFileSync9 } from "fs";
134011
+ import { basename as basename5, dirname as dirname5, extname as extname5, join as join11, posix } from "path";
134012
+ import { pipeline as pipeline2 } from "stream/promises";
134012
134013
 
134013
134014
  // src/db/google-drive.ts
134014
134015
  init_database();
@@ -143584,6 +143585,12 @@ async function uploadBufferToS3(source, body, s3Key, contentType = "application/
143584
143585
  return s3Key;
143585
143586
  }
143586
143587
 
143588
+ // src/lib/google-drive-client.ts
143589
+ import { existsSync as existsSync9, mkdirSync as mkdirSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "fs";
143590
+ import { homedir as homedir7 } from "os";
143591
+ import { join as join8 } from "path";
143592
+ import { Readable as Readable4 } from "stream";
143593
+
143587
143594
  // node_modules/@hasna/connectors/dist/index.js
143588
143595
  import { Buffer as Buffer22 } from "buffer";
143589
143596
  import { existsSync as existsSync5 } from "fs";
@@ -167379,6 +167386,9 @@ function runLegacyConnectorCommand(name, args, timeoutMs = 30000) {
167379
167386
 
167380
167387
  // src/lib/google-drive-client.ts
167381
167388
  var GOOGLE_FOLDER_MIME = "application/vnd.google-apps.folder";
167389
+ var DRIVE_API_BASE2 = "https://www.googleapis.com/drive/v3";
167390
+ var TOKEN_URL3 = "https://oauth2.googleapis.com/token";
167391
+ var REFRESH_BUFFER_MS3 = 5 * 60 * 1000;
167382
167392
  var DEFAULT_EXPORT_FORMATS2 = {
167383
167393
  document: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
167384
167394
  spreadsheet: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
@@ -167440,7 +167450,7 @@ class ConnectorSdkGoogleDriveClient {
167440
167450
  },
167441
167451
  exportMimeType: file.mimeType.startsWith("application/vnd.google-apps.") ? getExportMimeType(file.mimeType, exportFormats) : undefined
167442
167452
  });
167443
- if (!response.dataBase64) {
167453
+ if (response.dataBase64 === undefined) {
167444
167454
  throw new Error(`Google Drive download for "${file.name}" returned no data`);
167445
167455
  }
167446
167456
  const data = Buffer.from(response.dataBase64, "base64");
@@ -167450,6 +167460,29 @@ class ConnectorSdkGoogleDriveClient {
167450
167460
  mimeType: response.mimeType ?? file.mimeType ?? "application/octet-stream"
167451
167461
  };
167452
167462
  }
167463
+ async downloadFileStream(file, exportFormats = {}) {
167464
+ if (file.mimeType.startsWith("application/vnd.google-apps.")) {
167465
+ const downloaded = await this.downloadFile(file, exportFormats);
167466
+ return {
167467
+ body: Readable4.from(Buffer.from(downloaded.data)),
167468
+ filename: downloaded.filename,
167469
+ mimeType: downloaded.mimeType,
167470
+ size: downloaded.data.byteLength
167471
+ };
167472
+ }
167473
+ const response = await requestGoogleDrive(this.profile, `/files/${encodeURIComponent(file.id)}`, {
167474
+ alt: "media",
167475
+ supportsAllDrives: true
167476
+ });
167477
+ if (!response.body)
167478
+ throw new Error(`Google Drive download for "${file.name}" returned no stream`);
167479
+ return {
167480
+ body: Readable4.fromWeb(response.body),
167481
+ filename: file.name,
167482
+ mimeType: response.headers.get("content-type")?.split(";")[0] || file.mimeType || "application/octet-stream",
167483
+ size: file.size ? Number(file.size) : Number(response.headers.get("content-length") ?? 0) || undefined
167484
+ };
167485
+ }
167453
167486
  }
167454
167487
  async function runGoogleDriveOperation(operation, profile, input) {
167455
167488
  const result = await runConnectorOperation({
@@ -167477,9 +167510,136 @@ function getExportMimeType(googleMimeType, exportFormats) {
167477
167510
  return exportFormats.drawing ?? DEFAULT_EXPORT_FORMATS2.drawing;
167478
167511
  throw new Error(`Cannot export Google Workspace file type: ${googleMimeType}`);
167479
167512
  }
167513
+ async function requestGoogleDrive(profile, path, params) {
167514
+ const token = await getValidAccessToken3(profile);
167515
+ const url = new URL(`${DRIVE_API_BASE2}${path}`);
167516
+ for (const [key, value] of Object.entries(params)) {
167517
+ if (value !== undefined && value !== null && value !== "")
167518
+ url.searchParams.set(key, String(value));
167519
+ }
167520
+ const response = await fetch(url, {
167521
+ headers: {
167522
+ Authorization: `Bearer ${token}`,
167523
+ Accept: "application/octet-stream"
167524
+ }
167525
+ });
167526
+ if (!response.ok) {
167527
+ const body = await response.text().catch(() => "");
167528
+ throw new Error(`Google Drive request failed (${response.status}): ${extractGoogleError2(body) || response.statusText}`);
167529
+ }
167530
+ return response;
167531
+ }
167532
+ async function getValidAccessToken3(profile) {
167533
+ if (process.env.GOOGLE_ACCESS_TOKEN)
167534
+ return process.env.GOOGLE_ACCESS_TOKEN;
167535
+ const loaded = loadTokens4(profile);
167536
+ const tokens = loaded?.tokens;
167537
+ if (!tokens?.accessToken && !tokens?.refreshToken) {
167538
+ throw new Error(`Google Drive profile "${profile}" is not authenticated. Run: connectors auth googledrive`);
167539
+ }
167540
+ if (tokens.accessToken && (!tokens.expiresAt || Date.now() < tokens.expiresAt - REFRESH_BUFFER_MS3))
167541
+ return tokens.accessToken;
167542
+ if (!tokens.refreshToken)
167543
+ return tokens.accessToken ?? "";
167544
+ return (await refreshAccessToken3(profile, tokens, loaded?.baseDir)).accessToken ?? "";
167545
+ }
167546
+ async function refreshAccessToken3(profile, currentTokens, preferredBaseDir) {
167547
+ const credentials = loadCredentials3(profile);
167548
+ if (!credentials.clientId || !credentials.clientSecret)
167549
+ throw new Error("Google Drive OAuth credentials are not configured. Run: connectors auth googledrive");
167550
+ if (!currentTokens.refreshToken)
167551
+ throw new Error(`Google Drive profile "${profile}" has no refresh token. Run: connectors auth googledrive`);
167552
+ const response = await fetch(TOKEN_URL3, {
167553
+ method: "POST",
167554
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
167555
+ body: new URLSearchParams({
167556
+ client_id: credentials.clientId,
167557
+ client_secret: credentials.clientSecret,
167558
+ refresh_token: currentTokens.refreshToken,
167559
+ grant_type: "refresh_token"
167560
+ })
167561
+ });
167562
+ const data = await response.json().catch(() => ({}));
167563
+ if (!response.ok || !data.access_token)
167564
+ throw new Error(`Google Drive token refresh failed: ${data.error_description || data.error || response.statusText}`);
167565
+ const tokens = {
167566
+ accessToken: data.access_token,
167567
+ refreshToken: currentTokens.refreshToken,
167568
+ expiresAt: Date.now() + (data.expires_in ?? 3600) * 1000,
167569
+ tokenType: data.token_type ?? currentTokens.tokenType,
167570
+ scope: data.scope ?? currentTokens.scope
167571
+ };
167572
+ saveTokens3(profile, tokens, preferredBaseDir);
167573
+ return tokens;
167574
+ }
167575
+ function loadCredentials3(profile) {
167576
+ const envClientId = process.env.GOOGLE_CLIENT_ID;
167577
+ const envClientSecret = process.env.GOOGLE_CLIENT_SECRET;
167578
+ if (envClientId && envClientSecret)
167579
+ return { clientId: envClientId, clientSecret: envClientSecret };
167580
+ for (const baseDir of configDirs3()) {
167581
+ const credentials = {
167582
+ ...readJson3(join8(baseDir, "credentials.json")),
167583
+ ...readJson3(join8(baseDir, "profiles", profile, "config.json"))
167584
+ };
167585
+ if (credentials.clientId || credentials.clientSecret)
167586
+ return credentials;
167587
+ }
167588
+ return {};
167589
+ }
167590
+ function loadTokens4(profile) {
167591
+ for (const baseDir of configDirs3()) {
167592
+ const fromProfile = readJson3(join8(baseDir, "profiles", profile, "tokens.json"));
167593
+ if (fromProfile)
167594
+ return { tokens: fromProfile, baseDir };
167595
+ const flat = readJson3(join8(baseDir, "profiles", `${profile}.json`));
167596
+ const tokens = flat?.tokens ?? (flat?.accessToken || flat?.refreshToken ? flat : null);
167597
+ if (tokens)
167598
+ return { tokens, baseDir };
167599
+ }
167600
+ return null;
167601
+ }
167602
+ function saveTokens3(profile, tokens, preferredBaseDir) {
167603
+ const dirs = configDirs3();
167604
+ const baseDir = preferredBaseDir ?? dirs.find((dir) => existsSync9(dir)) ?? dirs[0];
167605
+ if (!baseDir)
167606
+ throw new Error("Google Drive connector configuration directory is unavailable");
167607
+ const profileDir = join8(baseDir, "profiles", profile);
167608
+ mkdirSync8(profileDir, { recursive: true });
167609
+ writeFileSync7(join8(profileDir, "tokens.json"), JSON.stringify(tokens, null, 2), { mode: 384 });
167610
+ }
167611
+ function configDirs3() {
167612
+ const explicit = process.env.HASNA_GOOGLE_DRIVE_CONNECTOR_DIR ?? process.env.GOOGLE_DRIVE_CONNECTOR_DIR;
167613
+ if (explicit)
167614
+ return [explicit];
167615
+ const baseDir = process.env.HASNA_CONNECTORS_DIR ?? join8(homedir7(), ".hasna", "connectors");
167616
+ return [join8(baseDir, "googledrive"), join8(baseDir, "connect-googledrive")];
167617
+ }
167618
+ function readJson3(path) {
167619
+ if (!existsSync9(path))
167620
+ return null;
167621
+ try {
167622
+ return JSON.parse(readFileSync8(path, "utf8"));
167623
+ } catch {
167624
+ return null;
167625
+ }
167626
+ }
167627
+ function extractGoogleError2(body) {
167628
+ if (!body)
167629
+ return "";
167630
+ try {
167631
+ const parsed = JSON.parse(body);
167632
+ if (typeof parsed.error === "object" && parsed.error?.message)
167633
+ return parsed.error.message;
167634
+ if (typeof parsed.error === "string")
167635
+ return parsed.error_description || parsed.error;
167636
+ } catch {}
167637
+ return body.slice(0, 500);
167638
+ }
167480
167639
 
167481
167640
  // src/lib/google-drive.ts
167482
167641
  var DRIVE_FIELDS = "nextPageToken,files(id,name,mimeType,size,modifiedTime,parents,version,md5Checksum)";
167642
+ var STREAM_TO_S3_THRESHOLD_BYTES = 64 * 1024 * 1024;
167483
167643
  var clientFactory = createConnectorProfileGoogleDriveClient;
167484
167644
  var profileStatusProvider = listGoogleDriveProfileStatusesFromConnectorConfig;
167485
167645
  var storageAdapter = {
@@ -167487,9 +167647,17 @@ var storageAdapter = {
167487
167647
  writeLocal: async (source, relativePath, data) => {
167488
167648
  if (!source.path)
167489
167649
  throw new Error("Local destination source missing path");
167490
- const localPath = join8(source.path, relativePath);
167491
- mkdirSync8(dirname5(localPath), { recursive: true });
167492
- writeFileSync7(localPath, data);
167650
+ const localPath = join11(source.path, relativePath);
167651
+ mkdirSync10(dirname5(localPath), { recursive: true });
167652
+ writeFileSync9(localPath, data);
167653
+ return relativePath;
167654
+ },
167655
+ writeLocalStream: async (source, relativePath, body) => {
167656
+ if (!source.path)
167657
+ throw new Error("Local destination source missing path");
167658
+ const localPath = join11(source.path, relativePath);
167659
+ mkdirSync10(dirname5(localPath), { recursive: true });
167660
+ await pipeline2(body, createWriteStream2(localPath));
167493
167661
  return relativePath;
167494
167662
  }
167495
167663
  };
@@ -167615,22 +167783,17 @@ async function syncGoogleDriveSource(source) {
167615
167783
  const existing = getGoogleDriveImportedObject(source.id, item.drive_id, item.id);
167616
167784
  if (existing && !shouldImport(config9, item, existing, destination.source.id, destination.storage_type))
167617
167785
  continue;
167618
- const downloaded = await downloadOrArchiveGoogleDriveItem(client2, item, config9);
167619
- const importedName = basename5(downloaded.filename);
167620
- const importedPath = buildImportedPath(config9, item, importedName);
167621
- const contentType = downloaded.mimeType || ($lookup(downloaded.filename) || item.mime || "application/octet-stream");
167622
- const data = Buffer.from(downloaded.data);
167623
- const storageKey = await writeToDestination(destination.source, destination.storage_type, importedPath, data, contentType);
167786
+ const importResult = await importGoogleDriveItem(client2, item, config9, destination.source, destination.storage_type);
167624
167787
  const fileRecord = upsertFile({
167625
167788
  id: existing?.file_record_id,
167626
167789
  source_id: destination.source.id,
167627
167790
  machine_id: machine.id,
167628
- path: storageKey,
167629
- name: importedName,
167630
- ext: extname5(importedName).toLowerCase(),
167631
- size: data.byteLength,
167632
- mime: contentType,
167633
- hash: item.hash ?? hashBuffer(data),
167791
+ path: importResult.storageKey,
167792
+ name: importResult.importedName,
167793
+ ext: extname5(importResult.importedName).toLowerCase(),
167794
+ size: importResult.size,
167795
+ mime: importResult.contentType,
167796
+ hash: importResult.hash,
167634
167797
  status: "active",
167635
167798
  modified_at: item.modified_at
167636
167799
  });
@@ -167640,17 +167803,17 @@ async function syncGoogleDriveSource(source) {
167640
167803
  file_id: item.id,
167641
167804
  profile: config9.profile,
167642
167805
  parent_id: item.parent_id,
167643
- path: importedPath,
167644
- name: importedName,
167645
- mime: contentType,
167646
- size: data.byteLength,
167806
+ path: importResult.importedPath,
167807
+ name: importResult.importedName,
167808
+ mime: importResult.contentType,
167809
+ size: importResult.size,
167647
167810
  modified_at: item.modified_at,
167648
167811
  version: item.version,
167649
- hash: item.hash,
167812
+ hash: importResult.hash,
167650
167813
  storage_type: destination.storage_type,
167651
- storage_key: storageKey,
167814
+ storage_key: importResult.storageKey,
167652
167815
  destination_source_id: destination.source.id,
167653
- s3_key: destination.storage_type === "s3" ? storageKey : "",
167816
+ s3_key: destination.storage_type === "s3" ? importResult.storageKey : "",
167654
167817
  file_record_id: fileRecord.id,
167655
167818
  deleted: false,
167656
167819
  last_imported_at: new Date().toISOString()
@@ -167715,6 +167878,51 @@ async function writeToDestination(source, storageType, importedPath, data, conte
167715
167878
  }
167716
167879
  return storageAdapter.writeLocal(source, importedPath, data);
167717
167880
  }
167881
+ async function importGoogleDriveItem(client2, item, config9, destinationSource, storageType) {
167882
+ if (shouldStreamGoogleDriveItem(client2, item, storageType)) {
167883
+ const downloaded2 = await client2.downloadFileStream(toApiFile(item), config9.export_formats);
167884
+ return importGoogleDriveStream(destinationSource, storageType, item, config9, downloaded2);
167885
+ }
167886
+ const downloaded = await downloadOrArchiveGoogleDriveItem(client2, item, config9);
167887
+ const importedName = basename5(downloaded.filename);
167888
+ const importedPath = buildImportedPath(config9, item, importedName);
167889
+ const contentType = downloaded.mimeType || ($lookup(downloaded.filename) || item.mime || "application/octet-stream");
167890
+ const data = Buffer.from(downloaded.data);
167891
+ const storageKey = await writeToDestination(destinationSource, storageType, importedPath, data, contentType);
167892
+ return {
167893
+ importedName,
167894
+ importedPath,
167895
+ contentType,
167896
+ size: data.byteLength,
167897
+ hash: item.hash ?? hashBuffer(data),
167898
+ storageKey
167899
+ };
167900
+ }
167901
+ function shouldStreamGoogleDriveItem(client2, item, storageType) {
167902
+ return storageType === "s3" && typeof client2.downloadFileStream === "function" && !item.mime.startsWith("application/vnd.google-apps.") && item.size >= STREAM_TO_S3_THRESHOLD_BYTES;
167903
+ }
167904
+ async function importGoogleDriveStream(source, storageType, item, config9, downloaded) {
167905
+ const importedName = basename5(downloaded.filename);
167906
+ const importedPath = buildImportedPath(config9, item, importedName);
167907
+ const contentType = downloaded.mimeType || ($lookup(downloaded.filename) || item.mime || "application/octet-stream");
167908
+ const size = downloaded.size ?? item.size;
167909
+ const storageKey = await writeStreamToDestination(source, storageType, importedPath, downloaded.body, contentType, size);
167910
+ return {
167911
+ importedName,
167912
+ importedPath,
167913
+ contentType,
167914
+ size,
167915
+ hash: item.hash,
167916
+ storageKey
167917
+ };
167918
+ }
167919
+ async function writeStreamToDestination(source, storageType, importedPath, body, contentType, contentLength) {
167920
+ if (storageType === "s3") {
167921
+ const key = buildStorageKey(source, importedPath);
167922
+ return storageAdapter.uploadS3(source, body, key, contentType, contentLength);
167923
+ }
167924
+ return storageAdapter.writeLocalStream(source, importedPath, body);
167925
+ }
167718
167926
  async function getIncludedSharedDrives(source, client2) {
167719
167927
  const config9 = getGoogleDriveConfig(source);
167720
167928
  if (!config9.include_all_shared_drives && (!config9.shared_drive_ids || config9.shared_drive_ids.length === 0)) {
@@ -167979,8 +168187,8 @@ function requireId(partial, table) {
167979
168187
  }
167980
168188
 
167981
168189
  // src/cli/index.tsx
167982
- import { resolve as resolve3, join as join17 } from "path";
167983
- import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
168190
+ import { resolve as resolve3, join as join18 } from "path";
168191
+ import { existsSync as existsSync13, readFileSync as readFileSync10 } from "fs";
167984
168192
  import { createRequire as createRequire2 } from "module";
167985
168193
  var _require = createRequire2(import.meta.url);
167986
168194
  var _pkg = _require("../../package.json");
@@ -168037,7 +168245,7 @@ sources.command("add <path-or-s3>").description("Add a local folder or S3 bucket
168037
168245
  console.log(chalk.green(`\u2713 S3 source added: ${source.id} \u2192 s3://${bucket}${prefix ? `/${prefix}` : ""}`));
168038
168246
  } else {
168039
168247
  const absPath = resolve3(pathOrS3);
168040
- if (!existsSync9(absPath)) {
168248
+ if (!existsSync13(absPath)) {
168041
168249
  console.error(chalk.red(`Path does not exist: ${absPath}`));
168042
168250
  process.exit(1);
168043
168251
  }
@@ -168478,7 +168686,7 @@ program2.command("download <file-id> [dest]").description("Download a file to lo
168478
168686
  process.exit(1);
168479
168687
  }
168480
168688
  if (source.type === "local") {
168481
- const fullPath = join17(source.path, file.path);
168689
+ const fullPath = join18(source.path, file.path);
168482
168690
  console.log(chalk.dim(`Local file at: ${fullPath}`));
168483
168691
  return;
168484
168692
  }
@@ -168499,7 +168707,7 @@ program2.command("upload <local-path> <source-id> [s3-key]").description("Upload
168499
168707
  console.error(chalk.red("upload only works with S3 sources"));
168500
168708
  process.exit(1);
168501
168709
  }
168502
- if (!existsSync9(localPath)) {
168710
+ if (!existsSync13(localPath)) {
168503
168711
  console.error(chalk.red(`File not found: ${localPath}`));
168504
168712
  process.exit(1);
168505
168713
  }
@@ -168772,7 +168980,7 @@ program2.command("open <file-id>").description("Open a file in the default appli
168772
168980
  console.error(chalk.red("open only works with local sources"));
168773
168981
  process.exit(1);
168774
168982
  }
168775
- const fullPath = join17(source.path, file.path);
168983
+ const fullPath = join18(source.path, file.path);
168776
168984
  Bun.spawn(getOpenCommand(fullPath), { stdout: "inherit", stderr: "inherit" });
168777
168985
  } catch (e3) {
168778
168986
  console.error(chalk.red(e3.message));
@@ -168787,7 +168995,7 @@ program2.command("where <file-id>").description("Print the full absolute path of
168787
168995
  console.error(chalk.red("where only works with local sources"));
168788
168996
  process.exit(1);
168789
168997
  }
168790
- process.stdout.write(join17(source.path, file.path) + `
168998
+ process.stdout.write(join18(source.path, file.path) + `
168791
168999
  `);
168792
169000
  } catch (e3) {
168793
169001
  console.error(chalk.red(e3.message));
@@ -168802,9 +169010,9 @@ program2.command("cat <file-id>").description("Print file content to stdout").op
168802
169010
  console.error(chalk.red("cat only works with local sources"));
168803
169011
  process.exit(1);
168804
169012
  }
168805
- const fullPath = join17(source.path, file.path);
169013
+ const fullPath = join18(source.path, file.path);
168806
169014
  const maxBytes = parseIntFlag(opts.maxBytes, "max-bytes", { min: 0 });
168807
- const buf = readFileSync8(fullPath);
169015
+ const buf = readFileSync10(fullPath);
168808
169016
  const slice = maxBytes > 0 ? buf.slice(0, maxBytes) : buf;
168809
169017
  process.stdout.write(slice);
168810
169018
  } catch (e3) {