@fluid-tools/fetch-tool 1.4.0-115997 → 2.0.0-dev-rc.1.0.0.224419

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 (37) hide show
  1. package/.eslintrc.js +6 -8
  2. package/CHANGELOG.md +117 -0
  3. package/README.md +38 -7
  4. package/bin/fluid-fetch +0 -0
  5. package/dist/fluidAnalyzeMessages.d.ts.map +1 -1
  6. package/dist/fluidAnalyzeMessages.js +106 -116
  7. package/dist/fluidAnalyzeMessages.js.map +1 -1
  8. package/dist/fluidFetch.js +5 -3
  9. package/dist/fluidFetch.js.map +1 -1
  10. package/dist/fluidFetchArgs.d.ts +0 -3
  11. package/dist/fluidFetchArgs.d.ts.map +1 -1
  12. package/dist/fluidFetchArgs.js +10 -14
  13. package/dist/fluidFetchArgs.js.map +1 -1
  14. package/dist/fluidFetchInit.d.ts +0 -1
  15. package/dist/fluidFetchInit.d.ts.map +1 -1
  16. package/dist/fluidFetchInit.js +41 -34
  17. package/dist/fluidFetchInit.js.map +1 -1
  18. package/dist/fluidFetchMessages.d.ts.map +1 -1
  19. package/dist/fluidFetchMessages.js +168 -200
  20. package/dist/fluidFetchMessages.js.map +1 -1
  21. package/dist/fluidFetchSharePoint.d.ts +0 -1
  22. package/dist/fluidFetchSharePoint.d.ts.map +1 -1
  23. package/dist/fluidFetchSharePoint.js +20 -6
  24. package/dist/fluidFetchSharePoint.js.map +1 -1
  25. package/dist/fluidFetchSnapshot.d.ts.map +1 -1
  26. package/dist/fluidFetchSnapshot.js +18 -20
  27. package/dist/fluidFetchSnapshot.js.map +1 -1
  28. package/package.json +47 -42
  29. package/prettier.config.cjs +8 -0
  30. package/src/fluidAnalyzeMessages.ts +701 -630
  31. package/src/fluidFetch.ts +93 -88
  32. package/src/fluidFetchArgs.ts +167 -168
  33. package/src/fluidFetchInit.ts +133 -104
  34. package/src/fluidFetchMessages.ts +253 -232
  35. package/src/fluidFetchSharePoint.ts +130 -112
  36. package/src/fluidFetchSnapshot.ts +313 -295
  37. package/tsconfig.json +8 -15
package/src/fluidFetch.ts CHANGED
@@ -13,115 +13,120 @@ import { getSharepointFiles, getSingleSharePointFile } from "./fluidFetchSharePo
13
13
  import { fluidFetchSnapshot } from "./fluidFetchSnapshot";
14
14
 
15
15
  async function fluidFetchOneFile(urlStr: string, name?: string) {
16
- const documentService = await fluidFetchInit(urlStr);
17
- const saveDir = paramSaveDir !== undefined
18
- ? (name !== undefined
19
- ? `${paramSaveDir}/${name}`
20
- : paramSaveDir)
21
- : undefined;
22
- if (saveDir !== undefined) {
23
- const mkdir = util.promisify(fs.mkdir);
24
- const writeFile = util.promisify(fs.writeFile);
25
- await mkdir(saveDir, { recursive: true });
26
- const info = {
27
- creationDate: new Date().toString(),
28
- connectionInfo,
29
- url: urlStr,
30
- };
31
- await writeFile(`${saveDir}/info.json`, JSON.stringify(info, undefined, 2));
32
- }
16
+ const documentService = await fluidFetchInit(urlStr);
17
+ const saveDir =
18
+ paramSaveDir !== undefined
19
+ ? name !== undefined
20
+ ? `${paramSaveDir}/${name}`
21
+ : paramSaveDir
22
+ : undefined;
23
+ if (saveDir !== undefined) {
24
+ const mkdir = util.promisify(fs.mkdir);
25
+ const writeFile = util.promisify(fs.writeFile);
26
+ await mkdir(saveDir, { recursive: true });
27
+ const info = {
28
+ creationDate: new Date().toString(),
29
+ connectionInfo,
30
+ url: urlStr,
31
+ };
32
+ await writeFile(`${saveDir}/info.json`, JSON.stringify(info, undefined, 2));
33
+ }
33
34
 
34
- await fluidFetchSnapshot(documentService, saveDir);
35
- await fluidFetchMessages(documentService, saveDir);
35
+ await fluidFetchSnapshot(documentService, saveDir);
36
+ await fluidFetchMessages(documentService, saveDir);
36
37
  }
