@fluid-tools/fetch-tool 2.0.0-dev-rc.2.0.0.246488 → 2.0.0-dev-rc.3.0.0.253463

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.
Files changed (58) hide show
  1. package/dist/fluidAnalyzeMessages.js +22 -19
  2. package/dist/fluidAnalyzeMessages.js.map +1 -1
  3. package/dist/fluidFetch.js +18 -18
  4. package/dist/fluidFetch.js.map +1 -1
  5. package/dist/fluidFetchInit.d.ts +1 -1
  6. package/dist/fluidFetchInit.d.ts.map +1 -1
  7. package/dist/fluidFetchInit.js +15 -15
  8. package/dist/fluidFetchInit.js.map +1 -1
  9. package/dist/fluidFetchMessages.d.ts +1 -1
  10. package/dist/fluidFetchMessages.d.ts.map +1 -1
  11. package/dist/fluidFetchMessages.js +13 -13
  12. package/dist/fluidFetchMessages.js.map +1 -1
  13. package/dist/fluidFetchSharePoint.d.ts +1 -1
  14. package/dist/fluidFetchSharePoint.d.ts.map +1 -1
  15. package/dist/fluidFetchSharePoint.js +6 -6
  16. package/dist/fluidFetchSharePoint.js.map +1 -1
  17. package/dist/fluidFetchSnapshot.d.ts +1 -1
  18. package/dist/fluidFetchSnapshot.d.ts.map +1 -1
  19. package/dist/fluidFetchSnapshot.js +20 -20
  20. package/dist/fluidFetchSnapshot.js.map +1 -1
  21. package/dist/package.json +3 -0
  22. package/lib/fluidAnalyzeMessages.d.ts +8 -0
  23. package/lib/fluidAnalyzeMessages.d.ts.map +1 -0
  24. package/lib/fluidAnalyzeMessages.js +629 -0
  25. package/lib/fluidAnalyzeMessages.js.map +1 -0
  26. package/lib/fluidFetch.d.ts +6 -0
  27. package/lib/fluidFetch.d.ts.map +1 -0
  28. package/lib/fluidFetch.js +120 -0
  29. package/lib/fluidFetch.js.map +1 -0
  30. package/lib/fluidFetchArgs.d.ts +31 -0
  31. package/lib/fluidFetchArgs.d.ts.map +1 -0
  32. package/lib/fluidFetchArgs.js +192 -0
  33. package/lib/fluidFetchArgs.js.map +1 -0
  34. package/lib/fluidFetchInit.d.ts +8 -0
  35. package/lib/fluidFetchInit.d.ts.map +1 -0
  36. package/lib/fluidFetchInit.js +117 -0
  37. package/lib/fluidFetchInit.js.map +1 -0
  38. package/lib/fluidFetchMessages.d.ts +7 -0
  39. package/lib/fluidFetchMessages.d.ts.map +1 -0
  40. package/lib/fluidFetchMessages.js +225 -0
  41. package/lib/fluidFetchMessages.js.map +1 -0
  42. package/lib/fluidFetchSharePoint.d.ts +9 -0
  43. package/lib/fluidFetchSharePoint.d.ts.map +1 -0
  44. package/lib/fluidFetchSharePoint.js +100 -0
  45. package/lib/fluidFetchSharePoint.js.map +1 -0
  46. package/lib/fluidFetchSnapshot.d.ts +7 -0
  47. package/lib/fluidFetchSnapshot.d.ts.map +1 -0
  48. package/lib/fluidFetchSnapshot.js +261 -0
  49. package/lib/fluidFetchSnapshot.js.map +1 -0
  50. package/package.json +26 -26
  51. package/src/fluidAnalyzeMessages.ts +13 -10
  52. package/src/fluidFetch.ts +8 -6
  53. package/src/fluidFetchInit.ts +13 -9
  54. package/src/fluidFetchMessages.ts +8 -6
  55. package/src/fluidFetchSharePoint.ts +10 -8
  56. package/src/fluidFetchSnapshot.ts +7 -4
  57. package/tsconfig.cjs.json +7 -0
  58. package/tsconfig.json +2 -5
