@fluidframework/tool-utils 2.4.0 → 2.5.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/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # @fluidframework/tool-utils
2
2
 
3
+ ## 2.5.0
4
+
5
+ Dependency updates only.
6
+
3
7
  ## 2.4.0
4
8
 
5
9
  Dependency updates only.
@@ -5,5 +5,5 @@
5
5
  * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY
6
6
  */
7
7
  export declare const pkgName = "@fluidframework/tool-utils";
8
- export declare const pkgVersion = "2.4.0";
8
+ export declare const pkgVersion = "2.5.0";
9
9
  //# sourceMappingURL=packageVersion.d.ts.map
@@ -8,5 +8,5 @@
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.pkgVersion = exports.pkgName = void 0;
10
10
  exports.pkgName = "@fluidframework/tool-utils";
11
- exports.pkgVersion = "2.4.0";
11
+ exports.pkgVersion = "2.5.0";
12
12
  //# sourceMappingURL=packageVersion.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAEU,QAAA,OAAO,GAAG,4BAA4B,CAAC;AACvC,QAAA,UAAU,GAAG,OAAO,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/tool-utils\";\nexport const pkgVersion = \"2.4.0\";\n"]}
1
+ {"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAEU,QAAA,OAAO,GAAG,4BAA4B,CAAC;AACvC,QAAA,UAAU,GAAG,OAAO,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/tool-utils\";\nexport const pkgVersion = \"2.5.0\";\n"]}
@@ -9,6 +9,11 @@ import type { ITree } from "@fluidframework/driver-definitions/internal";
9
9
  * @internal
10
10
  */
11
11
  export declare const gcBlobPrefix = "__gc";
12
+ /**
13
+ * The name of the legacy catch-up ops blob in Merge Tree.
14
+ * @internal
15
+ */
16
+ export declare const legacyCatchUpBlobName = "catchupOps";
12
17
  /**
13
18
  * @internal
14
19
  */