37
38
 
38
39
  async function tryFluidFetchOneSharePointFile(server: string, driveItem: IOdspDriveItem) {
39
- const { path, name, driveId, itemId } = driveItem;
40
- console.log(`File: ${path}/${name}`);
41
- await fluidFetchOneFile(`https://${server}/_api/v2.1/drives/${driveId}/items/${itemId}`, name);
40
+ const { path, name, driveId, itemId } = driveItem;
41
+ console.log(`File: ${path}/${name}`);
42
+ await fluidFetchOneFile(`https://${server}/_api/v2.1/drives/${driveId}/items/${itemId}`, name);
42
43
  }
43
44
 
44
- function getSharePointSpecificDriveItem(url: URL): { driveId: string; itemId: string; } | undefined {
45
- if (url.searchParams.has("driveId") && url.searchParams.has("itemId")) {
46
- return {
47
- driveId: url.searchParams.get("driveId") as string,
48
- itemId: url.searchParams.get("itemId") as string,
49
- };
50
- }
45
+ function getSharePointSpecificDriveItem(url: URL): { driveId: string; itemId: string } | undefined {
46
+ if (url.searchParams.has("driveId") && url.searchParams.has("itemId")) {
47
+ return {
48
+ driveId: url.searchParams.get("driveId") as string,
49
+ itemId: url.searchParams.get("itemId") as string,
50
+ };
51
+ }
51
52
  }
52
53
 
53
54
  function getSharepointServerRelativePathFromURL(url: URL) {
54
- if (url.pathname.startsWith("/_api/v2.1/drives/")) {
55
- return undefined;
56
- }
55
+ if (url.pathname.startsWith("/_api/v2.1/drives/")) {
56
+ return undefined;
57
+ }
57
58
 
58
- const hostnameParts = url.hostname.split(".");
59
- const suffix = hostnameParts[0].endsWith("-my") ? "/_layouts/15/onedrive.aspx" : "/forms/allitems.aspx";
59
+ const hostnameParts = url.hostname.split(".");
60
+ const suffix = hostnameParts[0].endsWith("-my")
61
+ ? "/_layouts/15/onedrive.aspx"
62
+ : "/forms/allitems.aspx";
60
63
 
61
- let sitePath = url.pathname;
62
- if (url.searchParams.has("id")) {
63
- sitePath = url.searchParams.get("id") as string;
64
- } else if (url.searchParams.has("RootFolder")) {
65
- sitePath = url.searchParams.get("RootFolder") as string;
66
- } else if (url.pathname.toLowerCase().endsWith(suffix)) {
67
- sitePath = sitePath.substr(0, url.pathname.length - suffix.length);
68
- }
64
+ let sitePath = url.pathname;
65
+ if (url.searchParams.has("id")) {
66
+ sitePath = url.searchParams.get("id") as string;
67
+ } else if (url.searchParams.has("RootFolder")) {
68
+ sitePath = url.searchParams.get("RootFolder") as string;
69
+ } else if (url.pathname.toLowerCase().endsWith(suffix)) {
70
+ sitePath = sitePath.substr(0, url.pathname.length - suffix.length);
71
+ }
69
72
 
70
- return decodeURI(sitePath);
73
+ return decodeURI(sitePath);
71
74
  }
72
75
 
73
76
  async function fluidFetchMain() {
74
- if (paramURL === undefined) {
75
- return;
76
- }
77
+ if (paramURL === undefined) {
78
+ return;
79
+ }
77
80
 
78
- const url = new URL(paramURL);
79
- const server = url.hostname;
80
- if (isOdspHostname(server)) {
81
- // See if the url already has the specific item
82
- const driveItem = getSharePointSpecificDriveItem(url);
83
- if (driveItem) {
84
- const file = await getSingleSharePointFile(server, driveItem.driveId, driveItem.itemId);
85
- await tryFluidFetchOneSharePointFile(server, file);
86
- return;
87
- }
81
+ const url = new URL(paramURL);
82
+ const server = url.hostname;
83
+ if (isOdspHostname(server)) {
84
+ // See if the url already has the specific item
85
+ const driveItem = getSharePointSpecificDriveItem(url);
86
+ if (driveItem) {
87
+ const file = await getSingleSharePointFile(server, driveItem.driveId, driveItem.itemId);
88
+ await tryFluidFetchOneSharePointFile(server, file);
89
+ return;
90
+ }
88
91
 
89
- // See if the url given represent a sharepoint directory
90
- const serverRelativePath = getSharepointServerRelativePathFromURL(url);
91
- if (serverRelativePath !== undefined) {
92
- const files = await getSharepointFiles(server, serverRelativePath, false);
93
- for (const file of files) {
94
- if (file.name.endsWith(".b") || file.name.endsWith(".fluid")) {
95
- await tryFluidFetchOneSharePointFile(server, file);
96
- }
97
- }
98
- return;
99
- }
100
- }
92
+ // See if the url given represent a sharepoint directory
93
+ const serverRelativePath = getSharepointServerRelativePathFromURL(url);
94
+ if (serverRelativePath !== undefined) {
95
+ const files = await getSharepointFiles(server, serverRelativePath, false);
96
+ for (const file of files) {
97
+ if (file.name.endsWith(".b") || file.name.endsWith(".fluid")) {
98
+ await tryFluidFetchOneSharePointFile(server, file);
99
+ }
100
+ }
101
+ return;
102
+ }
103
+ }
101
104
 
102
- return fluidFetchOneFile(paramURL);
105
+ return fluidFetchOneFile(paramURL);
103
106
  }
104
107
 
105
108
  parseArguments();
106
109
 
107
110
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
108
111
  fluidFetchMain()
109
- .catch((error: Error) => {
110
- if (error instanceof Error) {
111
- let extraMsg = "";
112
- for (const key of Object.keys(error)) {
113
- // error[key] might have circular structure
114
- try {
115
- if (key !== "message" && key !== "stack") {
116
- extraMsg += `\n${key}: ${JSON.stringify(error[key], undefined, 2)}`;
117
- }
118
- } catch (_) { }
119
- }
120
- console.error(`ERROR: ${error.stack}${extraMsg}`);
121
- } else if (typeof error === "object") {
122
- console.error(`ERROR: Unknown exception object\n${JSON.stringify(error, undefined, 2)}`);
123
- } else {
124
- console.error(`ERROR: ${error}`);
125
- }
126
- })
127
- .then(() => process.exit(0));
112
+ .catch((error: Error) => {
113
+ if (error instanceof Error) {
114
+ let extraMsg = "";
115
+ for (const key of Object.keys(error)) {
116
+ // error[key] might have circular structure
117
+ try {
118
+ if (key !== "message" && key !== "stack") {
119
+ extraMsg += `\n${key}: ${JSON.stringify(error[key], undefined, 2)}`;
120
+ }
121
+ } catch (_) {}
122
+ }
123
+ console.error(`ERROR: ${error.stack}${extraMsg}`);
124
+ } else if (typeof error === "object") {
125
+ console.error(
126
+ `ERROR: Unknown exception object\n${JSON.stringify(error, undefined, 2)}`,
127
+ );
128
+ } else {
129
+ console.error(`ERROR: ${error}`);
130
+ }
131
+ })
132
+ .then(() => process.exit(0));
@@ -14,16 +14,15 @@ export let dumpSnapshotVersions = false;
14
14
  export let overWrite = false;
15
15
  export let paramSnapshotVersionIndex: number | undefined;
16
16
  export let paramNumSnapshotVersions = 10;
17
- export let paramUnpackAggregatedBlobs = true;
18
17
  export let paramActualFormatting = false;
19
18
 
20
19
  let paramForceTokenReauth = false;
21
20
 
22
21
  // Only return true once, to reauth on first call.
23
22
  export function getForceTokenReauth() {
24
- const result = paramForceTokenReauth;
25
- paramForceTokenReauth = false;
26
- return result;
23
+ const result = paramForceTokenReauth;
24
+ paramForceTokenReauth = false;
25
+ return result;
27
26
  }
28
27
 
29
28
  export let paramSaveDir: string | undefined;
@@ -36,184 +35,184 @@ export let connectToWebSocket = false;
36
35
 
37
36
  export let localDataOnly = false;
38
37
 
39
- export let paramSite: string | undefined;
40
-
41
- const optionsArray =
42
- [
43
- ["--dump:rawmessage", "dump all messages"],
44
- ["--dump:snapshotVersion", "dump a list of snapshot version"],
45
- ["--dump:snapshotTree", "dump the snapshot trees"],
46
- ["--forceTokenReauth", "Force reauthorize token (SPO only)"],
47
- ["--stat:message", "show message type, channel type, data type statistics"],
48
- ["--stat:snapshot", "show a table of snapshot path and blob size"],
49
- ["--stat", "Show both messages & snapshot stats"],
50
- ["--filter:messageType <type>", "filter message by <type>"],
51
- ["--jwt <token>", "token to be used for routerlicious URLs"],
52
- ["--numSnapshotVersions <number>", "Number of versions to load (default:10)"],
53
- ["--noUnpack", "Do not unpack aggregated blobs"],
54
- ["--actualPayload", "Do not format json payloads nicely, preserve actual bytes / formatting in storage"],
55
- ["--saveDir <outdir>", "Save data of the snapshots and messages"],
56
- ["--snapshotVersionIndex <number>", "Index of the version to dump"],
57
- ["--websocket", "Connect to web socket to download initial messages"],
58
- ["--local", "Do not connect to storage, use earlier downloaded data. Requires --saveDir."],
59
- ];
60
-
61
- export function printUsage() {
62
- console.log("Usage: fluid-fetch [options] URL");
63
- console.log("URL: <ODSP URL>|<Routerlicious URL>");
64
- console.log("Options:");
65
- for (const i of optionsArray) {
66
- console.log(` ${i[0].padEnd(32)}: ${i[1]}`);
67
- }
38
+ const optionsArray = [
39
+ ["--dump:rawmessage", "dump all messages"],
40
+ ["--dump:snapshotVersion", "dump a list of snapshot version"],
41
+ ["--dump:snapshotTree", "dump the snapshot trees"],
42
+ ["--forceTokenReauth", "Force reauthorize token (SPO only)"],
43
+ ["--stat:message", "show message type, channel type, data type statistics"],
44
+ ["--stat:snapshot", "show a table of snapshot path and blob size"],
45
+ ["--stat", "Show both messages & snapshot stats"],
46
+ ["--filter:messageType <type>", "filter message by <type>"],
47
+ ["--jwt <token>", "token to be used for routerlicious URLs"],
48
+ ["--numSnapshotVersions <number>", "Number of versions to load (default:10)"],
49
+ [
50
+ "--actualPayload",
51
+ "Do not format json payloads nicely, preserve actual bytes / formatting in storage",
52
+ ],
53
+ ["--saveDir <outdir>", "Save data of the snapshots and messages"],
54
+ ["--snapshotVersionIndex <number>", "Index of the version to dump"],
55
+ ["--websocket", "Connect to web socket to download initial messages"],
56
+ ["--local", "Do not connect to storage, use earlier downloaded data. Requires --saveDir."],
57
+ ];
58
+
59
+ function printUsage() {
60
+ console.log("Usage: fluid-fetch [options] URL");
61
+ console.log("URL: <ODSP URL>|<Routerlicious URL>");
62
+ console.log("Options:");
63
+ for (const i of optionsArray) {
64
+ console.log(` ${i[0].padEnd(32)}: ${i[1]}`);
65
+ }
68
66
  }
69
67
 
70
68
  // Can be used in unit test to pass in customized argument values
71
69
  // More argument options can be added when needed
72
70
  export function setArguments(values: {
73
- saveDir: string;
74
- paramURL: string;
75
- dumpMessages?: boolean;
76
- dumpMessageStats?: boolean;
77
- dumpSnapshotStats?: boolean;
78
- dumpSnapshotTrees?: boolean;
79
- overWrite?: boolean; }) {
80
- paramSaveDir = values.saveDir;
81
- paramURL = values.paramURL;
82
- dumpMessages = values.dumpMessages ?? dumpMessages;
83
- dumpMessageStats = values.dumpMessageStats ?? dumpMessageStats;
84
- dumpSnapshotStats = values.dumpSnapshotStats ?? dumpSnapshotStats;
85
- dumpSnapshotTrees = values.dumpSnapshotTrees ?? dumpSnapshotTrees;
86
- overWrite = values.overWrite ?? overWrite;
71
+ saveDir: string;
72
+ paramURL: string;
73
+ dumpMessages?: boolean;
74
+ dumpMessageStats?: boolean;
75
+ dumpSnapshotStats?: boolean;
76
+ dumpSnapshotTrees?: boolean;
77
+ overWrite?: boolean;
78
+ }) {
79
+ paramSaveDir = values.saveDir;
80
+ paramURL = values.paramURL;
81
+ dumpMessages = values.dumpMessages ?? dumpMessages;
82
+ dumpMessageStats = values.dumpMessageStats ?? dumpMessageStats;
83
+ dumpSnapshotStats = values.dumpSnapshotStats ?? dumpSnapshotStats;
84
+ dumpSnapshotTrees = values.dumpSnapshotTrees ?? dumpSnapshotTrees;
85
+ overWrite = values.overWrite ?? overWrite;
87
86
  }
88
87
 
89
88
  export function parseArguments() {
90
- for (let i = 2; i < process.argv.length; i++) {
91
- const arg = process.argv[i];
92
- switch (arg) {
93
- case "--dump:rawmessage":
94
- dumpMessages = true;
95
- break;
96
- case "--dump:rawmessage:overwrite":
97
- dumpMessages = true;
98
- overWrite = true;
99
- break;
100
- case "--stat:message":
101
- dumpMessageStats = true;
102
- break;
103
- case "--stat":
104
- dumpMessageStats = true;
105
- dumpSnapshotStats = true;
106
- break;
107
- case "--filter:messageType":
108
- messageTypeFilter.add(parseStrArg(i++, "type name for messageType filter"));
109
- break;
110
- case "--stat:snapshot":
111
- dumpSnapshotStats = true;
112
- break;
113
- case "--dump:snapshotVersion":
114
- dumpSnapshotVersions = true;
115
- break;
116
- case "--dump:snapshotTree":
117
- dumpSnapshotTrees = true;
118
- break;
119
- case "--help":
120
- printUsage();
121
- process.exit(0);
122
- case "--jwt":
123
- paramJWT = parseStrArg(i++, "jwt token");
124
- break;
125
- case "--forceTokenReauth":
126
- paramForceTokenReauth = true;
127
- break;
128
- case "--snapshotVersionIndex":
129
- paramSnapshotVersionIndex = parseIntArg(i++, "version index", true);
130
- break;
131
- case "--numSnapshotVersions":
132
- paramNumSnapshotVersions = parseIntArg(i++, "number of versions", false);
133
- break;
134
- case "--noUnpack":
135
- paramUnpackAggregatedBlobs = false;
136
- break;
137
- case "--actualPayload":
138
- paramActualFormatting = true;
139
- break;
140
- case "--saveDir":
141
- paramSaveDir = parseStrArg(i++, "save data path");
142
- break;
143
- case "--websocket":
144
- connectToWebSocket = true;
145
- break;
146
- case "--local":
147
- localDataOnly = true;
148
- break;
149
- default:
150
- try {
151
- const url = new URL(arg);
152
- if (url.protocol === "https:") {
153
- paramURL = arg;
154
- break;
155
- }
156
- if (url.protocol === "http:" && url.hostname === "localhost") {
157
- paramURL = arg;
158
- break;
159
- }
160
- } catch (e) {
161
- console.error(e);
162
- }
163
-
164
- console.error(`ERROR: Invalid argument ${arg}`);
165
- printUsage();
166
- process.exit(-1);
167
- break;
168
- }
169
- }
170
- checkArgs();
89
+ for (let i = 2; i < process.argv.length; i++) {
90
+ const arg = process.argv[i];
91
+ switch (arg) {
92
+ case "--dump:rawmessage":
93
+ dumpMessages = true;
94
+ break;
95
+ case "--dump:rawmessage:overwrite":
96
+ dumpMessages = true;
97
+ overWrite = true;
98
+ break;
99
+ case "--stat:message":
100
+ dumpMessageStats = true;
101
+ break;
102
+ case "--stat":
103
+ dumpMessageStats = true;
104
+ dumpSnapshotStats = true;
105
+ break;
106
+ case "--filter:messageType":
107
+ messageTypeFilter.add(parseStrArg(i++, "type name for messageType filter"));
108
+ break;
109
+ case "--stat:snapshot":
110
+ dumpSnapshotStats = true;
111
+ break;
112
+ case "--dump:snapshotVersion":
113
+ dumpSnapshotVersions = true;
114
+ break;
115
+ case "--dump:snapshotTree":
116
+ dumpSnapshotTrees = true;
117
+ break;
118
+ case "--help":
119
+ printUsage();
120
+ process.exit(0);
121
+ case "--jwt":
122
+ paramJWT = parseStrArg(i++, "jwt token");
123
+ break;
124
+ case "--forceTokenReauth":
125
+ paramForceTokenReauth = true;
126
+ break;
127
+ case "--snapshotVersionIndex":
128
+ paramSnapshotVersionIndex = parseIntArg(i++, "version index", true);
129
+ break;
130
+ case "--numSnapshotVersions":
131
+ paramNumSnapshotVersions = parseIntArg(i++, "number of versions", false);
132
+ break;
133
+ case "--actualPayload":
134
+ paramActualFormatting = true;
135
+ break;
136
+ case "--saveDir":
137
+ paramSaveDir = parseStrArg(i++, "save data path");
138
+ break;
139
+ case "--websocket":
140
+ connectToWebSocket = true;
141
+ break;
142
+ case "--local":
143
+ localDataOnly = true;
144
+ break;
145
+ default:
146
+ try {
147
+ const url = new URL(arg);
148
+ if (url.protocol === "https:") {
149
+ paramURL = arg;
150
+ break;
151
+ }
152
+ if (url.protocol === "http:" && url.hostname === "localhost") {
153
+ paramURL = arg;
154
+ break;
155
+ }
156
+ } catch (e) {
157
+ console.error(e);
158
+ }
159
+
160
+ console.error(`ERROR: Invalid argument ${arg}`);
161
+ printUsage();
162
+ process.exit(-1);
163
+ break;
164
+ }
165
+ }
166
+ checkArgs();
171
167
  }
172
168
 
173
169
  function parseStrArg(i: number, name: string) {
174
- if (i + 1 >= process.argv.length) {
175
- console.error(`ERROR: Missing ${name}`);
176
- printUsage();
177
- process.exit(-1);
178
- }
179
- return process.argv[i + 1];
170
+ if (i + 1 >= process.argv.length) {
171
+ console.error(`ERROR: Missing ${name}`);
172
+ printUsage();
173
+ process.exit(-1);
174
+ }
175
+ return process.argv[i + 1];
180
176
  }
181
177
  function parseIntArg(i: number, name: string, allowZero: boolean) {
182
- if (i + 1 >= process.argv.length) {
183
- console.error(`ERROR: Missing ${name}`);
184
- printUsage();
185
- process.exit(-1);
186
- }
187
- const numStr = process.argv[i + 1];
188
- const paramNumber = parseInt(numStr, 10);
189
- if (isNaN(paramNumber) || (allowZero ? paramNumber < 0 : paramNumber <= 0)) {
190
- console.error(`ERROR: Invalid ${name} ${numStr}`);
191
- printUsage();
192
- process.exit(-1);
193
- }
194
- return paramNumber;
178
+ if (i + 1 >= process.argv.length) {
179
+ console.error(`ERROR: Missing ${name}`);
180
+ printUsage();
181
+ process.exit(-1);
182
+ }
183
+ const numStr = process.argv[i + 1];
184
+ const paramNumber = parseInt(numStr, 10);
185
+ if (isNaN(paramNumber) || (allowZero ? paramNumber < 0 : paramNumber <= 0)) {
186
+ console.error(`ERROR: Invalid ${name} ${numStr}`);
187
+ printUsage();
188
+ process.exit(-1);
189
+ }
190
+ return paramNumber;
195
191
  }
196
192
 
197
193
  function checkArgs() {
198
- if (paramSnapshotVersionIndex !== undefined) {
199
- paramNumSnapshotVersions = Math.max(paramSnapshotVersionIndex + 1, paramNumSnapshotVersions);
200
- }
201
-
202
- if (paramURL === undefined) {
203
- if (paramSaveDir !== undefined) {
204
- const file = `${paramSaveDir}/info.json`;
205
- if (fs.existsSync(file)) {
206
- const info = JSON.parse(fs.readFileSync(file, { encoding: "utf-8" }));
207
- paramURL = info.url;
208
- } else {
209
- console.log(`Can't find file ${file}`);
210
- }
211
- }
212
-
213
- if (paramURL === undefined) {
214
- console.error("ERROR: Missing URL");
215
- printUsage();
216
- process.exit(-1);
217
- }
218
- }
194
+ if (paramSnapshotVersionIndex !== undefined) {
195
+ paramNumSnapshotVersions = Math.max(
196
+ paramSnapshotVersionIndex + 1,
197
+ paramNumSnapshotVersions,
198
+ );
199
+ }
200
+
201
+ if (paramURL === undefined) {
202
+ if (paramSaveDir !== undefined) {
203
+ const file = `${paramSaveDir}/info.json`;
204
+ if (fs.existsSync(file)) {
205
+ const info = JSON.parse(fs.readFileSync(file, { encoding: "utf-8" }));
206
+ paramURL = info.url;
207
+ } else {
208
+ console.log(`Can't find file ${file}`);
209
+ }
210
+ }
211
+
212
+ if (paramURL === undefined) {
213
+ console.error("ERROR: Missing URL");
214
+ printUsage();
215
+ process.exit(-1);
216
+ }
217
+ }
219
218
  }