@@ -0,0 +1,261 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ import fs from "fs";
6
+ import util from "util";
7
+ import { bufferToString, stringToBuffer } from "@fluid-internal/client-utils";
8
+ import { formatNumber } from "./fluidAnalyzeMessages.js";
9
+ import { dumpSnapshotStats, dumpSnapshotTrees, dumpSnapshotVersions, paramActualFormatting, paramNumSnapshotVersions, paramSnapshotVersionIndex, } from "./fluidFetchArgs.js";
10
+ import { latestVersionsId } from "./fluidFetchInit.js";
11
+ function isFetchedTree(fetchedData) {
12
+ return "patched" in fetchedData;
13
+ }
14
+ const blobCache = new Map();
15
+ let blobCachePrevious = new Map();
16
+ let blobCacheCurrent = new Map();
17
+ function fetchBlobs(prefix, tree, storage, blobIdMap) {
18
+ const result = [];
19
+ for (const item of Object.keys(tree.blobs)) {
20
+ const treePath = `${prefix}${item}`;
21
+ const blobId = tree.blobs[item];
22
+ if (blobId !== null) {
23
+ let reused = true;
24
+ let blob = blobCachePrevious.get(blobId);
25
+ if (!blob) {
26
+ reused = false;
27
+ blob = blobCache.get(blobId);
28
+ if (blob === undefined) {
29
+ blob = storage.readBlob(blobId);
30
+ blobCache.set(blobId, blob);
31
+ }
32
+ }
33
+ blobCacheCurrent.set(blobId, blob);
34
+ // Use the blobIdMap to assign a number for each unique blob
35
+ // and use it as a prefix for files to avoid case-insensitive fs
36
+ let index = blobIdMap.get(blobId);
37
+ if (index === undefined) {
38
+ index = blobIdMap.size;
39
+ blobIdMap.set(blobId, index);
40
+ }
41
+ const filename = `${index}-${blobId}`;
42
+ result.push({ treePath, blobId, blob, reused, filename });
43
+ // patch the tree so that we can write it out to reference the file
44
+ tree.blobs[item] = filename;
45
+ }
46
+ }
47
+ return result;
48
+ }
49
+ function createTreeBlob(tree, prefix, patched) {
50
+ const id = tree.id ?? "original";
51
+ const blob = stringToBuffer(JSON.stringify(tree), "utf8");
52
+ const filename = patched ? "tree" : `tree-${id}`;
53
+ const treePath = `${prefix}${filename}`;
54
+ return { treePath, blobId: "original tree $id", filename, blob, patched, reused: false };
55
+ }
56
+ async function fetchBlobsFromSnapshotTree(storage, tree, prefix = "/", parentBlobIdMap) {
57
+ const isTopLevel = !parentBlobIdMap;
58
+ if (isTopLevel && dumpSnapshotTrees) {
59
+ console.log(tree);
60
+ }
61
+ if (prefix === "/") {
62
+ blobCachePrevious = blobCacheCurrent;
63
+ blobCacheCurrent = new Map();
64
+ }
65
+ // Create the tree info before fetching blobs (which will modify it)
66
+ let topLevelBlob;
67
+ if (isTopLevel) {
68
+ topLevelBlob = createTreeBlob(tree, prefix, false);
69
+ }
70
+ const blobIdMap = parentBlobIdMap ?? new Map();
71
+ let result = fetchBlobs(prefix, tree, storage, blobIdMap);
72
+ for (const subtreeId of Object.keys(tree.trees)) {
73
+ const subtree = tree.trees[subtreeId];
74
+ const dataStoreBlobs = await fetchBlobsFromSnapshotTree(storage, subtree, `${prefix}${subtreeId}/`, blobIdMap);
75
+ result = result.concat(dataStoreBlobs);
76
+ }
77
+ if (topLevelBlob) {
78
+ result.push(topLevelBlob);
79
+ result.push(createTreeBlob(tree, prefix, true));
80
+ }
81
+ return result;
82
+ }
83
+ function getDumpFetchedData(fetchedData) {
84
+ const sorted = fetchedData.sort((a, b) => a.treePath.localeCompare(b.treePath));
85
+ return sorted.filter((item) => !isFetchedTree(item) || !item.patched);
86
+ }
87
+ async function dumpSnapshotTreeVerbose(name, fetchedData) {
88
+ let size = 0;
89
+ const sorted = getDumpFetchedData(fetchedData);
90
+ let nameLength = 10;
91
+ for (const item of sorted) {
92
+ nameLength = Math.max(nameLength, item.treePath.length);
93
+ }
94
+ console.log("");
95
+ console.log(`${"Blob Path".padEnd(nameLength)} | Reused | Bytes`);
96
+ console.log("-".repeat(nameLength + 26));
97
+ for (const item of sorted) {
98
+ const buffer = await item.blob;
99
+ if (buffer === undefined) {
100
+ continue;
101
+ }
102
+ const blob = bufferToString(buffer, "utf8");
103
+ console.log(`${item.treePath.padEnd(nameLength)} | ${item.reused ? "X" : " "} | ${formatNumber(blob.length).padStart(10)}`);
104
+ size += blob.length;
105
+ }
106
+ console.log("-".repeat(nameLength + 26));
107
+ console.log(`${"Total snapshot size".padEnd(nameLength)} | | ${formatNumber(size).padStart(10)}`);
108
+ }
109
+ async function dumpSnapshotTree(name, fetchedData) {
110
+ let size = 0;
111
+ let sizeNew = 0;
112
+ let blobCountNew = 0;
113
+ const sorted = getDumpFetchedData(fetchedData);
114
+ for (const item of sorted) {
115
+ const buffer = await item.blob;
116
+ if (buffer === undefined) {
117
+ continue;
118
+ }
119
+ const blob = bufferToString(buffer, "utf8");
120
+ if (!item.reused) {
121
+ sizeNew += blob.length;
122
+ blobCountNew++;
123
+ }
124
+ size += blob.length;
125
+ }
126
+ return { blobCountNew, blobCount: sorted.length, size, sizeNew };
127
+ }
128
+ async function saveSnapshot(name, fetchedData, saveDir) {
129
+ const outDir = `${saveDir}/${name}/`;
130
+ const mkdir = util.promisify(fs.mkdir);
131
+ await mkdir(`${outDir}/decoded`, { recursive: true });
132
+ await Promise.all(fetchedData.map(async (item) => {
133
+ const buffer = await item.blob;
134
+ if (buffer === undefined) {
135
+ console.error(`ERROR: Unable to get data for blob ${item.blobId}`);
136
+ return;
137
+ }
138
+ if (!isFetchedTree(item)) {
139
+ // Just write the data as is.
140
+ fs.writeFileSync(`${outDir}/${item.filename}`, Buffer.from(buffer));
141
+ // we assume that the buffer is utf8 here, which currently is true for
142
+ // all of our snapshot blobs. It doesn't necessary be true in the future
143
+ let decoded = bufferToString(buffer, "utf8");
144
+ try {
145
+ if (!paramActualFormatting) {
146
+ decoded = JSON.stringify(JSON.parse(decoded), undefined, 2);
147
+ }
148
+ }
149
+ catch (e) { }
150
+ fs.writeFileSync(`${outDir}/decoded/${item.filename}.json`, decoded);
151
+ }
152
+ else {
153
+ // Write out same data for tree decoded or not, except for formatting
154
+ const treeString = bufferToString(buffer, "utf8");
155
+ fs.writeFileSync(`${outDir}/${item.filename}.json`, treeString);
156
+ fs.writeFileSync(`${outDir}/decoded/${item.filename}.json`, paramActualFormatting
157
+ ? treeString
158
+ : JSON.stringify(JSON.parse(treeString), undefined, 2));
159
+ }
160
+ }));
161
+ }
162
+ async function fetchBlobsFromVersion(storage, version) {
163
+ const tree = await reportErrors(`getSnapshotTree ${version.id}`, storage.getSnapshotTree(version));
164
+ if (!tree) {
165
+ throw new Error("Failed to load snapshot tree");
166
+ }
167
+ return fetchBlobsFromSnapshotTree(storage, tree);
168
+ }
169
+ async function reportErrors(message, res) {
170
+ try {
171
+ return await res;
172
+ }
173
+ catch (error) {
174
+ console.error(`Error calling ${message}`);
175
+ throw error;
176
+ }
177
+ }
178
+ export async function fluidFetchSnapshot(documentService, saveDir) {
179
+ if (!dumpSnapshotStats &&
180
+ !dumpSnapshotTrees &&
181
+ !dumpSnapshotVersions &&
182
+ saveDir === undefined) {
183
+ return;
184
+ }
185
+ // --local mode - do not connect to storage.
186
+ // For now, bail out early.
187
+ // In future, separate download from analyzes parts and allow offline analyzes
188
+ if (!documentService) {
189
+ return;
190
+ }
191
+ console.log("\n");
192
+ const storage = await documentService.connectToStorage();
193
+ let version;
194
+ const versions = await reportErrors(`getVersions ${latestVersionsId}`, storage.getVersions(latestVersionsId, paramNumSnapshotVersions));
195
+ if (dumpSnapshotVersions) {
196
+ console.log("Snapshot versions");
197
+ console.log(versions);
198
+ }
199
+ let blobsToDump;
200
+ if (paramSnapshotVersionIndex !== undefined) {
201
+ version = versions[paramSnapshotVersionIndex];
202
+ if (version === undefined) {
203
+ console.log(`There are only ${versions.length} snapshots, --snapshotVersionIndex is too large`);
204
+ return;
205
+ }
206
+ if (saveDir !== undefined) {
207
+ blobsToDump = await fetchBlobsFromVersion(storage, version);
208
+ const name = version.id;
209
+ console.log(`Saving snapshot ${name}`);
210
+ await saveSnapshot(name, blobsToDump, saveDir);
211
+ }
212
+ }
213
+ else {
214
+ version = versions[0];
215
+ if (saveDir !== undefined && versions.length > 0) {
216
+ console.log(" Name | Date | Size | New Size | Blobs | New Blobs");
217
+ console.log("-".repeat(86));
218
+ // Go in reverse order, to correctly calculate blob reuse - from oldest to newest snapshots
219
+ for (let i = versions.length - 1; i >= 0; i--) {
220
+ const v = versions[i];
221
+ const blobs = await fetchBlobsFromVersion(storage, v);
222
+ blobsToDump = blobs;
223
+ const name = `${i}-${v.id}`;
224
+ const res = await dumpSnapshotTree(name, blobs);
225
+ let date = "";
226
+ if (v.date !== undefined) {
227
+ try {
228
+ date = new Date(v.date).toLocaleString();
229
+ }
230
+ catch (e) {
231
+ date = v.date.replace("T", " ");
232
+ const index = date.lastIndexOf(".");
233
+ if (index > 0) {
234
+ date = `${date.substr(0, index)} Z`;
235
+ }
236
+ }
237
+ }
238
+ date = date.padStart(23);
239
+ const size = formatNumber(res.size).padStart(10);
240
+ const sizeNew = formatNumber(res.sizeNew).padStart(10);
241
+ const blobCount = formatNumber(res.blobCount).padStart(6);
242
+ const blobCountNew = formatNumber(res.blobCountNew).padStart(9);
243
+ console.log(`${name.padEnd(15)} | ${date} | ${size} | ${sizeNew} | ${blobCount} | ${blobCountNew}`);
244
+ await saveSnapshot(name, blobs, saveDir);
245
+ }
246
+ }
247
+ }
248
+ if (dumpSnapshotStats || dumpSnapshotTrees) {
249
+ if (version === undefined) {
250
+ console.log("No snapshot tree");
251
+ }
252
+ else {
253
+ if (blobsToDump === undefined) {
254
+ blobsToDump = await fetchBlobsFromVersion(storage, version);
255
+ }
256
+ console.log(`\n\nSnapshot version ${version.id}`);
257
+ await dumpSnapshotTreeVerbose(version.id, blobsToDump);
258
+ }
259
+ }
260
+ }
261
+ //# sourceMappingURL=fluidFetchSnapshot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fluidFetchSnapshot.js","sourceRoot":"","sources":["../src/fluidFetchSnapshot.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport fs from \"fs\";\nimport util from \"util\";\n\nimport { bufferToString, stringToBuffer } from \"@fluid-internal/client-utils\";\nimport {\n\tIDocumentService,\n\tIDocumentStorageService,\n} from \"@fluidframework/driver-definitions/internal\";\nimport { ISnapshotTree, IVersion } from \"@fluidframework/protocol-definitions\";\n\nimport { formatNumber } from \"./fluidAnalyzeMessages.js\";\nimport {\n\tdumpSnapshotStats,\n\tdumpSnapshotTrees,\n\tdumpSnapshotVersions,\n\tparamActualFormatting,\n\tparamNumSnapshotVersions,\n\tparamSnapshotVersionIndex,\n} from \"./fluidFetchArgs.js\";\nimport { latestVersionsId } from \"./fluidFetchInit.js\";\n\ninterface ISnapshotInfo {\n\tblobCountNew: number;\n\tblobCount: number;\n\tsize: number;\n\tsizeNew: number;\n}\n\ntype IFetchedData = IFetchedBlob | IFetchedTree;\n\ninterface IFetchedBlob {\n\ttreePath: string;\n\tfilename: string;\n\tblobId: string;\n\tblob: Promise<ArrayBufferLike | undefined>;\n\treused: boolean;\n}\n\ninterface IFetchedTree {\n\ttreePath: string;\n\tblobId: string;\n\tfilename: string;\n\tblob: ArrayBufferLike;\n\n\treused: false;\n\n\tpatched: boolean;\n}\n\nfunction isFetchedTree(fetchedData: IFetchedData): fetchedData is IFetchedTree {\n\treturn \"patched\" in fetchedData;\n}\n\nconst blobCache = new Map<string, Promise<ArrayBufferLike>>();\nlet blobCachePrevious = new Map<string, Promise<ArrayBufferLike>>();\nlet blobCacheCurrent = new Map<string, Promise<ArrayBufferLike>>();\n\nfunction fetchBlobs(\n\tprefix: string,\n\ttree: ISnapshotTree,\n\tstorage: IDocumentStorageService,\n\tblobIdMap: Map<string, number>,\n) {\n\tconst result: IFetchedBlob[] = [];\n\tfor (const item of Object.keys(tree.blobs)) {\n\t\tconst treePath = `${prefix}${item}`;\n\t\tconst blobId = tree.blobs[item];\n\t\tif (blobId !== null) {\n\t\t\tlet reused = true;\n\t\t\tlet blob = blobCachePrevious.get(blobId);\n\t\t\tif (!blob) {\n\t\t\t\treused = false;\n\t\t\t\tblob = blobCache.get(blobId);\n\t\t\t\tif (blob === undefined) {\n\t\t\t\t\tblob = storage.readBlob(blobId);\n\t\t\t\t\tblobCache.set(blobId, blob);\n\t\t\t\t}\n\t\t\t}\n\t\t\tblobCacheCurrent.set(blobId, blob);\n\n\t\t\t// Use the blobIdMap to assign a number for each unique blob\n\t\t\t// and use it as a prefix for files to avoid case-insensitive fs\n\t\t\tlet index = blobIdMap.get(blobId);\n\t\t\tif (index === undefined) {\n\t\t\t\tindex = blobIdMap.size;\n\t\t\t\tblobIdMap.set(blobId, index);\n\t\t\t}\n\t\t\tconst filename = `${index}-${blobId}`;\n\t\t\tresult.push({ treePath, blobId, blob, reused, filename });\n\n\t\t\t// patch the tree so that we can write it out to reference the file\n\t\t\ttree.blobs[item] = filename;\n\t\t}\n\t}\n\treturn result;\n}\n\nfunction createTreeBlob(tree: ISnapshotTree, prefix: string, patched: boolean): IFetchedTree {\n\tconst id = tree.id ?? \"original\";\n\tconst blob = stringToBuffer(JSON.stringify(tree), \"utf8\");\n\tconst filename = patched ? \"tree\" : `tree-${id}`;\n\tconst treePath = `${prefix}${filename}`;\n\treturn { treePath, blobId: \"original tree $id\", filename, blob, patched, reused: false };\n}\n\nasync function fetchBlobsFromSnapshotTree(\n\tstorage: IDocumentStorageService,\n\ttree: ISnapshotTree,\n\tprefix: string = \"/\",\n\tparentBlobIdMap?: Map<string, number>,\n): Promise<IFetchedData[]> {\n\tconst isTopLevel = !parentBlobIdMap;\n\tif (isTopLevel && dumpSnapshotTrees) {\n\t\tconsole.log(tree);\n\t}\n\n\tif (prefix === \"/\") {\n\t\tblobCachePrevious = blobCacheCurrent;\n\t\tblobCacheCurrent = new Map<string, Promise<ArrayBufferLike>>();\n\t}\n\n\t// Create the tree info before fetching blobs (which will modify it)\n\tlet topLevelBlob: IFetchedTree | undefined;\n\tif (isTopLevel) {\n\t\ttopLevelBlob = createTreeBlob(tree, prefix, false);\n\t}\n\n\tconst blobIdMap = parentBlobIdMap ?? new Map<string, number>();\n\tlet result: IFetchedData[] = fetchBlobs(prefix, tree, storage, blobIdMap);\n\n\tfor (const subtreeId of Object.keys(tree.trees)) {\n\t\tconst subtree = tree.trees[subtreeId];\n\t\tconst dataStoreBlobs = await fetchBlobsFromSnapshotTree(\n\t\t\tstorage,\n\t\t\tsubtree,\n\t\t\t`${prefix}${subtreeId}/`,\n\t\t\tblobIdMap,\n\t\t);\n\t\tresult = result.concat(dataStoreBlobs);\n\t}\n\n\tif (topLevelBlob) {\n\t\tresult.push(topLevelBlob);\n\t\tresult.push(createTreeBlob(tree, prefix, true));\n\t}\n\treturn result;\n}\n\nfunction getDumpFetchedData(fetchedData: IFetchedData[]) {\n\tconst sorted = fetchedData.sort((a, b) => a.treePath.localeCompare(b.treePath));\n\treturn sorted.filter((item) => !isFetchedTree(item) || !item.patched);\n}\n\nasync function dumpSnapshotTreeVerbose(name: string, fetchedData: IFetchedData[]) {\n\tlet size = 0;\n\tconst sorted = getDumpFetchedData(fetchedData);\n\n\tlet nameLength = 10;\n\tfor (const item of sorted) {\n\t\tnameLength = Math.max(nameLength, item.treePath.length);\n\t}\n\n\tconsole.log(\"\");\n\tconsole.log(`${\"Blob Path\".padEnd(nameLength)} | Reused | Bytes`);\n\tconsole.log(\"-\".repeat(nameLength + 26));\n\tfor (const item of sorted) {\n\t\tconst buffer = await item.blob;\n\t\tif (buffer === undefined) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst blob = bufferToString(buffer, \"utf8\");\n\t\tconsole.log(\n\t\t\t`${item.treePath.padEnd(nameLength)} | ${item.reused ? \"X\" : \" \"} | ${formatNumber(\n\t\t\t\tblob.length,\n\t\t\t).padStart(10)}`,\n\t\t);\n\t\tsize += blob.length;\n\t}\n\n\tconsole.log(\"-\".repeat(nameLength + 26));\n\tconsole.log(\n\t\t`${\"Total snapshot size\".padEnd(nameLength)} | | ${formatNumber(size).padStart(10)}`,\n\t);\n}\n\nasync function dumpSnapshotTree(name: string, fetchedData: IFetchedData[]): Promise<ISnapshotInfo> {\n\tlet size = 0;\n\tlet sizeNew = 0;\n\tlet blobCountNew = 0;\n\tconst sorted = getDumpFetchedData(fetchedData);\n\n\tfor (const item of sorted) {\n\t\tconst buffer = await item.blob;\n\t\tif (buffer === undefined) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst blob = bufferToString(buffer, \"utf8\");\n\t\tif (!item.reused) {\n\t\t\tsizeNew += blob.length;\n\t\t\tblobCountNew++;\n\t\t}\n\t\tsize += blob.length;\n\t}\n\n\treturn { blobCountNew, blobCount: sorted.length, size, sizeNew };\n}\n\nasync function saveSnapshot(name: string, fetchedData: IFetchedData[], saveDir: string) {\n\tconst outDir = `${saveDir}/${name}/`;\n\tconst mkdir = util.promisify(fs.mkdir);\n\n\tawait mkdir(`${outDir}/decoded`, { recursive: true });\n\tawait Promise.all(\n\t\tfetchedData.map(async (item) => {\n\t\t\tconst buffer = await item.blob;\n\t\t\tif (buffer === undefined) {\n\t\t\t\tconsole.error(`ERROR: Unable to get data for blob ${item.blobId}`);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (!isFetchedTree(item)) {\n\t\t\t\t// Just write the data as is.\n\t\t\t\tfs.writeFileSync(`${outDir}/${item.filename}`, Buffer.from(buffer));\n\n\t\t\t\t// we assume that the buffer is utf8 here, which currently is true for\n\t\t\t\t// all of our snapshot blobs. It doesn't necessary be true in the future\n\t\t\t\tlet decoded = bufferToString(buffer, \"utf8\");\n\t\t\t\ttry {\n\t\t\t\t\tif (!paramActualFormatting) {\n\t\t\t\t\t\tdecoded = JSON.stringify(JSON.parse(decoded), undefined, 2);\n\t\t\t\t\t}\n\t\t\t\t} catch (e) {}\n\t\t\t\tfs.writeFileSync(`${outDir}/decoded/${item.filename}.json`, decoded);\n\t\t\t} else {\n\t\t\t\t// Write out same data for tree decoded or not, except for formatting\n\t\t\t\tconst treeString = bufferToString(buffer, \"utf8\");\n\t\t\t\tfs.writeFileSync(`${outDir}/${item.filename}.json`, treeString);\n\t\t\t\tfs.writeFileSync(\n\t\t\t\t\t`${outDir}/decoded/${item.filename}.json`,\n\t\t\t\t\tparamActualFormatting\n\t\t\t\t\t\t? treeString\n\t\t\t\t\t\t: JSON.stringify(JSON.parse(treeString), undefined, 2),\n\t\t\t\t);\n\t\t\t}\n\t\t}),\n\t);\n}\n\nasync function fetchBlobsFromVersion(storage: IDocumentStorageService, version: IVersion) {\n\tconst tree = await reportErrors(\n\t\t`getSnapshotTree ${version.id}`,\n\t\tstorage.getSnapshotTree(version),\n\t);\n\tif (!tree) {\n\t\tthrow new Error(\"Failed to load snapshot tree\");\n\t}\n\treturn fetchBlobsFromSnapshotTree(storage, tree);\n}\n\nasync function reportErrors<T>(message: string, res: Promise<T>) {\n\ttry {\n\t\treturn await res;\n\t} catch (error) {\n\t\tconsole.error(`Error calling ${message}`);\n\t\tthrow error;\n\t}\n}\n\nexport async function fluidFetchSnapshot(documentService?: IDocumentService, saveDir?: string) {\n\tif (\n\t\t!dumpSnapshotStats &&\n\t\t!dumpSnapshotTrees &&\n\t\t!dumpSnapshotVersions &&\n\t\tsaveDir === undefined\n\t) {\n\t\treturn;\n\t}\n\n\t// --local mode - do not connect to storage.\n\t// For now, bail out early.\n\t// In future, separate download from analyzes parts and allow offline analyzes\n\tif (!documentService) {\n\t\treturn;\n\t}\n\n\tconsole.log(\"\\n\");\n\n\tconst storage = await documentService.connectToStorage();\n\n\tlet version: IVersion | undefined;\n\tconst versions = await reportErrors(\n\t\t`getVersions ${latestVersionsId}`,\n\t\tstorage.getVersions(latestVersionsId, paramNumSnapshotVersions),\n\t);\n\tif (dumpSnapshotVersions) {\n\t\tconsole.log(\"Snapshot versions\");\n\t\tconsole.log(versions);\n\t}\n\n\tlet blobsToDump: IFetchedData[] | undefined;\n\tif (paramSnapshotVersionIndex !== undefined) {\n\t\tversion = versions[paramSnapshotVersionIndex];\n\t\tif (version === undefined) {\n\t\t\tconsole.log(\n\t\t\t\t`There are only ${versions.length} snapshots, --snapshotVersionIndex is too large`,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\tif (saveDir !== undefined) {\n\t\t\tblobsToDump = await fetchBlobsFromVersion(storage, version);\n\t\t\tconst name = version.id;\n\t\t\tconsole.log(`Saving snapshot ${name}`);\n\t\t\tawait saveSnapshot(name, blobsToDump, saveDir);\n\t\t}\n\t} else {\n\t\tversion = versions[0];\n\t\tif (saveDir !== undefined && versions.length > 0) {\n\t\t\tconsole.log(\n\t\t\t\t\" Name | Date | Size | New Size | Blobs | New Blobs\",\n\t\t\t);\n\t\t\tconsole.log(\"-\".repeat(86));\n\n\t\t\t// Go in reverse order, to correctly calculate blob reuse - from oldest to newest snapshots\n\t\t\tfor (let i = versions.length - 1; i >= 0; i--) {\n\t\t\t\tconst v = versions[i];\n\t\t\t\tconst blobs = await fetchBlobsFromVersion(storage, v);\n\t\t\t\tblobsToDump = blobs;\n\t\t\t\tconst name = `${i}-${v.id}`;\n\t\t\t\tconst res = await dumpSnapshotTree(name, blobs);\n\n\t\t\t\tlet date = \"\";\n\t\t\t\tif (v.date !== undefined) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tdate = new Date(v.date).toLocaleString();\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\tdate = v.date.replace(\"T\", \" \");\n\t\t\t\t\t\tconst index = date.lastIndexOf(\".\");\n\t\t\t\t\t\tif (index > 0) {\n\t\t\t\t\t\t\tdate = `${date.substr(0, index)} Z`;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tdate = date.padStart(23);\n\t\t\t\tconst size = formatNumber(res.size).padStart(10);\n\t\t\t\tconst sizeNew = formatNumber(res.sizeNew).padStart(10);\n\t\t\t\tconst blobCount = formatNumber(res.blobCount).padStart(6);\n\t\t\t\tconst blobCountNew = formatNumber(res.blobCountNew).padStart(9);\n\n\t\t\t\tconsole.log(\n\t\t\t\t\t`${name.padEnd(\n\t\t\t\t\t\t15,\n\t\t\t\t\t)} | ${date} | ${size} | ${sizeNew} | ${blobCount} | ${blobCountNew}`,\n\t\t\t\t);\n\n\t\t\t\tawait saveSnapshot(name, blobs, saveDir);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (dumpSnapshotStats || dumpSnapshotTrees) {\n\t\tif (version === undefined) {\n\t\t\tconsole.log(\"No snapshot tree\");\n\t\t} else {\n\t\t\tif (blobsToDump === undefined) {\n\t\t\t\tblobsToDump = await fetchBlobsFromVersion(storage, version);\n\t\t\t}\n\t\t\tconsole.log(`\\n\\nSnapshot version ${version.id}`);\n\t\t\tawait dumpSnapshotTreeVerbose(version.id, blobsToDump);\n\t\t}\n\t}\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluid-tools/fetch-tool",
3
- "version": "2.0.0-dev-rc.2.0.0.246488",
3
+ "version": "2.0.0-dev-rc.3.0.0.253463",
4
4
  "description": "Console tool to fetch Fluid data from relay service",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": {