@@ -1 +1 @@
1
- {"version":3,"file":"snapshotNormalizer.d.ts","sourceRoot":"","sources":["../src/snapshotNormalizer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAc,MAAM,6CAA6C,CAAC;AAarF;;;;GAIG;AACH,eAAO,MAAM,YAAY,SAAS,CAAC;AAEnC;;GAEG;AACH,MAAM,WAAW,yBAAyB;IAEzC,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B;;;;OAIG;IACH,2BAA2B,CAAC,EAAE,MAAM,EAAE,CAAC;CACvC;AAwHD;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACpC,QAAQ,EAAE,KAAK,EACf,MAAM,CAAC,EAAE,yBAAyB,GAChC,KAAK,CAkBP"}
1
+ {"version":3,"file":"snapshotNormalizer.d.ts","sourceRoot":"","sources":["../src/snapshotNormalizer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAEX,KAAK,EAEL,MAAM,6CAA6C,CAAC;AAarD;;;;GAIG;AACH,eAAO,MAAM,YAAY,SAAS,CAAC;AAEnC;;;GAGG;AACH,eAAO,MAAM,qBAAqB,eAAe,CAAC;AAElD;;GAEG;AACH,MAAM,WAAW,yBAAyB;IAEzC,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B;;;;OAIG;IACH,2BAA2B,CAAC,EAAE,MAAM,EAAE,CAAC;CACvC;AA4ID;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACpC,QAAQ,EAAE,KAAK,EACf,MAAM,CAAC,EAAE,yBAAyB,GAChC,KAAK,CAkBP"}
@@ -4,7 +4,7 @@
4
4
  * Licensed under the MIT License.
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.getNormalizedSnapshot = exports.gcBlobPrefix = void 0;
7
+ exports.getNormalizedSnapshot = exports.legacyCatchUpBlobName = exports.gcBlobPrefix = void 0;
8
8
  const internal_1 = require("@fluidframework/driver-definitions/internal");
9
9
  const internal_2 = require("@fluidframework/driver-utils/internal");
10
10
  /**
@@ -17,6 +17,11 @@ const metadataBlobName = ".metadata";
17
17
  * @internal
18
18
  */
19
19
  exports.gcBlobPrefix = "__gc";
20
+ /**
21
+ * The name of the legacy catch-up ops blob in Merge Tree.
22
+ * @internal
23
+ */
24
+ exports.legacyCatchUpBlobName = "catchupOps";
20
25
  const sortStringified = (elem1, elem2) => {
21
26
  const serializedElem1 = JSON.stringify(elem1);
22
27
  const serializedElem2 = JSON.stringify(elem2);
@@ -88,6 +93,26 @@ function getNormalizedBlobContent(blobContent, blobName) {
88
93
  }
89
94
  content = JSON.stringify(gcState);
90
95
  }
96
+ /**
97
+ * The legacy catch-up ops blob in merge tree DDS contains sequenced messages. These ops used to have metadata property.
98
+ * However, we stopped sending the metadata property to DDS because it was a Runtime layer concept. Remove the metadata
99
+ * property from the ops because latest snapshots won't have the metadata property.
100
+ */
101
+ if (blobName === exports.legacyCatchUpBlobName) {
102
+ try {
103
+ const catchupOps = JSON.parse(content);
104
+ if (catchupOps !== undefined && catchupOps.length > 0) {
105
+ for (const [index, op] of catchupOps.entries()) {
106
+ op.metadata = undefined;
107
+ catchupOps[index] = op;
108
+ }
109
+ }
110
+ content = JSON.stringify(catchupOps);
111
+ }
112
+ catch {
113
+ // Do nothing
114
+ }
115
+ }
91
116
  /**
92
117
  * The metadata blob has "summaryNumber" or "summaryCount" that tells which summary this is for a container. It can
93
118
  * be different in summaries of two clients even if they are generated at the same sequence#. For instance, at seq#
@@ -190,9 +215,10 @@ function normalizeEntry(entry, config) {
190
215
  switch (entry.type) {
191
216
  case internal_1.TreeEntry.Blob: {
192
217
  let contents = entry.value.contents;
193
- // If this blob has to be normalized or it's a GC blob, parse and sort the blob contents first.
218
+ // If this blob has to be normalized, it's a GC or legacy catchup blob, parse and sort the blob contents first.
194
219
  if ((config?.blobsToNormalize?.includes(entry.path) ?? false) ||
195
- entry.path.startsWith(exports.gcBlobPrefix)) {
220
+ entry.path.startsWith(exports.gcBlobPrefix) ||
221
+ entry.path === exports.legacyCatchUpBlobName) {
196
222
  contents = getNormalizedBlobContent(contents, entry.path);
197
223
  }
198
224
  return new internal_2.BlobTreeEntry(entry.path, contents);
@@ -1 +1 @@
1
- {"version":3,"file":"snapshotNormalizer.js","sourceRoot":"","sources":["../src/snapshotNormalizer.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 type { ITree, ITreeEntry } from \"@fluidframework/driver-definitions/internal\";\nimport { TreeEntry } from \"@fluidframework/driver-definitions/internal\";\nimport {\n\tAttachmentTreeEntry,\n\tBlobTreeEntry,\n\tTreeTreeEntry,\n} from \"@fluidframework/driver-utils/internal\";\n\n/**\n * The name of the metadata blob added to the root of the container runtime.\n */\nconst metadataBlobName = \".metadata\";\n\n/**\n * The prefix that all GC blob names start with.\n *\n * @internal\n */\nexport const gcBlobPrefix = \"__gc\";\n\n/**\n * @internal\n */\nexport interface ISnapshotNormalizerConfig {\n\t// The paths of blobs whose contents should be normalized.\n\tblobsToNormalize?: string[];\n\t/**\n\t * channel types who's content (non-attribute) blobs will be excluded.\n\t * this is used to exclude the content of channels who's content cannot be compared\n\t * as the content is non-deterministic between snapshot at the same sequence number.\n\t */\n\texcludedChannelContentTypes?: string[];\n}\n\nconst sortStringified = (elem1: unknown, elem2: unknown): number => {\n\tconst serializedElem1 = JSON.stringify(elem1);\n\tconst serializedElem2 = JSON.stringify(elem2);\n\treturn serializedElem1.localeCompare(serializedElem2);\n};\n\n/**\n * Function that deep sorts an array. It handles cases where array elements are objects or arrays.\n * @returns the sorted array.\n */\nfunction getDeepSortedArray(array: unknown[]): unknown[] {\n\tconst sortedArray: unknown[] = [];\n\t// Sort arrays and objects, if any, in the array.\n\tfor (const element of array) {\n\t\tif (Array.isArray(element)) {\n\t\t\tsortedArray.push(getDeepSortedArray(element));\n\t\t} else if (element instanceof Object) {\n\t\t\tsortedArray.push(getDeepSortedObject(element));\n\t\t} else {\n\t\t\tsortedArray.push(element);\n\t\t}\n\t}\n\n\t// Now that all the arrays and objects in this array's elements have been sorted, sort it by comparing each\n\t// element's stringified version.\n\treturn sortedArray.sort(sortStringified);\n}\n\n/**\n * Function that deep sorts an object. It handles cases where object properties are arrays or objects.\n * @returns the sorted object.\n */\nfunction getDeepSortedObject<T extends object>(obj: T): T {\n\t// eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n\tconst sortedObj: T = {} as T;\n\t// Sort the object keys first. Then sort arrays and objects, if any, in the object.\n\tconst keys = Object.keys(obj).sort();\n\tfor (const key of keys) {\n\t\tconst value: unknown = obj[key];\n\t\tif (Array.isArray(value)) {\n\t\t\tsortedObj[key] = getDeepSortedArray(value);\n\t\t} else if (value instanceof Object) {\n\t\t\tsortedObj[key] = getDeepSortedObject(value);\n\t\t} else {\n\t\t\tsortedObj[key] = value;\n\t\t}\n\t}\n\n\treturn sortedObj;\n}\n\n/**\n * Function that normalizes a blob's content. If the content is an object or an array, deep sorts them.\n * Special handling for certain runtime blobs, such as the \"gc\" blob.\n * @returns the normalized blob content.\n */\nfunction getNormalizedBlobContent(blobContent: string, blobName: string): string {\n\tlet content = blobContent;\n\tif (blobName.startsWith(gcBlobPrefix)) {\n\t\t// The following code parses JSON and makes some assumptions about the type of data within. There does not appear to\n\t\t// be a better type than `any` to use here, so the lint rules are disabled.\n\n\t\t/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */\n\n\t\t// GC blobs may contain `unreferencedTimestampMs` for node that became unreferenced. This is the timestamp\n\t\t// of the last op processed or current timestamp and can differ between clients depending on when GC was run.\n\t\t// So, remove it for the purposes of comparing snapshots.\n\t\tconst gcState: any = JSON.parse(content);\n\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n\t\tfor (const [, data] of Object.entries(gcState.gcNodes)) {\n\t\t\tdelete (data as any).unreferencedTimestampMs;\n\t\t}\n\t\tcontent = JSON.stringify(gcState);\n\t}\n\n\t/**\n\t * The metadata blob has \"summaryNumber\" or \"summaryCount\" that tells which summary this is for a container. It can\n\t * be different in summaries of two clients even if they are generated at the same sequence#. For instance, at seq#\n\t * 1000, if one client has summarized 10 times and other has summarizer 15 times, summaryNumber will be different\n\t * for them. So, update \"summaryNumber\" to 0 for purposes of comparing snapshots.\n\t */\n\tif (blobName === metadataBlobName) {\n\t\tconst metadata = JSON.parse(content);\n\t\tif (metadata.summaryNumber !== undefined) {\n\t\t\tmetadata.summaryNumber = 0;\n\t\t}\n\t\tif (metadata.summaryCount !== undefined) {\n\t\t\tmetadata.summaryCount = 0;\n\t\t}\n\t\t// \"telemetryDocumentId\" is not a deterministic property (random guid), so we need to set it to something consistent\n\t\tif (metadata.telemetryDocumentId !== undefined) {\n\t\t\tmetadata.telemetryDocumentId = \"x\";\n\t\t}\n\t\t// default was not written before, now it's written in.\n\t\tif (metadata.documentSchema !== undefined) {\n\t\t\tmetadata.documentSchema = undefined;\n\t\t}\n\t\tcontent = JSON.stringify(metadata);\n\t}\n\n\t// Deep sort the content if it's parseable.\n\ttry {\n\t\tlet contentObj = JSON.parse(content);\n\t\tif (Array.isArray(contentObj)) {\n\t\t\tcontentObj = getDeepSortedArray(contentObj);\n\t\t} else if (contentObj instanceof Object) {\n\t\t\tcontentObj = getDeepSortedObject(contentObj);\n\t\t}\n\t\tcontent = JSON.stringify(contentObj);\n\t} catch {\n\t\t// Do nothing\n\t}\n\n\t/* eslint-enable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */\n\n\treturn content;\n}\n\n/**\n * Helper function that normalizes the given snapshot tree. It sorts objects and arrays in the snapshot. It also\n * normalizes certain blob contents for which the order of content does not matter. For example, garbage collection\n * blobs contains objects / arrays whose element order do not matter.\n * @param snapshot - The snapshot tree to normalize.\n * @param config - Configs to use when normalizing snapshot. For example, it can contain paths of blobs whose contents\n * should be normalized as well.\n * @returns a copy of the normalized snapshot tree.\n * @internal\n */\nexport function getNormalizedSnapshot(\n\tsnapshot: ITree,\n\tconfig?: ISnapshotNormalizerConfig,\n): ITree {\n\t// Merge blobs to normalize in the config with runtime blobs to normalize. The contents of these blobs will be\n\t// parsed and deep sorted.\n\tconst normalizedEntries: ITreeEntry[] = [];\n\n\t// The metadata blob in the root of the summary tree needs to be normalized.\n\tconst blobsToNormalize = [metadataBlobName, ...(config?.blobsToNormalize ?? [])];\n\tfor (const entry of snapshot.entries) {\n\t\tnormalizedEntries.push(normalizeEntry(entry, { ...config, blobsToNormalize }));\n\t}\n\n\t// Sort the tree entries based on their path.\n\tnormalizedEntries.sort((a, b) => a.path.localeCompare(b.path));\n\n\treturn {\n\t\tentries: normalizedEntries,\n\t\tid: snapshot.id,\n\t};\n}\n\nfunction normalizeMatrix(value: ITree): ITree {\n\tconst rows = value.entries.find((e) => e.path === \"rows\");\n\n\tif (!rows || !(\"entries\" in rows.value)) {\n\t\treturn value;\n\t}\n\n\tconst segments = rows.value.entries.find((e) => e.path === \"segments\");\n\n\tif (!segments || !(\"entries\" in segments.value)) {\n\t\treturn value;\n\t}\n\n\tconst header = segments.value.entries.find((e) => e.path === \"header\");\n\n\tif (!header || !(\"contents\" in header.value)) {\n\t\treturn value;\n\t}\n\n\tif (!header?.value.contents.includes(\"removedClientId\")) {\n\t\treturn value;\n\t}\n\n\t/* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */\n\n\tconst contents = JSON.parse(header?.value.contents);\n\n\tfor (const segment of contents.segments) {\n\t\tif (\"removedClientId\" in segment) {\n\t\t\tsegment.removedClientId = undefined;\n\t\t}\n\n\t\tif (\"removedClientIds\" in segment) {\n\t\t\tsegment.removedClientIds = undefined;\n\t\t}\n\t}\n\n\theader.value.contents = JSON.stringify(contents);\n\n\t/* eslint-enable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */\n\n\treturn value;\n}\n\nfunction normalizeEntry(\n\tentry: ITreeEntry,\n\tconfig: ISnapshotNormalizerConfig | undefined,\n): ITreeEntry {\n\tswitch (entry.type) {\n\t\tcase TreeEntry.Blob: {\n\t\t\tlet contents = entry.value.contents;\n\t\t\t// If this blob has to be normalized or it's a GC blob, parse and sort the blob contents first.\n\t\t\tif (\n\t\t\t\t(config?.blobsToNormalize?.includes(entry.path) ?? false) ||\n\t\t\t\tentry.path.startsWith(gcBlobPrefix)\n\t\t\t) {\n\t\t\t\tcontents = getNormalizedBlobContent(contents, entry.path);\n\t\t\t}\n\t\t\treturn new BlobTreeEntry(entry.path, contents);\n\t\t}\n\t\tcase TreeEntry.Tree: {\n\t\t\tif (config?.excludedChannelContentTypes !== undefined) {\n\t\t\t\tfor (const maybeAttributes of entry.value.entries) {\n\t\t\t\t\tif (\n\t\t\t\t\t\tmaybeAttributes.type === TreeEntry.Blob &&\n\t\t\t\t\t\tmaybeAttributes.path === \".attributes\"\n\t\t\t\t\t) {\n\t\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n\t\t\t\t\t\tconst parsed: { type?: string } = JSON.parse(maybeAttributes.value.contents);\n\t\t\t\t\t\tif (parsed.type === \"https://graph.microsoft.com/types/sharedmatrix\") {\n\t\t\t\t\t\t\treturn new TreeTreeEntry(\n\t\t\t\t\t\t\t\tentry.path,\n\t\t\t\t\t\t\t\tnormalizeMatrix(getNormalizedSnapshot(entry.value, config)),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tparsed.type !== undefined &&\n\t\t\t\t\t\t\tconfig.excludedChannelContentTypes.includes(parsed.type)\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t// remove everything to match the unknown channel\n\t\t\t\t\t\t\treturn new TreeTreeEntry(entry.path, { entries: [maybeAttributes] });\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn new TreeTreeEntry(entry.path, getNormalizedSnapshot(entry.value, config));\n\t\t}\n\t\tcase TreeEntry.Attachment: {\n\t\t\treturn new AttachmentTreeEntry(entry.path, entry.value.id);\n\t\t}\n\n\t\tdefault: {\n\t\t\tthrow new Error(\"Unknown entry type\");\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"snapshotNormalizer.js","sourceRoot":"","sources":["../src/snapshotNormalizer.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 type {\n\tISequencedDocumentMessage,\n\tITree,\n\tITreeEntry,\n} from \"@fluidframework/driver-definitions/internal\";\nimport { TreeEntry } from \"@fluidframework/driver-definitions/internal\";\nimport {\n\tAttachmentTreeEntry,\n\tBlobTreeEntry,\n\tTreeTreeEntry,\n} from \"@fluidframework/driver-utils/internal\";\n\n/**\n * The name of the metadata blob added to the root of the container runtime.\n */\nconst metadataBlobName = \".metadata\";\n\n/**\n * The prefix that all GC blob names start with.\n *\n * @internal\n */\nexport const gcBlobPrefix = \"__gc\";\n\n/**\n * The name of the legacy catch-up ops blob in Merge Tree.\n * @internal\n */\nexport const legacyCatchUpBlobName = \"catchupOps\";\n\n/**\n * @internal\n */\nexport interface ISnapshotNormalizerConfig {\n\t// The paths of blobs whose contents should be normalized.\n\tblobsToNormalize?: string[];\n\t/**\n\t * channel types who's content (non-attribute) blobs will be excluded.\n\t * this is used to exclude the content of channels who's content cannot be compared\n\t * as the content is non-deterministic between snapshot at the same sequence number.\n\t */\n\texcludedChannelContentTypes?: string[];\n}\n\nconst sortStringified = (elem1: unknown, elem2: unknown): number => {\n\tconst serializedElem1 = JSON.stringify(elem1);\n\tconst serializedElem2 = JSON.stringify(elem2);\n\treturn serializedElem1.localeCompare(serializedElem2);\n};\n\n/**\n * Function that deep sorts an array. It handles cases where array elements are objects or arrays.\n * @returns the sorted array.\n */\nfunction getDeepSortedArray(array: unknown[]): unknown[] {\n\tconst sortedArray: unknown[] = [];\n\t// Sort arrays and objects, if any, in the array.\n\tfor (const element of array) {\n\t\tif (Array.isArray(element)) {\n\t\t\tsortedArray.push(getDeepSortedArray(element));\n\t\t} else if (element instanceof Object) {\n\t\t\tsortedArray.push(getDeepSortedObject(element));\n\t\t} else {\n\t\t\tsortedArray.push(element);\n\t\t}\n\t}\n\n\t// Now that all the arrays and objects in this array's elements have been sorted, sort it by comparing each\n\t// element's stringified version.\n\treturn sortedArray.sort(sortStringified);\n}\n\n/**\n * Function that deep sorts an object. It handles cases where object properties are arrays or objects.\n * @returns the sorted object.\n */\nfunction getDeepSortedObject<T extends object>(obj: T): T {\n\t// eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n\tconst sortedObj: T = {} as T;\n\t// Sort the object keys first. Then sort arrays and objects, if any, in the object.\n\tconst keys = Object.keys(obj).sort();\n\tfor (const key of keys) {\n\t\tconst value: unknown = obj[key];\n\t\tif (Array.isArray(value)) {\n\t\t\tsortedObj[key] = getDeepSortedArray(value);\n\t\t} else if (value instanceof Object) {\n\t\t\tsortedObj[key] = getDeepSortedObject(value);\n\t\t} else {\n\t\t\tsortedObj[key] = value;\n\t\t}\n\t}\n\n\treturn sortedObj;\n}\n\n/**\n * Function that normalizes a blob's content. If the content is an object or an array, deep sorts them.\n * Special handling for certain runtime blobs, such as the \"gc\" blob.\n * @returns the normalized blob content.\n */\nfunction getNormalizedBlobContent(blobContent: string, blobName: string): string {\n\tlet content = blobContent;\n\tif (blobName.startsWith(gcBlobPrefix)) {\n\t\t// The following code parses JSON and makes some assumptions about the type of data within. There does not appear to\n\t\t// be a better type than `any` to use here, so the lint rules are disabled.\n\n\t\t/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */\n\n\t\t// GC blobs may contain `unreferencedTimestampMs` for node that became unreferenced. This is the timestamp\n\t\t// of the last op processed or current timestamp and can differ between clients depending on when GC was run.\n\t\t// So, remove it for the purposes of comparing snapshots.\n\t\tconst gcState: any = JSON.parse(content);\n\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n\t\tfor (const [, data] of Object.entries(gcState.gcNodes)) {\n\t\t\tdelete (data as any).unreferencedTimestampMs;\n\t\t}\n\t\tcontent = JSON.stringify(gcState);\n\t}\n\n\t/**\n\t * The legacy catch-up ops blob in merge tree DDS contains sequenced messages. These ops used to have metadata property.\n\t * However, we stopped sending the metadata property to DDS because it was a Runtime layer concept. Remove the metadata\n\t * property from the ops because latest snapshots won't have the metadata property.\n\t */\n\tif (blobName === legacyCatchUpBlobName) {\n\t\ttry {\n\t\t\tconst catchupOps = JSON.parse(content) as ISequencedDocumentMessage[];\n\t\t\tif (catchupOps !== undefined && catchupOps.length > 0) {\n\t\t\t\tfor (const [index, op] of catchupOps.entries()) {\n\t\t\t\t\top.metadata = undefined;\n\t\t\t\t\tcatchupOps[index] = op;\n\t\t\t\t}\n\t\t\t}\n\t\t\tcontent = JSON.stringify(catchupOps);\n\t\t} catch {\n\t\t\t// Do nothing\n\t\t}\n\t}\n\n\t/**\n\t * The metadata blob has \"summaryNumber\" or \"summaryCount\" that tells which summary this is for a container. It can\n\t * be different in summaries of two clients even if they are generated at the same sequence#. For instance, at seq#\n\t * 1000, if one client has summarized 10 times and other has summarizer 15 times, summaryNumber will be different\n\t * for them. So, update \"summaryNumber\" to 0 for purposes of comparing snapshots.\n\t */\n\tif (blobName === metadataBlobName) {\n\t\tconst metadata = JSON.parse(content);\n\t\tif (metadata.summaryNumber !== undefined) {\n\t\t\tmetadata.summaryNumber = 0;\n\t\t}\n\t\tif (metadata.summaryCount !== undefined) {\n\t\t\tmetadata.summaryCount = 0;\n\t\t}\n\t\t// \"telemetryDocumentId\" is not a deterministic property (random guid), so we need to set it to something consistent\n\t\tif (metadata.telemetryDocumentId !== undefined) {\n\t\t\tmetadata.telemetryDocumentId = \"x\";\n\t\t}\n\t\t// default was not written before, now it's written in.\n\t\tif (metadata.documentSchema !== undefined) {\n\t\t\tmetadata.documentSchema = undefined;\n\t\t}\n\t\tcontent = JSON.stringify(metadata);\n\t}\n\n\t// Deep sort the content if it's parseable.\n\ttry {\n\t\tlet contentObj = JSON.parse(content);\n\t\tif (Array.isArray(contentObj)) {\n\t\t\tcontentObj = getDeepSortedArray(contentObj);\n\t\t} else if (contentObj instanceof Object) {\n\t\t\tcontentObj = getDeepSortedObject(contentObj);\n\t\t}\n\t\tcontent = JSON.stringify(contentObj);\n\t} catch {\n\t\t// Do nothing\n\t}\n\n\t/* eslint-enable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */\n\n\treturn content;\n}\n\n/**\n * Helper function that normalizes the given snapshot tree. It sorts objects and arrays in the snapshot. It also\n * normalizes certain blob contents for which the order of content does not matter. For example, garbage collection\n * blobs contains objects / arrays whose element order do not matter.\n * @param snapshot - The snapshot tree to normalize.\n * @param config - Configs to use when normalizing snapshot. For example, it can contain paths of blobs whose contents\n * should be normalized as well.\n * @returns a copy of the normalized snapshot tree.\n * @internal\n */\nexport function getNormalizedSnapshot(\n\tsnapshot: ITree,\n\tconfig?: ISnapshotNormalizerConfig,\n): ITree {\n\t// Merge blobs to normalize in the config with runtime blobs to normalize. The contents of these blobs will be\n\t// parsed and deep sorted.\n\tconst normalizedEntries: ITreeEntry[] = [];\n\n\t// The metadata blob in the root of the summary tree needs to be normalized.\n\tconst blobsToNormalize = [metadataBlobName, ...(config?.blobsToNormalize ?? [])];\n\tfor (const entry of snapshot.entries) {\n\t\tnormalizedEntries.push(normalizeEntry(entry, { ...config, blobsToNormalize }));\n\t}\n\n\t// Sort the tree entries based on their path.\n\tnormalizedEntries.sort((a, b) => a.path.localeCompare(b.path));\n\n\treturn {\n\t\tentries: normalizedEntries,\n\t\tid: snapshot.id,\n\t};\n}\n\nfunction normalizeMatrix(value: ITree): ITree {\n\tconst rows = value.entries.find((e) => e.path === \"rows\");\n\n\tif (!rows || !(\"entries\" in rows.value)) {\n\t\treturn value;\n\t}\n\n\tconst segments = rows.value.entries.find((e) => e.path === \"segments\");\n\n\tif (!segments || !(\"entries\" in segments.value)) {\n\t\treturn value;\n\t}\n\n\tconst header = segments.value.entries.find((e) => e.path === \"header\");\n\n\tif (!header || !(\"contents\" in header.value)) {\n\t\treturn value;\n\t}\n\n\tif (!header?.value.contents.includes(\"removedClientId\")) {\n\t\treturn value;\n\t}\n\n\t/* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */\n\n\tconst contents = JSON.parse(header?.value.contents);\n\n\tfor (const segment of contents.segments) {\n\t\tif (\"removedClientId\" in segment) {\n\t\t\tsegment.removedClientId = undefined;\n\t\t}\n\n\t\tif (\"removedClientIds\" in segment) {\n\t\t\tsegment.removedClientIds = undefined;\n\t\t}\n\t}\n\n\theader.value.contents = JSON.stringify(contents);\n\n\t/* eslint-enable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */\n\n\treturn value;\n}\n\nfunction normalizeEntry(\n\tentry: ITreeEntry,\n\tconfig: ISnapshotNormalizerConfig | undefined,\n): ITreeEntry {\n\tswitch (entry.type) {\n\t\tcase TreeEntry.Blob: {\n\t\t\tlet contents = entry.value.contents;\n\t\t\t// If this blob has to be normalized, it's a GC or legacy catchup blob, parse and sort the blob contents first.\n\t\t\tif (\n\t\t\t\t(config?.blobsToNormalize?.includes(entry.path) ?? false) ||\n\t\t\t\tentry.path.startsWith(gcBlobPrefix) ||\n\t\t\t\tentry.path === legacyCatchUpBlobName\n\t\t\t) {\n\t\t\t\tcontents = getNormalizedBlobContent(contents, entry.path);\n\t\t\t}\n\t\t\treturn new BlobTreeEntry(entry.path, contents);\n\t\t}\n\t\tcase TreeEntry.Tree: {\n\t\t\tif (config?.excludedChannelContentTypes !== undefined) {\n\t\t\t\tfor (const maybeAttributes of entry.value.entries) {\n\t\t\t\t\tif (\n\t\t\t\t\t\tmaybeAttributes.type === TreeEntry.Blob &&\n\t\t\t\t\t\tmaybeAttributes.path === \".attributes\"\n\t\t\t\t\t) {\n\t\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n\t\t\t\t\t\tconst parsed: { type?: string } = JSON.parse(maybeAttributes.value.contents);\n\t\t\t\t\t\tif (parsed.type === \"https://graph.microsoft.com/types/sharedmatrix\") {\n\t\t\t\t\t\t\treturn new TreeTreeEntry(\n\t\t\t\t\t\t\t\tentry.path,\n\t\t\t\t\t\t\t\tnormalizeMatrix(getNormalizedSnapshot(entry.value, config)),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tparsed.type !== undefined &&\n\t\t\t\t\t\t\tconfig.excludedChannelContentTypes.includes(parsed.type)\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t// remove everything to match the unknown channel\n\t\t\t\t\t\t\treturn new TreeTreeEntry(entry.path, { entries: [maybeAttributes] });\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn new TreeTreeEntry(entry.path, getNormalizedSnapshot(entry.value, config));\n\t\t}\n\t\tcase TreeEntry.Attachment: {\n\t\t\treturn new AttachmentTreeEntry(entry.path, entry.value.id);\n\t\t}\n\n\t\tdefault: {\n\t\t\tthrow new Error(\"Unknown entry type\");\n\t\t}\n\t}\n}\n"]}
@@ -5,5 +5,5 @@
5
5
  * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY
