@sinoia/hubdoc-tools 1.8.1 → 1.8.2
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/cli.js +200 -34
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -732518,12 +732518,57 @@ var import_chalk26 = __toESM(require_source());
|
|
|
732518
732518
|
|
|
732519
732519
|
// apps/cli/cli/fuse/hubdoc-fs.ts
|
|
732520
732520
|
var import_fs_extra32 = __toESM(require_lib());
|
|
732521
|
+
var import_fs3 = require("fs");
|
|
732521
732522
|
var import_os7 = __toESM(require("os"));
|
|
732522
732523
|
var import_path34 = __toESM(require("path"));
|
|
732523
732524
|
var import_crypto6 = __toESM(require("crypto"));
|
|
732524
732525
|
var import_form_data5 = __toESM(require_form_data());
|
|
732525
732526
|
var DIR_MODE = 16877;
|
|
732526
732527
|
var FILE_MODE = 33188;
|
|
732528
|
+
var CHUNKED_THRESHOLD2 = 50 * 1024 * 1024;
|
|
732529
|
+
var MIME_BY_EXT = {
|
|
732530
|
+
// Office 2007+
|
|
732531
|
+
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
732532
|
+
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
732533
|
+
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
732534
|
+
// Office legacy
|
|
732535
|
+
".doc": "application/msword",
|
|
732536
|
+
".xls": "application/vnd.ms-excel",
|
|
732537
|
+
".ppt": "application/vnd.ms-powerpoint",
|
|
732538
|
+
// OpenDocument
|
|
732539
|
+
".odt": "application/vnd.oasis.opendocument.text",
|
|
732540
|
+
".ods": "application/vnd.oasis.opendocument.spreadsheet",
|
|
732541
|
+
".odp": "application/vnd.oasis.opendocument.presentation",
|
|
732542
|
+
// PDF & text
|
|
732543
|
+
".pdf": "application/pdf",
|
|
732544
|
+
".txt": "text/plain",
|
|
732545
|
+
".md": "text/markdown",
|
|
732546
|
+
".csv": "text/csv",
|
|
732547
|
+
".json": "application/json",
|
|
732548
|
+
".xml": "application/xml",
|
|
732549
|
+
".html": "text/html",
|
|
732550
|
+
// Images
|
|
732551
|
+
".png": "image/png",
|
|
732552
|
+
".jpg": "image/jpeg",
|
|
732553
|
+
".jpeg": "image/jpeg",
|
|
732554
|
+
".gif": "image/gif",
|
|
732555
|
+
".webp": "image/webp",
|
|
732556
|
+
".svg": "image/svg+xml",
|
|
732557
|
+
".tiff": "image/tiff",
|
|
732558
|
+
".tif": "image/tiff",
|
|
732559
|
+
".heic": "image/heic",
|
|
732560
|
+
// Archives
|
|
732561
|
+
".zip": "application/zip",
|
|
732562
|
+
".gz": "application/gzip",
|
|
732563
|
+
".tar": "application/x-tar",
|
|
732564
|
+
".7z": "application/x-7z-compressed"
|
|
732565
|
+
};
|
|
732566
|
+
function mimeForFile(name) {
|
|
732567
|
+
return MIME_BY_EXT[import_path34.default.extname(name).toLowerCase()] || "application/octet-stream";
|
|
732568
|
+
}
|
|
732569
|
+
function isOfficeLockfile(name) {
|
|
732570
|
+
return /^~\$/.test(name) || /^\.~lock\./.test(name) || /^Word Work File /.test(name);
|
|
732571
|
+
}
|
|
732527
732572
|
var HubdocFs = class {
|
|
732528
732573
|
constructor(ctx, root, opts = {}) {
|
|
732529
732574
|
this.ctx = ctx;
|
|
@@ -732697,47 +732742,99 @@ var HubdocFs = class {
|
|
|
732697
732742
|
node.downloaded = true;
|
|
732698
732743
|
node.size = (await import_fs_extra32.default.stat(dest)).size;
|
|
732699
732744
|
}
|
|
732700
|
-
|
|
732745
|
+
/**
|
|
732746
|
+
* Upload the local cache of `node` to Hubdoc — either as a new document
|
|
732747
|
+
* (POST) or as a new version of an existing one (PATCH). Files above
|
|
732748
|
+
* `CHUNKED_THRESHOLD` go through `ChunkedUploader` so large Excel/PPT
|
|
732749
|
+
* workbooks don't stall the multipart POST.
|
|
732750
|
+
*/
|
|
732751
|
+
async uploadNode(node, mode) {
|
|
732752
|
+
if (node.localOnly) return;
|
|
732701
732753
|
const local = this.cacheFileFor(node);
|
|
732754
|
+
const size = (await import_fs_extra32.default.stat(local)).size;
|
|
732755
|
+
const contentType2 = mimeForFile(node.name);
|
|
732756
|
+
if (mode === "update" && !node.id) {
|
|
732757
|
+
mode = "create";
|
|
732758
|
+
}
|
|
732759
|
+
if (mode === "create" && size >= CHUNKED_THRESHOLD2) {
|
|
732760
|
+
const uploader = new ChunkedUploader2(this.ctx.baseUrl, this.ctx.token);
|
|
732761
|
+
const result = await uploader.uploadFile({
|
|
732762
|
+
filePath: local,
|
|
732763
|
+
workspaceId: node.workspaceId,
|
|
732764
|
+
folderId: node.folderId
|
|
732765
|
+
});
|
|
732766
|
+
if (!result.success) throw new Error(`Chunked upload failed: ${result.error || "unknown error"}`);
|
|
732767
|
+
const form2 = new import_form_data5.default();
|
|
732768
|
+
form2.append("chunked_upload_id", result.uploadId);
|
|
732769
|
+
if (node.folderId) form2.append("documents_folder_id", node.folderId);
|
|
732770
|
+
else if (node.workspaceId) form2.append("workspace_id", node.workspaceId);
|
|
732771
|
+
const res = await this.ctx.axios.post("/api/v1/documents/documents", form2, {
|
|
732772
|
+
headers: form2.getHeaders(),
|
|
732773
|
+
maxBodyLength: Infinity,
|
|
732774
|
+
maxContentLength: Infinity
|
|
732775
|
+
});
|
|
732776
|
+
const created = res.data?.document ?? res.data;
|
|
732777
|
+
if (created?.id) node.id = created.id;
|
|
732778
|
+
node.isNew = false;
|
|
732779
|
+
node.size = size;
|
|
732780
|
+
return;
|
|
732781
|
+
}
|
|
732702
732782
|
const form = new import_form_data5.default();
|
|
732703
732783
|
form.append("uploaded_file", import_fs_extra32.default.createReadStream(local), {
|
|
732704
732784
|
filename: node.name,
|
|
732705
|
-
contentType:
|
|
732785
|
+
contentType: contentType2
|
|
732706
732786
|
});
|
|
732707
|
-
if (
|
|
732708
|
-
|
|
732709
|
-
|
|
732710
|
-
|
|
732711
|
-
|
|
732712
|
-
|
|
732713
|
-
|
|
732714
|
-
|
|
732715
|
-
|
|
732716
|
-
|
|
732717
|
-
|
|
732718
|
-
|
|
732719
|
-
|
|
732720
|
-
|
|
732787
|
+
if (mode === "create") {
|
|
732788
|
+
if (node.folderId) form.append("documents_folder_id", node.folderId);
|
|
732789
|
+
else if (node.workspaceId) form.append("workspace_id", node.workspaceId);
|
|
732790
|
+
if (node.name.toLowerCase().endsWith(".md")) {
|
|
732791
|
+
this.log(`\u26A0\uFE0F ${node.name}: write-back markdown \u2014 voir caveat collab #508`);
|
|
732792
|
+
}
|
|
732793
|
+
const res = await this.ctx.axios.post("/api/v1/documents/documents", form, {
|
|
732794
|
+
headers: form.getHeaders(),
|
|
732795
|
+
maxBodyLength: Infinity,
|
|
732796
|
+
maxContentLength: Infinity
|
|
732797
|
+
});
|
|
732798
|
+
const created = res.data?.document ?? res.data;
|
|
732799
|
+
if (created?.id) node.id = created.id;
|
|
732800
|
+
node.isNew = false;
|
|
732801
|
+
} else {
|
|
732802
|
+
await this.ctx.axios.patch(`/api/v1/documents/documents/${node.id}`, form, {
|
|
732803
|
+
headers: form.getHeaders(),
|
|
732804
|
+
maxBodyLength: Infinity,
|
|
732805
|
+
maxContentLength: Infinity
|
|
732806
|
+
});
|
|
732807
|
+
}
|
|
732808
|
+
node.size = size;
|
|
732809
|
+
}
|
|
732810
|
+
async uploadNew(node) {
|
|
732811
|
+
await this.uploadNode(node, "create");
|
|
732721
732812
|
}
|
|
732722
732813
|
async uploadUpdate(node) {
|
|
732723
|
-
|
|
732724
|
-
const form = new import_form_data5.default();
|
|
732725
|
-
form.append("uploaded_file", import_fs_extra32.default.createReadStream(local), {
|
|
732726
|
-
filename: node.name,
|
|
732727
|
-
contentType: "application/octet-stream"
|
|
732728
|
-
});
|
|
732729
|
-
await this.ctx.axios.patch(`/api/v1/documents/documents/${node.id}`, form, {
|
|
732730
|
-
headers: form.getHeaders(),
|
|
732731
|
-
maxBodyLength: Infinity,
|
|
732732
|
-
maxContentLength: Infinity
|
|
732733
|
-
});
|
|
732734
|
-
node.size = (await import_fs_extra32.default.stat(local)).size;
|
|
732814
|
+
await this.uploadNode(node, "update");
|
|
732735
732815
|
}
|
|
732736
732816
|
invalidateParent(p3) {
|
|
732737
732817
|
const parent = this.nodes.get(parentPath(p3));
|
|
732738
732818
|
if (parent) parent.loadedAt = 0;
|
|
732739
732819
|
}
|
|
732740
732820
|
// --- FUSE ops ------------------------------------------------------------
|
|
732821
|
+
/**
|
|
732822
|
+
* Public accessor for the FUSE ops object. Used by the mount lifecycle
|
|
732823
|
+
* to hand the ops to the native binding, and by the test suite to drive
|
|
732824
|
+
* the filesystem without loading the native binding. Callers must first
|
|
732825
|
+
* install a compatible error-code source via `setFuseBinding()` (the
|
|
732826
|
+
* mount path does this via require('@cocalc/fuse-native')).
|
|
732827
|
+
*/
|
|
732828
|
+
getOps() {
|
|
732829
|
+
if (!this.fuse) {
|
|
732830
|
+
throw new Error("FUSE binding not set \u2014 call setFuseBinding() or mount() first");
|
|
732831
|
+
}
|
|
732832
|
+
return this.ops();
|
|
732833
|
+
}
|
|
732834
|
+
/** Inject a FUSE-like error-code source. Exposed for tests. */
|
|
732835
|
+
setFuseBinding(binding) {
|
|
732836
|
+
this.fuse = binding;
|
|
732837
|
+
}
|
|
732741
732838
|
ops() {
|
|
732742
732839
|
const Fuse = this.fuse;
|
|
732743
732840
|
const ro = !!this.opts.readOnly;
|
|
@@ -732781,7 +732878,18 @@ var HubdocFs = class {
|
|
|
732781
732878
|
if (node.kind !== "file") return cb(Fuse.EISDIR);
|
|
732782
732879
|
const write = (flags & 3) !== 0;
|
|
732783
732880
|
if (write && ro) return cb(Fuse.EACCES);
|
|
732784
|
-
|
|
732881
|
+
const truncOnOpen = (flags & import_fs3.constants.O_TRUNC) !== 0;
|
|
732882
|
+
if (truncOnOpen && !node.localOnly && !ro) {
|
|
732883
|
+
await import_fs_extra32.default.writeFile(this.cacheFileFor(node), Buffer.alloc(0));
|
|
732884
|
+
node.downloaded = true;
|
|
732885
|
+
node.dirty = true;
|
|
732886
|
+
node.size = 0;
|
|
732887
|
+
} else if (node.id && !node.localOnly) {
|
|
732888
|
+
await this.download(node);
|
|
732889
|
+
} else if (!node.cachePath || !await import_fs_extra32.default.pathExists(node.cachePath)) {
|
|
732890
|
+
await import_fs_extra32.default.writeFile(this.cacheFileFor(node), Buffer.alloc(0));
|
|
732891
|
+
node.downloaded = true;
|
|
732892
|
+
}
|
|
732785
732893
|
const localFd = await import_fs_extra32.default.open(this.cacheFileFor(node), "r+").catch(async () => import_fs_extra32.default.open(this.cacheFileFor(node), "w+"));
|
|
732786
732894
|
const fd = this.nextFd++;
|
|
732787
732895
|
this.handles.set(fd, { node, localFd, write });
|
|
@@ -732827,7 +732935,10 @@ var HubdocFs = class {
|
|
|
732827
732935
|
mtime: /* @__PURE__ */ new Date(),
|
|
732828
732936
|
isNew: true,
|
|
732829
732937
|
dirty: true,
|
|
732830
|
-
downloaded: true
|
|
732938
|
+
downloaded: true,
|
|
732939
|
+
// Office / LibreOffice lockfiles must never be pushed to the server —
|
|
732940
|
+
// they are pure kernel-side artefacts of an in-progress save.
|
|
732941
|
+
localOnly: isOfficeLockfile(name)
|
|
732831
732942
|
};
|
|
732832
732943
|
await import_fs_extra32.default.writeFile(this.cacheFileFor(node), Buffer.alloc(0));
|
|
732833
732944
|
this.nodes.set(p3, node);
|
|
@@ -732845,11 +732956,14 @@ var HubdocFs = class {
|
|
|
732845
732956
|
await import_fs_extra32.default.close(h3.localFd);
|
|
732846
732957
|
} catch {
|
|
732847
732958
|
}
|
|
732848
|
-
if (h3.node.dirty && !ro) {
|
|
732959
|
+
if (h3.node.dirty && !ro && !h3.node.localOnly) {
|
|
732849
732960
|
if (h3.node.isNew || !h3.node.id) await this.uploadNew(h3.node);
|
|
732850
732961
|
else await this.uploadUpdate(h3.node);
|
|
732851
732962
|
h3.node.dirty = false;
|
|
732852
732963
|
h3.node.mtime = /* @__PURE__ */ new Date();
|
|
732964
|
+
} else if (h3.node.dirty && h3.node.localOnly) {
|
|
732965
|
+
h3.node.dirty = false;
|
|
732966
|
+
h3.node.mtime = /* @__PURE__ */ new Date();
|
|
732853
732967
|
}
|
|
732854
732968
|
cb(0);
|
|
732855
732969
|
}),
|
|
@@ -732859,7 +732973,9 @@ var HubdocFs = class {
|
|
|
732859
732973
|
if (ro) return cb(Fuse.EACCES);
|
|
732860
732974
|
const node = await this.resolve(p3);
|
|
732861
732975
|
if (!node || node.kind !== "file") return cb(Fuse.ENOENT);
|
|
732862
|
-
if (node.id
|
|
732976
|
+
if (node.id && !node.localOnly) {
|
|
732977
|
+
await this.ctx.axios.delete(`/api/v1/documents/documents/${node.id}`);
|
|
732978
|
+
}
|
|
732863
732979
|
if (node.cachePath) await import_fs_extra32.default.remove(node.cachePath).catch(() => {
|
|
732864
732980
|
});
|
|
732865
732981
|
this.nodes.delete(p3);
|
|
@@ -732907,6 +733023,56 @@ var HubdocFs = class {
|
|
|
732907
733023
|
if (!newParent || newParent.kind !== "dir" || newParent.hub === "root") return cb(Fuse.EPERM);
|
|
732908
733024
|
const newName = import_path34.default.basename(dest);
|
|
732909
733025
|
const newFolderId = newParent.hub === "folder" ? newParent.folderId : null;
|
|
733026
|
+
if (node.localOnly) {
|
|
733027
|
+
this.nodes.delete(src);
|
|
733028
|
+
node.name = newName;
|
|
733029
|
+
node.folderId = node.kind === "file" ? newFolderId ?? void 0 : node.folderId;
|
|
733030
|
+
node.workspaceId = newParent.workspaceId;
|
|
733031
|
+
this.nodes.set(dest, node);
|
|
733032
|
+
this.invalidateParent(src);
|
|
733033
|
+
this.invalidateParent(dest);
|
|
733034
|
+
return cb(0);
|
|
733035
|
+
}
|
|
733036
|
+
let destNode = null;
|
|
733037
|
+
try {
|
|
733038
|
+
destNode = await this.resolve(dest);
|
|
733039
|
+
} catch {
|
|
733040
|
+
}
|
|
733041
|
+
if (node.kind === "file" && !node.id) {
|
|
733042
|
+
if (destNode && destNode.kind === "file" && destNode.id) {
|
|
733043
|
+
const sourceCache = this.cacheFileFor(node);
|
|
733044
|
+
const local = this.cacheFileFor(destNode);
|
|
733045
|
+
await import_fs_extra32.default.copy(sourceCache, local, { overwrite: true });
|
|
733046
|
+
destNode.dirty = true;
|
|
733047
|
+
destNode.downloaded = true;
|
|
733048
|
+
destNode.name = newName;
|
|
733049
|
+
destNode.folderId = newFolderId ?? void 0;
|
|
733050
|
+
destNode.workspaceId = newParent.workspaceId;
|
|
733051
|
+
await this.uploadNode(destNode, "update");
|
|
733052
|
+
destNode.dirty = false;
|
|
733053
|
+
destNode.mtime = /* @__PURE__ */ new Date();
|
|
733054
|
+
this.nodes.delete(src);
|
|
733055
|
+
await import_fs_extra32.default.remove(sourceCache).catch(() => {
|
|
733056
|
+
});
|
|
733057
|
+
this.invalidateParent(src);
|
|
733058
|
+
this.invalidateParent(dest);
|
|
733059
|
+
return cb(0);
|
|
733060
|
+
}
|
|
733061
|
+
node.name = newName;
|
|
733062
|
+
node.folderId = newFolderId ?? void 0;
|
|
733063
|
+
node.workspaceId = newParent.workspaceId;
|
|
733064
|
+
await this.uploadNew(node);
|
|
733065
|
+
this.nodes.delete(src);
|
|
733066
|
+
this.nodes.set(dest, node);
|
|
733067
|
+
this.invalidateParent(src);
|
|
733068
|
+
this.invalidateParent(dest);
|
|
733069
|
+
return cb(0);
|
|
733070
|
+
}
|
|
733071
|
+
if (destNode && destNode !== node && destNode.kind === "file" && destNode.id) {
|
|
733072
|
+
await this.ctx.axios.delete(`/api/v1/documents/documents/${destNode.id}`).catch(() => {
|
|
733073
|
+
});
|
|
733074
|
+
this.nodes.delete(dest);
|
|
733075
|
+
}
|
|
732910
733076
|
const resource = node.kind === "file" ? "documents" : "folders";
|
|
732911
733077
|
if (!node.id) return cb(Fuse.EPERM);
|
|
732912
733078
|
await this.ctx.axios.patch(`/api/v1/documents/${resource}/${node.id}`, {
|
|
@@ -732940,7 +733106,7 @@ var HubdocFs = class {
|
|
|
732940
733106
|
}
|
|
732941
733107
|
this.fuse = Fuse;
|
|
732942
733108
|
await import_fs_extra32.default.ensureDir(mountPath);
|
|
732943
|
-
const fuse = new Fuse(mountPath, this.
|
|
733109
|
+
const fuse = new Fuse(mountPath, this.getOps(), {
|
|
732944
733110
|
force: true,
|
|
732945
733111
|
mkdir: true,
|
|
732946
733112
|
displayFolder: "Hubdoc",
|
|
@@ -733760,7 +733926,7 @@ async function handlePermissionsShow(id, opts) {
|
|
|
733760
733926
|
// apps/cli/cli.ts
|
|
733761
733927
|
var getVersion = () => {
|
|
733762
733928
|
try {
|
|
733763
|
-
if (true) return "1.8.
|
|
733929
|
+
if (true) return "1.8.2";
|
|
733764
733930
|
} catch {
|
|
733765
733931
|
}
|
|
733766
733932
|
for (const candidate of [
|