@@ -10,36 +10,36 @@
10
10
  },
11
11
  "license": "MIT",
12
12
  "author": "Microsoft and contributors",
13
- "main": "dist/fluidFetch.js",
14
- "types": "dist/fluidFetch.d.ts",
13
+ "type": "module",
15
14
  "bin": {
16
15
  "fluid-fetch": "bin/fluid-fetch"
17
16
  },
18
17
  "dependencies": {
19
- "@fluid-internal/client-utils": "2.0.0-dev-rc.2.0.0.246488",
20
- "@fluidframework/container-runtime": "2.0.0-dev-rc.2.0.0.246488",
21
- "@fluidframework/core-interfaces": "2.0.0-dev-rc.2.0.0.246488",
22
- "@fluidframework/core-utils": "2.0.0-dev-rc.2.0.0.246488",
23
- "@fluidframework/datastore": "2.0.0-dev-rc.2.0.0.246488",
24
- "@fluidframework/driver-definitions": "2.0.0-dev-rc.2.0.0.246488",
25
- "@fluidframework/odsp-doclib-utils": "2.0.0-dev-rc.2.0.0.246488",
26
- "@fluidframework/odsp-driver": "2.0.0-dev-rc.2.0.0.246488",
27
- "@fluidframework/odsp-driver-definitions": "2.0.0-dev-rc.2.0.0.246488",
28
- "@fluidframework/odsp-urlresolver": "2.0.0-dev-rc.2.0.0.246488",
18
+ "@fluid-internal/client-utils": "2.0.0-dev-rc.3.0.0.253463",
19
+ "@fluidframework/container-runtime": "2.0.0-dev-rc.3.0.0.253463",
20
+ "@fluidframework/core-interfaces": "2.0.0-dev-rc.3.0.0.253463",
21
+ "@fluidframework/core-utils": "2.0.0-dev-rc.3.0.0.253463",
22
+ "@fluidframework/datastore": "2.0.0-dev-rc.3.0.0.253463",
23
+ "@fluidframework/driver-definitions": "2.0.0-dev-rc.3.0.0.253463",
24
+ "@fluidframework/odsp-doclib-utils": "2.0.0-dev-rc.3.0.0.253463",
25
+ "@fluidframework/odsp-driver": "2.0.0-dev-rc.3.0.0.253463",
26
+ "@fluidframework/odsp-driver-definitions": "2.0.0-dev-rc.3.0.0.253463",
27
+ "@fluidframework/odsp-urlresolver": "2.0.0-dev-rc.3.0.0.253463",
29
28
  "@fluidframework/protocol-definitions": "^3.2.0",
30
- "@fluidframework/routerlicious-driver": "2.0.0-dev-rc.2.0.0.246488",
31
- "@fluidframework/routerlicious-urlresolver": "2.0.0-dev-rc.2.0.0.246488",
32
- "@fluidframework/runtime-definitions": "2.0.0-dev-rc.2.0.0.246488",
33
- "@fluidframework/tool-utils": "2.0.0-dev-rc.2.0.0.246488"
29
+ "@fluidframework/routerlicious-driver": "2.0.0-dev-rc.3.0.0.253463",
30
+ "@fluidframework/routerlicious-urlresolver": "2.0.0-dev-rc.3.0.0.253463",
31
+ "@fluidframework/runtime-definitions": "2.0.0-dev-rc.3.0.0.253463",
32
+ "@fluidframework/tool-utils": "2.0.0-dev-rc.3.0.0.253463"
34
33
  },