6
6
  */
7
7
  export declare const pkgName = "@fluidframework/tool-utils";
8
- export declare const pkgVersion = "2.4.0";
8
+ export declare const pkgVersion = "2.5.0";
9
9
  //# sourceMappingURL=packageVersion.d.ts.map
@@ -5,5 +5,5 @@
5
5
  * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY
6
6
  */
7
7
  export const pkgName = "@fluidframework/tool-utils";
8
- export const pkgVersion = "2.4.0";
8
+ export const pkgVersion = "2.5.0";
9
9
  //# sourceMappingURL=packageVersion.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,OAAO,GAAG,4BAA4B,CAAC;AACpD,MAAM,CAAC,MAAM,UAAU,GAAG,OAAO,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/tool-utils\";\nexport const pkgVersion = \"2.4.0\";\n"]}
1
+ {"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,OAAO,GAAG,4BAA4B,CAAC;AACpD,MAAM,CAAC,MAAM,UAAU,GAAG,OAAO,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/tool-utils\";\nexport const pkgVersion = \"2.5.0\";\n"]}
@@ -9,6 +9,11 @@ import type { ITree } from "@fluidframework/driver-definitions/internal";
9
9
  * @internal
10
10
  */
11
11
  export declare const gcBlobPrefix = "__gc";