35
34
  "devDependencies": {
36
- "@arethetypeswrong/cli": "^0.13.3",
35
+ "@biomejs/biome": "^1.6.2",
37
36
  "@fluid-tools/build-cli": "^0.34.0",
38
37
  "@fluid-tools/fetch-tool-previous": "npm:@fluid-tools/fetch-tool@2.0.0-internal.8.0.0",
39
38
  "@fluidframework/build-common": "^2.0.3",
40
39
  "@fluidframework/build-tools": "^0.34.0",
41
40
  "@fluidframework/eslint-config-fluid": "^5.1.0",
42
41
  "@types/node": "^18.19.0",
42
+ "copyfiles": "^2.4.1",
43
43
  "eslint": "~8.55.0",
44
44
  "prettier": "~3.0.3",
45
45
  "rimraf": "^4.4.0",
@@ -51,15 +51,15 @@
51
51
  "scripts": {
52
52
  "build": "fluid-build . --task build",
53
53
  "build:compile": "fluid-build . --task compile",
54
- "check:are-the-types-wrong": "attw --pack",
55
- "clean": "rimraf --glob dist \"**/*.tsbuildinfo\" \"**/*.build.log\"",
54
+ "build:esnext": "tsc --project ./tsconfig.json",
55
+ "check:prettier": "prettier --check . --cache --ignore-path ../../../.prettierignore",
56
+ "clean": "rimraf --glob dist \"**/*.tsbuildinfo\" \"**/*.build.log\" lib",
56
57
  "eslint": "eslint --format stylish src",
57
58
  "eslint:fix": "eslint --format stylish src --fix --fix-type problem,suggestion,layout",
58
- "format": "npm run prettier:fix",
59
- "lint": "npm run prettier && npm run eslint",
60
- "lint:fix": "npm run prettier:fix && npm run eslint:fix",
61
- "prettier": "prettier --check . --cache --ignore-path ../../../.prettierignore",
62
- "prettier:fix": "prettier --write . --cache --ignore-path ../../../.prettierignore",
63
- "tsc": "tsc"
59
+ "format": "fluid-build --task format .",
60
+ "format:prettier": "prettier --write . --cache --ignore-path ../../../.prettierignore",
61
+ "lint": "fluid-build . --task lint",
62
+ "lint:fix": "fluid-build . --task eslint:fix --task format",
63
+ "tsc": "fluid-tsc commonjs --project ./tsconfig.cjs.json && copyfiles -f ../../../common/build/build-common/src/cjs/package.json ./dist"
64
64
  }
65
65
  }
@@ -3,7 +3,13 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { assert, unreachableCase } from "@fluidframework/core-utils";
6
+ import {
7
+ ContainerMessageType,
8
+ IChunkedOp,
9
+ unpackRuntimeMessage,
10
+ } from "@fluidframework/container-runtime/internal";
11
+ import { assert, unreachableCase } from "@fluidframework/core-utils/internal";
12
+ import { DataStoreMessageType } from "@fluidframework/datastore/internal";
7
13
  import {
8
14
  ISequencedDocumentMessage,
9
15
  ISummaryAck,
@@ -12,13 +18,7 @@ import {
12
18
  MessageType,
13
19
  TreeEntry,
14
20
  } from "@fluidframework/protocol-definitions";
15
- import { IAttachMessage, IEnvelope } from "@fluidframework/runtime-definitions";
16
- import {
17
- IChunkedOp,
18
- ContainerMessageType,
19
- unpackRuntimeMessage,
20
- } from "@fluidframework/container-runtime";
21
- import { DataStoreMessageType } from "@fluidframework/datastore";
21
+ import { IAttachMessage, IEnvelope } from "@fluidframework/runtime-definitions/internal";
22
22
 
23
23
  const noClientName = "No Client";
24
24
  const objectTypePrefix = "https://graph.microsoft.com/types/";
@@ -555,6 +555,9 @@ function processOp(
555
555
  case ContainerMessageType.GC: {
556
556
  break;
557
557
  }
558
+ case ContainerMessageType.DocumentSchemaChange: {
559
+ break;
560
+ }
558
561
  case ContainerMessageType.ChunkedOp: {
559
562
  const chunk = runtimeMessage.contents as IChunkedOp;
560
563
  // TODO: Verify whether this should be able to handle server-generated ops (with null clientId)
@@ -580,8 +583,8 @@ function processOp(
580
583
  opCount = chunk.totalChunks; // 1 op for each chunk.
581
584
  const patchedMessage = Object.create(runtimeMessage);
582
585
  patchedMessage.contents = chunks.join("");
583
- patchedMessage.type = chunk.originalType;
584
- type = chunk.originalType;
586
+ type = (chunk as any).originalType;
587
+ patchedMessage.type = type;
585
588
  totalMsgSize = value.totalSize;
586
589
  chunkMap.delete(patchedMessage.clientId);
587
590
  } else {
package/src/fluidFetch.ts CHANGED
@@ -5,12 +5,14 @@
5
5
 
6
6
  import fs from "fs";
7
7
  import util from "util";
8
- import { isOdspHostname, IOdspDriveItem } from "@fluidframework/odsp-doclib-utils/internal";
9
- import { paramSaveDir, paramURL, parseArguments } from "./fluidFetchArgs";
10
- import { connectionInfo, fluidFetchInit } from "./fluidFetchInit";
11
- import { fluidFetchMessages } from "./fluidFetchMessages";
12
- import { getSharepointFiles, getSingleSharePointFile } from "./fluidFetchSharePoint";
13
- import { fluidFetchSnapshot } from "./fluidFetchSnapshot";
8
+
9
+ import { IOdspDriveItem, isOdspHostname } from "@fluidframework/odsp-doclib-utils/internal";
10
+
11
+ import { paramSaveDir, paramURL, parseArguments } from "./fluidFetchArgs.js";
12
+ import { connectionInfo, fluidFetchInit } from "./fluidFetchInit.js";
13
+ import { fluidFetchMessages } from "./fluidFetchMessages.js";
14
+ import { getSharepointFiles, getSingleSharePointFile } from "./fluidFetchSharePoint.js";
15
+ import { fluidFetchSnapshot } from "./fluidFetchSnapshot.js";
14
16
 
15
17
  async function fluidFetchOneFile(urlStr: string, name?: string) {
16
18
  const documentService = await fluidFetchInit(urlStr);
@@ -4,19 +4,23 @@
4
4
  */
5
5
 
6
6
  import { IRequest } from "@fluidframework/core-interfaces";
7
- import { IResolvedUrl } from "@fluidframework/driver-definitions";
7
+ import { IResolvedUrl } from "@fluidframework/driver-definitions/internal";
8
8
  import { IClientConfig, IOdspAuthRequestInfo } from "@fluidframework/odsp-doclib-utils/internal";
9
- import * as odsp from "@fluidframework/odsp-driver";
9
+ import * as odsp from "@fluidframework/odsp-driver/internal";
10
10
  import {
11
11
  IOdspResolvedUrl,
12
12
  OdspResourceTokenFetchOptions,
13
- } from "@fluidframework/odsp-driver-definitions";
14
- import { FluidAppOdspUrlResolver, OdspUrlResolver } from "@fluidframework/odsp-urlresolver";
15
- import * as r11s from "@fluidframework/routerlicious-driver";
16
- import { RouterliciousUrlResolver } from "@fluidframework/routerlicious-urlresolver";
17
- import { getMicrosoftConfiguration } from "@fluidframework/tool-utils";
18
- import { localDataOnly, paramJWT } from "./fluidFetchArgs";
19
- import { resolveWrapper } from "./fluidFetchSharePoint";
13
+ } from "@fluidframework/odsp-driver-definitions/internal";
14
+ import {
15
+ FluidAppOdspUrlResolver,
16
+ OdspUrlResolver,
17
+ } from "@fluidframework/odsp-urlresolver/internal";
18
+ import * as r11s from "@fluidframework/routerlicious-driver/internal";
19
+ import { RouterliciousUrlResolver } from "@fluidframework/routerlicious-urlresolver/internal";
20
+ import { getMicrosoftConfiguration } from "@fluidframework/tool-utils/internal";
21
+
22
+ import { localDataOnly, paramJWT } from "./fluidFetchArgs.js";
23
+ import { resolveWrapper } from "./fluidFetchSharePoint.js";
20
24
 
21
25
  export let latestVersionsId: string = "";
22
26
  export let connectionInfo: any;
@@ -4,23 +4,25 @@
4
4
  */
5
5
 
6
6
  import fs from "fs";
7
- import { assert } from "@fluidframework/core-utils";
8
- import { IDocumentService } from "@fluidframework/driver-definitions";
7
+
8
+ import { assert } from "@fluidframework/core-utils/internal";
9
+ import { IDocumentService } from "@fluidframework/driver-definitions/internal";
9
10
  import {
10
11
  IClient,
11
12
  ISequencedDocumentMessage,
12
13
  MessageType,
13
14
  ScopeType,
14
15
  } from "@fluidframework/protocol-definitions";
15
- import { printMessageStats } from "./fluidAnalyzeMessages";
16
+
17
+ import { printMessageStats } from "./fluidAnalyzeMessages.js";
16
18
  import {
17
19
  connectToWebSocket,
18
- dumpMessages,
19
20
  dumpMessageStats,
21
+ dumpMessages,
22
+ messageTypeFilter,
20
23
  overWrite,
21
24
  paramActualFormatting,
22
- messageTypeFilter,
23
- } from "./fluidFetchArgs";
25
+ } from "./fluidFetchArgs.js";
24
26
 
25
27
  function filenameFromIndex(index: number): string {
26
28
  return index === 0 ? "" : index.toString(); // support old tools...
@@ -4,24 +4,26 @@
4
4
  */
5
5
 
6
6
  import child_process from "child_process";
7
+
7
8
  import { DriverErrorTypes } from "@fluidframework/driver-definitions";
8
9
  import {
10
+ IClientConfig,
11
+ IOdspAuthRequestInfo,
12
+ IOdspDriveItem,
9
13
  getChildrenByDriveItem,
10
14
  getDriveItemByServerRelativePath,
11
15
  getDriveItemFromDriveAndItem,
12
- IClientConfig,
13
- IOdspDriveItem,
14
16
  getOdspRefreshTokenFn,
15
- IOdspAuthRequestInfo,
16
17
  } from "@fluidframework/odsp-doclib-utils/internal";
17
18
  import {
18
- getMicrosoftConfiguration,
19
+ IOdspTokenManagerCacheKey,
20
+ OdspTokenConfig,
19
21
  OdspTokenManager,
22
+ getMicrosoftConfiguration,
20
23
  odspTokensCache,
21
- OdspTokenConfig,
22
- IOdspTokenManagerCacheKey,
23
- } from "@fluidframework/tool-utils";
24
- import { getForceTokenReauth } from "./fluidFetchArgs";
24
+ } from "@fluidframework/tool-utils/internal";
25
+
26
+ import { getForceTokenReauth } from "./fluidFetchArgs.js";
25
27
 
26
28
  export async function resolveWrapper<T>(
27
29
  callback: (authRequestInfo: IOdspAuthRequestInfo) => Promise<T>,
@@ -7,10 +7,13 @@ import fs from "fs";
7
7
  import util from "util";
8
8
 
9
9
  import { bufferToString, stringToBuffer } from "@fluid-internal/client-utils";
10
- import { IDocumentService, IDocumentStorageService } from "@fluidframework/driver-definitions";
10
+ import {
11
+ IDocumentService,
12
+ IDocumentStorageService,
13
+ } from "@fluidframework/driver-definitions/internal";
11
14
  import { ISnapshotTree, IVersion } from "@fluidframework/protocol-definitions";
12
15
 
13
- import { formatNumber } from "./fluidAnalyzeMessages";
16
+ import { formatNumber } from "./fluidAnalyzeMessages.js";
14
17
  import {
15
18
  dumpSnapshotStats,
16
19
  dumpSnapshotTrees,
@@ -18,8 +21,8 @@ import {
18
21
  paramActualFormatting,
19
22
  paramNumSnapshotVersions,
20
23
  paramSnapshotVersionIndex,
21
- } from "./fluidFetchArgs";
22
- import { latestVersionsId } from "./fluidFetchInit";
24
+ } from "./fluidFetchArgs.js";
25
+ import { latestVersionsId } from "./fluidFetchInit.js";
23
26
 
24
27
  interface ISnapshotInfo {
25
28
  blobCountNew: number;
@@ -0,0 +1,7 @@
1
+ {
2
+ // This config must be used in a "type": "commonjs" environment. (Use `fluid-tsc commonjs`.)
3
+ "extends": "./tsconfig.json",
4
+ "compilerOptions": {
5
+ "outDir": "./dist",
6
+ },
7
+ }
package/tsconfig.json CHANGED
@@ -1,13 +1,10 @@
1
1
  {
2
- "extends": [
3
- "../../../common/build/build-common/tsconfig.base.json",
4
- "../../../common/build/build-common/tsconfig.cjs.json",
5
- ],
2
+ "extends": "../../../common/build/build-common/tsconfig.node16.json",
6
3
  "include": ["src/**/*.ts"],
7
4
  "exclude": ["dist", "node_modules"],
8
5
  "compilerOptions": {
9
6
  "rootDir": "./src",
10
- "outDir": "./dist",
7
+ "outDir": "./lib",
11
8
  "types": ["node"],
12
9
  },
13
10
  }