12
+ /**
13
+ * The name of the legacy catch-up ops blob in Merge Tree.
14
+ * @internal
15
+ */
16
+ export declare const legacyCatchUpBlobName = "catchupOps";
12
17
  /**
13
18
  * @internal
14
19
  */
@@ -1 +1 @@
1
- {"version":3,"file":"snapshotNormalizer.d.ts","sourceRoot":"","sources":["../src/snapshotNormalizer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAc,MAAM,6CAA6C,CAAC;AAarF;;;;GAIG;AACH,eAAO,MAAM,YAAY,SAAS,CAAC;AAEnC;;GAEG;AACH,MAAM,WAAW,yBAAyB;IAEzC,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B;;;;OAIG;IACH,2BAA2B,CAAC,EAAE,MAAM,EAAE,CAAC;CACvC;AAwHD;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACpC,QAAQ,EAAE,KAAK,EACf,MAAM,CAAC,EAAE,yBAAyB,GAChC,KAAK,CAkBP"}
1
+ {"version":3,"file":"snapshotNormalizer.d.ts","sourceRoot":"","sources":["../src/snapshotNormalizer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAEX,KAAK,EAEL,MAAM,6CAA6C,CAAC;AAarD;;;;GAIG;AACH,eAAO,MAAM,YAAY,SAAS,CAAC;AAEnC;;;GAGG;AACH,eAAO,MAAM,qBAAqB,eAAe,CAAC;AAElD;;GAEG;AACH,MAAM,WAAW,yBAAyB;IAEzC,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B;;;;OAIG;IACH,2BAA2B,CAAC,EAAE,MAAM,EAAE,CAAC;CACvC;AA4ID;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACpC,QAAQ,EAAE,KAAK,EACf,MAAM,CAAC,EAAE,yBAAyB,GAChC,KAAK,CAkBP"}
@@ -14,6 +14,11 @@ const metadataBlobName = ".metadata";
14
14
  * @internal
15
15
  */
16
16
  export const gcBlobPrefix = "__gc";
17
+ /**
18
+ * The name of the legacy catch-up ops blob in Merge Tree.
19
+ * @internal
20
+ */
21
+ export const legacyCatchUpBlobName = "catchupOps";
17
22
  const sortStringified = (elem1, elem2) => {
18
23
  const serializedElem1 = JSON.stringify(elem1);
19
24
  const serializedElem2 = JSON.stringify(elem2);
@@ -85,6 +90,26 @@ function getNormalizedBlobContent(blobContent, blobName) {
85
90
  }
86
91
  content = JSON.stringify(gcState);
87
92
  }
93
+ /**
94
+ * The legacy catch-up ops blob in merge tree DDS contains sequenced messages. These ops used to have metadata property.
95
+ * However, we stopped sending the metadata property to DDS because it was a Runtime layer concept. Remove the metadata
96
+ * property from the ops because latest snapshots won't have the metadata property.
97
+ */
98
+ if (blobName === legacyCatchUpBlobName) {
99
+ try {
100
+ const catchupOps = JSON.parse(content);
101
+ if (catchupOps !== undefined && catchupOps.length > 0) {
102
+ for (const [index, op] of catchupOps.entries()) {
103
+ op.metadata = undefined;
104
+ catchupOps[index] = op;
105
+ }
106
+ }
107
+ content = JSON.stringify(catchupOps);
108
+ }
109
+ catch {
110
+ // Do nothing
111
+ }
112
+ }
88
113
  /**
89
114
  * The metadata blob has "summaryNumber" or "summaryCount" that tells which summary this is for a container. It can
90
115
  * be different in summaries of two clients even if they are generated at the same sequence#. For instance, at seq#
@@ -186,9 +211,10 @@ function normalizeEntry(entry, config) {
186
211
  switch (entry.type) {
187
212
  case TreeEntry.Blob: {
188
213
  let contents = entry.value.contents;
189
- // If this blob has to be normalized or it's a GC blob, parse and sort the blob contents first.
214
+ // If this blob has to be normalized, it's a GC or legacy catchup blob, parse and sort the blob contents first.
190
215
  if ((config?.blobsToNormalize?.includes(entry.path) ?? false) ||
191
- entry.path.startsWith(gcBlobPrefix)) {
216
+ entry.path.startsWith(gcBlobPrefix) ||
217
+ entry.path === legacyCatchUpBlobName) {
192
218
  contents = getNormalizedBlobContent(contents, entry.path);
193
219
  }
194
220
  return new BlobTreeEntry(entry.path, contents);
@@ -1 +1 @@
1
- {"version":3,"file":"snapshotNormalizer.js","sourceRoot":"","sources":["../src/snapshotNormalizer.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 type { ITree, ITreeEntry } from \"@fluidframework/driver-definitions/internal\";\nimport { TreeEntry } from \"@fluidframework/driver-definitions/internal\";\nimport {\n\tAttachmentTreeEntry,\n\tBlobTreeEntry,\n\tTreeTreeEntry,\n} from \"@fluidframework/driver-utils/internal\";\n\n/**\n * The name of the metadata blob added to the root of the container runtime.\n */\nconst metadataBlobName = \".metadata\";\n\n/**\n * The prefix that all GC blob names start with.\n *\n * @internal\n */\nexport const gcBlobPrefix = \"__gc\";\n\n/**\n * @internal\n */\nexport interface ISnapshotNormalizerConfig {\n\t// The paths of blobs whose contents should be normalized.\n\tblobsToNormalize?: string[];\n\t/**\n\t * channel types who's content (non-attribute) blobs will be excluded.\n\t * this is used to exclude the content of channels who's content cannot be compared\n\t * as the content is non-deterministic between snapshot at the same sequence number.\n\t */\n\texcludedChannelContentTypes?: string[];\n}\n\nconst sortStringified = (elem1: unknown, elem2: unknown): number => {\n\tconst serializedElem1 = JSON.stringify(elem1);\n\tconst serializedElem2 = JSON.stringify(elem2);\n\treturn serializedElem1.localeCompare(serializedElem2);\n};\n\n/**\n * Function that deep sorts an array. It handles cases where array elements are objects or arrays.\n * @returns the sorted array.\n */\nfunction getDeepSortedArray(array: unknown[]): unknown[] {\n\tconst sortedArray: unknown[] = [];\n\t// Sort arrays and objects, if any, in the array.\n\tfor (const element of array) {\n\t\tif (Array.isArray(element)) {\n\t\t\tsortedArray.push(getDeepSortedArray(element));\n\t\t} else if (element instanceof Object) {\n\t\t\tsortedArray.push(getDeepSortedObject(element));\n\t\t} else {\n\t\t\tsortedArray.push(element);\n\t\t}\n\t}\n\n\t// Now that all the arrays and objects in this array's elements have been sorted, sort it by comparing each\n\t// element's stringified version.\n\treturn sortedArray.sort(sortStringified);\n}\n\n/**\n * Function that deep sorts an object. It handles cases where object properties are arrays or objects.\n * @returns the sorted object.\n */\nfunction getDeepSortedObject<T extends object>(obj: T): T {\n\t// eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n\tconst sortedObj: T = {} as T;\n\t// Sort the object keys first. Then sort arrays and objects, if any, in the object.\n\tconst keys = Object.keys(obj).sort();\n\tfor (const key of keys) {\n\t\tconst value: unknown = obj[key];\n\t\tif (Array.isArray(value)) {\n\t\t\tsortedObj[key] = getDeepSortedArray(value);\n\t\t} else if (value instanceof Object) {\n\t\t\tsortedObj[key] = getDeepSortedObject(value);\n\t\t} else {\n\t\t\tsortedObj[key] = value;\n\t\t}\n\t}\n\n\treturn sortedObj;\n}\n\n/**\n * Function that normalizes a blob's content. If the content is an object or an array, deep sorts them.\n * Special handling for certain runtime blobs, such as the \"gc\" blob.\n * @returns the normalized blob content.\n */\nfunction getNormalizedBlobContent(blobContent: string, blobName: string): string {\n\tlet content = blobContent;\n\tif (blobName.startsWith(gcBlobPrefix)) {\n\t\t// The following code parses JSON and makes some assumptions about the type of data within. There does not appear to\n\t\t// be a better type than `any` to use here, so the lint rules are disabled.\n\n\t\t/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */\n\n\t\t// GC blobs may contain `unreferencedTimestampMs` for node that became unreferenced. This is the timestamp\n\t\t// of the last op processed or current timestamp and can differ between clients depending on when GC was run.\n\t\t// So, remove it for the purposes of comparing snapshots.\n\t\tconst gcState: any = JSON.parse(content);\n\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n\t\tfor (const [, data] of Object.entries(gcState.gcNodes)) {\n\t\t\tdelete (data as any).unreferencedTimestampMs;\n\t\t}\n\t\tcontent = JSON.stringify(gcState);\n\t}\n\n\t/**\n\t * The metadata blob has \"summaryNumber\" or \"summaryCount\" that tells which summary this is for a container. It can\n\t * be different in summaries of two clients even if they are generated at the same sequence#. For instance, at seq#\n\t * 1000, if one client has summarized 10 times and other has summarizer 15 times, summaryNumber will be different\n\t * for them. So, update \"summaryNumber\" to 0 for purposes of comparing snapshots.\n\t */\n\tif (blobName === metadataBlobName) {\n\t\tconst metadata = JSON.parse(content);\n\t\tif (metadata.summaryNumber !== undefined) {\n\t\t\tmetadata.summaryNumber = 0;\n\t\t}\n\t\tif (metadata.summaryCount !== undefined) {\n\t\t\tmetadata.summaryCount = 0;\n\t\t}\n\t\t// \"telemetryDocumentId\" is not a deterministic property (random guid), so we need to set it to something consistent\n\t\tif (metadata.telemetryDocumentId !== undefined) {\n\t\t\tmetadata.telemetryDocumentId = \"x\";\n\t\t}\n\t\t// default was not written before, now it's written in.\n\t\tif (metadata.documentSchema !== undefined) {\n\t\t\tmetadata.documentSchema = undefined;\n\t\t}\n\t\tcontent = JSON.stringify(metadata);\n\t}\n\n\t// Deep sort the content if it's parseable.\n\ttry {\n\t\tlet contentObj = JSON.parse(content);\n\t\tif (Array.isArray(contentObj)) {\n\t\t\tcontentObj = getDeepSortedArray(contentObj);\n\t\t} else if (contentObj instanceof Object) {\n\t\t\tcontentObj = getDeepSortedObject(contentObj);\n\t\t}\n\t\tcontent = JSON.stringify(contentObj);\n\t} catch {\n\t\t// Do nothing\n\t}\n\n\t/* eslint-enable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */\n\n\treturn content;\n}\n\n/**\n * Helper function that normalizes the given snapshot tree. It sorts objects and arrays in the snapshot. It also\n * normalizes certain blob contents for which the order of content does not matter. For example, garbage collection\n * blobs contains objects / arrays whose element order do not matter.\n * @param snapshot - The snapshot tree to normalize.\n * @param config - Configs to use when normalizing snapshot. For example, it can contain paths of blobs whose contents\n * should be normalized as well.\n * @returns a copy of the normalized snapshot tree.\n * @internal\n */\nexport function getNormalizedSnapshot(\n\tsnapshot: ITree,\n\tconfig?: ISnapshotNormalizerConfig,\n): ITree {\n\t// Merge blobs to normalize in the config with runtime blobs to normalize. The contents of these blobs will be\n\t// parsed and deep sorted.\n\tconst normalizedEntries: ITreeEntry[] = [];\n\n\t// The metadata blob in the root of the summary tree needs to be normalized.\n\tconst blobsToNormalize = [metadataBlobName, ...(config?.blobsToNormalize ?? [])];\n\tfor (const entry of snapshot.entries) {\n\t\tnormalizedEntries.push(normalizeEntry(entry, { ...config, blobsToNormalize }));\n\t}\n\n\t// Sort the tree entries based on their path.\n\tnormalizedEntries.sort((a, b) => a.path.localeCompare(b.path));\n\n\treturn {\n\t\tentries: normalizedEntries,\n\t\tid: snapshot.id,\n\t};\n}\n\nfunction normalizeMatrix(value: ITree): ITree {\n\tconst rows = value.entries.find((e) => e.path === \"rows\");\n\n\tif (!rows || !(\"entries\" in rows.value)) {\n\t\treturn value;\n\t}\n\n\tconst segments = rows.value.entries.find((e) => e.path === \"segments\");\n\n\tif (!segments || !(\"entries\" in segments.value)) {\n\t\treturn value;\n\t}\n\n\tconst header = segments.value.entries.find((e) => e.path === \"header\");\n\n\tif (!header || !(\"contents\" in header.value)) {\n\t\treturn value;\n\t}\n\n\tif (!header?.value.contents.includes(\"removedClientId\")) {\n\t\treturn value;\n\t}\n\n\t/* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */\n\n\tconst contents = JSON.parse(header?.value.contents);\n\n\tfor (const segment of contents.segments) {\n\t\tif (\"removedClientId\" in segment) {\n\t\t\tsegment.removedClientId = undefined;\n\t\t}\n\n\t\tif (\"removedClientIds\" in segment) {\n\t\t\tsegment.removedClientIds = undefined;\n\t\t}\n\t}\n\n\theader.value.contents = JSON.stringify(contents);\n\n\t/* eslint-enable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */\n\n\treturn value;\n}\n\nfunction normalizeEntry(\n\tentry: ITreeEntry,\n\tconfig: ISnapshotNormalizerConfig | undefined,\n): ITreeEntry {\n\tswitch (entry.type) {\n\t\tcase TreeEntry.Blob: {\n\t\t\tlet contents = entry.value.contents;\n\t\t\t// If this blob has to be normalized or it's a GC blob, parse and sort the blob contents first.\n\t\t\tif (\n\t\t\t\t(config?.blobsToNormalize?.includes(entry.path) ?? false) ||\n\t\t\t\tentry.path.startsWith(gcBlobPrefix)\n\t\t\t) {\n\t\t\t\tcontents = getNormalizedBlobContent(contents, entry.path);\n\t\t\t}\n\t\t\treturn new BlobTreeEntry(entry.path, contents);\n\t\t}\n\t\tcase TreeEntry.Tree: {\n\t\t\tif (config?.excludedChannelContentTypes !== undefined) {\n\t\t\t\tfor (const maybeAttributes of entry.value.entries) {\n\t\t\t\t\tif (\n\t\t\t\t\t\tmaybeAttributes.type === TreeEntry.Blob &&\n\t\t\t\t\t\tmaybeAttributes.path === \".attributes\"\n\t\t\t\t\t) {\n\t\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n\t\t\t\t\t\tconst parsed: { type?: string } = JSON.parse(maybeAttributes.value.contents);\n\t\t\t\t\t\tif (parsed.type === \"https://graph.microsoft.com/types/sharedmatrix\") {\n\t\t\t\t\t\t\treturn new TreeTreeEntry(\n\t\t\t\t\t\t\t\tentry.path,\n\t\t\t\t\t\t\t\tnormalizeMatrix(getNormalizedSnapshot(entry.value, config)),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tparsed.type !== undefined &&\n\t\t\t\t\t\t\tconfig.excludedChannelContentTypes.includes(parsed.type)\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t// remove everything to match the unknown channel\n\t\t\t\t\t\t\treturn new TreeTreeEntry(entry.path, { entries: [maybeAttributes] });\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn new TreeTreeEntry(entry.path, getNormalizedSnapshot(entry.value, config));\n\t\t}\n\t\tcase TreeEntry.Attachment: {\n\t\t\treturn new AttachmentTreeEntry(entry.path, entry.value.id);\n\t\t}\n\n\t\tdefault: {\n\t\t\tthrow new Error(\"Unknown entry type\");\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"snapshotNormalizer.js","sourceRoot":"","sources":["../src/snapshotNormalizer.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 type {\n\tISequencedDocumentMessage,\n\tITree,\n\tITreeEntry,\n} from \"@fluidframework/driver-definitions/internal\";\nimport { TreeEntry } from \"@fluidframework/driver-definitions/internal\";\nimport {\n\tAttachmentTreeEntry,\n\tBlobTreeEntry,\n\tTreeTreeEntry,\n} from \"@fluidframework/driver-utils/internal\";\n\n/**\n * The name of the metadata blob added to the root of the container runtime.\n */\nconst metadataBlobName = \".metadata\";\n\n/**\n * The prefix that all GC blob names start with.\n *\n * @internal\n */\nexport const gcBlobPrefix = \"__gc\";\n\n/**\n * The name of the legacy catch-up ops blob in Merge Tree.\n * @internal\n */\nexport const legacyCatchUpBlobName = \"catchupOps\";\n\n/**\n * @internal\n */\nexport interface ISnapshotNormalizerConfig {\n\t// The paths of blobs whose contents should be normalized.\n\tblobsToNormalize?: string[];\n\t/**\n\t * channel types who's content (non-attribute) blobs will be excluded.\n\t * this is used to exclude the content of channels who's content cannot be compared\n\t * as the content is non-deterministic between snapshot at the same sequence number.\n\t */\n\texcludedChannelContentTypes?: string[];\n}\n\nconst sortStringified = (elem1: unknown, elem2: unknown): number => {\n\tconst serializedElem1 = JSON.stringify(elem1);\n\tconst serializedElem2 = JSON.stringify(elem2);\n\treturn serializedElem1.localeCompare(serializedElem2);\n};\n\n/**\n * Function that deep sorts an array. It handles cases where array elements are objects or arrays.\n * @returns the sorted array.\n */\nfunction getDeepSortedArray(array: unknown[]): unknown[] {\n\tconst sortedArray: unknown[] = [];\n\t// Sort arrays and objects, if any, in the array.\n\tfor (const element of array) {\n\t\tif (Array.isArray(element)) {\n\t\t\tsortedArray.push(getDeepSortedArray(element));\n\t\t} else if (element instanceof Object) {\n\t\t\tsortedArray.push(getDeepSortedObject(element));\n\t\t} else {\n\t\t\tsortedArray.push(element);\n\t\t}\n\t}\n\n\t// Now that all the arrays and objects in this array's elements have been sorted, sort it by comparing each\n\t// element's stringified version.\n\treturn sortedArray.sort(sortStringified);\n}\n\n/**\n * Function that deep sorts an object. It handles cases where object properties are arrays or objects.\n * @returns the sorted object.\n */\nfunction getDeepSortedObject<T extends object>(obj: T): T {\n\t// eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n\tconst sortedObj: T = {} as T;\n\t// Sort the object keys first. Then sort arrays and objects, if any, in the object.\n\tconst keys = Object.keys(obj).sort();\n\tfor (const key of keys) {\n\t\tconst value: unknown = obj[key];\n\t\tif (Array.isArray(value)) {\n\t\t\tsortedObj[key] = getDeepSortedArray(value);\n\t\t} else if (value instanceof Object) {\n\t\t\tsortedObj[key] = getDeepSortedObject(value);\n\t\t} else {\n\t\t\tsortedObj[key] = value;\n\t\t}\n\t}\n\n\treturn sortedObj;\n}\n\n/**\n * Function that normalizes a blob's content. If the content is an object or an array, deep sorts them.\n * Special handling for certain runtime blobs, such as the \"gc\" blob.\n * @returns the normalized blob content.\n */\nfunction getNormalizedBlobContent(blobContent: string, blobName: string): string {\n\tlet content = blobContent;\n\tif (blobName.startsWith(gcBlobPrefix)) {\n\t\t// The following code parses JSON and makes some assumptions about the type of data within. There does not appear to\n\t\t// be a better type than `any` to use here, so the lint rules are disabled.\n\n\t\t/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */\n\n\t\t// GC blobs may contain `unreferencedTimestampMs` for node that became unreferenced. This is the timestamp\n\t\t// of the last op processed or current timestamp and can differ between clients depending on when GC was run.\n\t\t// So, remove it for the purposes of comparing snapshots.\n\t\tconst gcState: any = JSON.parse(content);\n\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n\t\tfor (const [, data] of Object.entries(gcState.gcNodes)) {\n\t\t\tdelete (data as any).unreferencedTimestampMs;\n\t\t}\n\t\tcontent = JSON.stringify(gcState);\n\t}\n\n\t/**\n\t * The legacy catch-up ops blob in merge tree DDS contains sequenced messages. These ops used to have metadata property.\n\t * However, we stopped sending the metadata property to DDS because it was a Runtime layer concept. Remove the metadata\n\t * property from the ops because latest snapshots won't have the metadata property.\n\t */\n\tif (blobName === legacyCatchUpBlobName) {\n\t\ttry {\n\t\t\tconst catchupOps = JSON.parse(content) as ISequencedDocumentMessage[];\n\t\t\tif (catchupOps !== undefined && catchupOps.length > 0) {\n\t\t\t\tfor (const [index, op] of catchupOps.entries()) {\n\t\t\t\t\top.metadata = undefined;\n\t\t\t\t\tcatchupOps[index] = op;\n\t\t\t\t}\n\t\t\t}\n\t\t\tcontent = JSON.stringify(catchupOps);\n\t\t} catch {\n\t\t\t// Do nothing\n\t\t}\n\t}\n\n\t/**\n\t * The metadata blob has \"summaryNumber\" or \"summaryCount\" that tells which summary this is for a container. It can\n\t * be different in summaries of two clients even if they are generated at the same sequence#. For instance, at seq#\n\t * 1000, if one client has summarized 10 times and other has summarizer 15 times, summaryNumber will be different\n\t * for them. So, update \"summaryNumber\" to 0 for purposes of comparing snapshots.\n\t */\n\tif (blobName === metadataBlobName) {\n\t\tconst metadata = JSON.parse(content);\n\t\tif (metadata.summaryNumber !== undefined) {\n\t\t\tmetadata.summaryNumber = 0;\n\t\t}\n\t\tif (metadata.summaryCount !== undefined) {\n\t\t\tmetadata.summaryCount = 0;\n\t\t}\n\t\t// \"telemetryDocumentId\" is not a deterministic property (random guid), so we need to set it to something consistent\n\t\tif (metadata.telemetryDocumentId !== undefined) {\n\t\t\tmetadata.telemetryDocumentId = \"x\";\n\t\t}\n\t\t// default was not written before, now it's written in.\n\t\tif (metadata.documentSchema !== undefined) {\n\t\t\tmetadata.documentSchema = undefined;\n\t\t}\n\t\tcontent = JSON.stringify(metadata);\n\t}\n\n\t// Deep sort the content if it's parseable.\n\ttry {\n\t\tlet contentObj = JSON.parse(content);\n\t\tif (Array.isArray(contentObj)) {\n\t\t\tcontentObj = getDeepSortedArray(contentObj);\n\t\t} else if (contentObj instanceof Object) {\n\t\t\tcontentObj = getDeepSortedObject(contentObj);\n\t\t}\n\t\tcontent = JSON.stringify(contentObj);\n\t} catch {\n\t\t// Do nothing\n\t}\n\n\t/* eslint-enable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */\n\n\treturn content;\n}\n\n/**\n * Helper function that normalizes the given snapshot tree. It sorts objects and arrays in the snapshot. It also\n * normalizes certain blob contents for which the order of content does not matter. For example, garbage collection\n * blobs contains objects / arrays whose element order do not matter.\n * @param snapshot - The snapshot tree to normalize.\n * @param config - Configs to use when normalizing snapshot. For example, it can contain paths of blobs whose contents\n * should be normalized as well.\n * @returns a copy of the normalized snapshot tree.\n * @internal\n */\nexport function getNormalizedSnapshot(\n\tsnapshot: ITree,\n\tconfig?: ISnapshotNormalizerConfig,\n): ITree {\n\t// Merge blobs to normalize in the config with runtime blobs to normalize. The contents of these blobs will be\n\t// parsed and deep sorted.\n\tconst normalizedEntries: ITreeEntry[] = [];\n\n\t// The metadata blob in the root of the summary tree needs to be normalized.\n\tconst blobsToNormalize = [metadataBlobName, ...(config?.blobsToNormalize ?? [])];\n\tfor (const entry of snapshot.entries) {\n\t\tnormalizedEntries.push(normalizeEntry(entry, { ...config, blobsToNormalize }));\n\t}\n\n\t// Sort the tree entries based on their path.\n\tnormalizedEntries.sort((a, b) => a.path.localeCompare(b.path));\n\n\treturn {\n\t\tentries: normalizedEntries,\n\t\tid: snapshot.id,\n\t};\n}\n\nfunction normalizeMatrix(value: ITree): ITree {\n\tconst rows = value.entries.find((e) => e.path === \"rows\");\n\n\tif (!rows || !(\"entries\" in rows.value)) {\n\t\treturn value;\n\t}\n\n\tconst segments = rows.value.entries.find((e) => e.path === \"segments\");\n\n\tif (!segments || !(\"entries\" in segments.value)) {\n\t\treturn value;\n\t}\n\n\tconst header = segments.value.entries.find((e) => e.path === \"header\");\n\n\tif (!header || !(\"contents\" in header.value)) {\n\t\treturn value;\n\t}\n\n\tif (!header?.value.contents.includes(\"removedClientId\")) {\n\t\treturn value;\n\t}\n\n\t/* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */\n\n\tconst contents = JSON.parse(header?.value.contents);\n\n\tfor (const segment of contents.segments) {\n\t\tif (\"removedClientId\" in segment) {\n\t\t\tsegment.removedClientId = undefined;\n\t\t}\n\n\t\tif (\"removedClientIds\" in segment) {\n\t\t\tsegment.removedClientIds = undefined;\n\t\t}\n\t}\n\n\theader.value.contents = JSON.stringify(contents);\n\n\t/* eslint-enable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */\n\n\treturn value;\n}\n\nfunction normalizeEntry(\n\tentry: ITreeEntry,\n\tconfig: ISnapshotNormalizerConfig | undefined,\n): ITreeEntry {\n\tswitch (entry.type) {\n\t\tcase TreeEntry.Blob: {\n\t\t\tlet contents = entry.value.contents;\n\t\t\t// If this blob has to be normalized, it's a GC or legacy catchup blob, parse and sort the blob contents first.\n\t\t\tif (\n\t\t\t\t(config?.blobsToNormalize?.includes(entry.path) ?? false) ||\n\t\t\t\tentry.path.startsWith(gcBlobPrefix) ||\n\t\t\t\tentry.path === legacyCatchUpBlobName\n\t\t\t) {\n\t\t\t\tcontents = getNormalizedBlobContent(contents, entry.path);\n\t\t\t}\n\t\t\treturn new BlobTreeEntry(entry.path, contents);\n\t\t}\n\t\tcase TreeEntry.Tree: {\n\t\t\tif (config?.excludedChannelContentTypes !== undefined) {\n\t\t\t\tfor (const maybeAttributes of entry.value.entries) {\n\t\t\t\t\tif (\n\t\t\t\t\t\tmaybeAttributes.type === TreeEntry.Blob &&\n\t\t\t\t\t\tmaybeAttributes.path === \".attributes\"\n\t\t\t\t\t) {\n\t\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n\t\t\t\t\t\tconst parsed: { type?: string } = JSON.parse(maybeAttributes.value.contents);\n\t\t\t\t\t\tif (parsed.type === \"https://graph.microsoft.com/types/sharedmatrix\") {\n\t\t\t\t\t\t\treturn new TreeTreeEntry(\n\t\t\t\t\t\t\t\tentry.path,\n\t\t\t\t\t\t\t\tnormalizeMatrix(getNormalizedSnapshot(entry.value, config)),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tparsed.type !== undefined &&\n\t\t\t\t\t\t\tconfig.excludedChannelContentTypes.includes(parsed.type)\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t// remove everything to match the unknown channel\n\t\t\t\t\t\t\treturn new TreeTreeEntry(entry.path, { entries: [maybeAttributes] });\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn new TreeTreeEntry(entry.path, getNormalizedSnapshot(entry.value, config));\n\t\t}\n\t\tcase TreeEntry.Attachment: {\n\t\t\treturn new AttachmentTreeEntry(entry.path, entry.value.id);\n\t\t}\n\n\t\tdefault: {\n\t\t\tthrow new Error(\"Unknown entry type\");\n\t\t}\n\t}\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluidframework/tool-utils",
3
- "version": "2.4.0",
3
+ "version": "2.5.0",
4
4
  "description": "Common utilities for Fluid tools",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": {
@@ -59,10 +59,10 @@
59
59
  "temp-directory": "nyc/.nyc_output"
60
60
  },
61
61
  "dependencies": {
62
- "@fluidframework/core-utils": "~2.4.0",
63
- "@fluidframework/driver-definitions": "~2.4.0",
64
- "@fluidframework/driver-utils": "~2.4.0",
65
- "@fluidframework/odsp-doclib-utils": "~2.4.0",
62
+ "@fluidframework/core-utils": "~2.5.0",
63
+ "@fluidframework/driver-definitions": "~2.5.0",
64
+ "@fluidframework/driver-utils": "~2.5.0",
65
+ "@fluidframework/odsp-doclib-utils": "~2.5.0",
66
66
  "async-mutex": "^0.3.1",
67
67
  "debug": "^4.3.4",
68
68
  "jwt-decode": "^4.0.0",
@@ -70,13 +70,13 @@
70
70
  },
71
71
  "devDependencies": {
72
72
  "@arethetypeswrong/cli": "^0.16.4",
73
- "@biomejs/biome": "~1.8.3",
74
- "@fluid-internal/mocha-test-setup": "~2.4.0",
75
- "@fluid-tools/build-cli": "^0.48.0",
73
+ "@biomejs/biome": "~1.9.3",
74
+ "@fluid-internal/mocha-test-setup": "~2.5.0",
75
+ "@fluid-tools/build-cli": "^0.49.0",
76
76
  "@fluidframework/build-common": "^2.0.3",
77
- "@fluidframework/build-tools": "^0.48.0",
77
+ "@fluidframework/build-tools": "^0.49.0",
78
78
  "@fluidframework/eslint-config-fluid": "^5.4.0",
79
- "@fluidframework/tool-utils-previous": "npm:@fluidframework/tool-utils@~2.3.0",
79
+ "@fluidframework/tool-utils-previous": "npm:@fluidframework/tool-utils@~2.4.0",
80
80
  "@microsoft/api-extractor": "7.47.8",
81
81
  "@types/debug": "^4.1.5",
82
82
  "@types/mocha": "^9.1.1",
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/tool-utils";
9
- export const pkgVersion = "2.4.0";
9
+ export const pkgVersion = "2.5.0";
@@ -3,7 +3,11 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import type { ITree, ITreeEntry } from "@fluidframework/driver-definitions/internal";
6
+ import type {
7
+ ISequencedDocumentMessage,
8
+ ITree,
9
+ ITreeEntry,
10
+ } from "@fluidframework/driver-definitions/internal";
7
11
  import { TreeEntry } from "@fluidframework/driver-definitions/internal";
8
12
  import {
9
13
  AttachmentTreeEntry,
@@ -23,6 +27,12 @@ const metadataBlobName = ".metadata";
23
27
  */
24
28
  export const gcBlobPrefix = "__gc";
25
29
 
30
+ /**
31
+ * The name of the legacy catch-up ops blob in Merge Tree.
32
+ * @internal
33
+ */
34
+ export const legacyCatchUpBlobName = "catchupOps";
35
+
26
36
  /**
27
37
  * @internal
28
38
  */
@@ -112,6 +122,26 @@ function getNormalizedBlobContent(blobContent: string, blobName: string): string
112
122
  content = JSON.stringify(gcState);
113
123
  }
114
124
 
125
+ /**
126
+ * The legacy catch-up ops blob in merge tree DDS contains sequenced messages. These ops used to have metadata property.
127
+ * However, we stopped sending the metadata property to DDS because it was a Runtime layer concept. Remove the metadata
128
+ * property from the ops because latest snapshots won't have the metadata property.
129
+ */
130
+ if (blobName === legacyCatchUpBlobName) {
131
+ try {
132
+ const catchupOps = JSON.parse(content) as ISequencedDocumentMessage[];
133
+ if (catchupOps !== undefined && catchupOps.length > 0) {
134
+ for (const [index, op] of catchupOps.entries()) {
135
+ op.metadata = undefined;
136
+ catchupOps[index] = op;
137
+ }
138
+ }
139
+ content = JSON.stringify(catchupOps);
140
+ } catch {
141
+ // Do nothing
142
+ }
143
+ }
144
+
115
145
  /**
116
146
  * The metadata blob has "summaryNumber" or "summaryCount" that tells which summary this is for a container. It can
117
147
  * be different in summaries of two clients even if they are generated at the same sequence#. For instance, at seq#
@@ -239,10 +269,11 @@ function normalizeEntry(
239
269
  switch (entry.type) {
240
270
  case TreeEntry.Blob: {
241
271
  let contents = entry.value.contents;
242
- // If this blob has to be normalized or it's a GC blob, parse and sort the blob contents first.
272
+ // If this blob has to be normalized, it's a GC or legacy catchup blob, parse and sort the blob contents first.
243
273
  if (
244
274
  (config?.blobsToNormalize?.includes(entry.path) ?? false) ||
245
- entry.path.startsWith(gcBlobPrefix)
275
+ entry.path.startsWith(gcBlobPrefix) ||
276
+ entry.path === legacyCatchUpBlobName
246
277
  ) {
247
278
  contents = getNormalizedBlobContent(contents, entry.path);
248
279
  }