@itwin/workspace-editor 4.0.0-dev.51 → 4.0.0-dev.54
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/lib/WorkspaceEditor.js +616 -616
- package/package.json +7 -7
package/lib/WorkspaceEditor.js
CHANGED
|
@@ -1,617 +1,617 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
/*---------------------------------------------------------------------------------------------
|
|
4
|
-
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
|
|
5
|
-
* See LICENSE.md in the project root for license terms and full copyright notice.
|
|
6
|
-
*--------------------------------------------------------------------------------------------*/
|
|
7
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
-
const fs = require("fs");
|
|
9
|
-
const glob = require("glob");
|
|
10
|
-
const path_1 = require("path");
|
|
11
|
-
const readline = require("readline");
|
|
12
|
-
const Yargs = require("yargs");
|
|
13
|
-
const core_backend_1 = require("@itwin/core-backend");
|
|
14
|
-
const core_bentley_1 = require("@itwin/core-bentley");
|
|
15
|
-
const core_common_1 = require("@itwin/core-common");
|
|
16
|
-
// cspell:ignore nodir nocase
|
|
17
|
-
/* eslint-disable id-blacklist,no-console */
|
|
18
|
-
/** Currently executing an "@" script? */
|
|
19
|
-
let inScript = false;
|
|
20
|
-
let logTimer;
|
|
21
|
-
async function askQuestion(query) {
|
|
22
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
23
|
-
return new Promise((resolve) => rl.question(query, (ans) => {
|
|
24
|
-
rl.close();
|
|
25
|
-
resolve(ans);
|
|
26
|
-
}));
|
|
27
|
-
}
|
|
28
|
-
function flushLog() {
|
|
29
|
-
core_backend_1.IModelHost.platform.flushLog();
|
|
30
|
-
}
|
|
31
|
-
/** show a message, potentially flushing log messages first */
|
|
32
|
-
function showMessage(msg) {
|
|
33
|
-
if (logTimer)
|
|
34
|
-
flushLog();
|
|
35
|
-
console.log(msg);
|
|
36
|
-
}
|
|
37
|
-
/** perform a vacuum on a database, with a message while it's happening */
|
|
38
|
-
function doVacuum(dbName, container) {
|
|
39
|
-
process.stdout.write(`Vacuuming ${dbName} ... `);
|
|
40
|
-
const db = new core_backend_1.SQLiteDb();
|
|
41
|
-
db.withOpenDb({ dbName, openMode: core_bentley_1.OpenMode.ReadWrite, container }, () => db.vacuum());
|
|
42
|
-
process.stdout.write("done");
|
|
43
|
-
showMessage("");
|
|
44
|
-
}
|
|
45
|
-
/** Convert a file size byte value to a friendly string, rounding to nearest integer for K, M, G, T */
|
|
46
|
-
function friendlyFileSize(size) {
|
|
47
|
-
if (size < 1024)
|
|
48
|
-
return `${size}`; // less than 1K, just show number of bytes with no units
|
|
49
|
-
const units = Math.floor(Math.log(size) / Math.log(1024));
|
|
50
|
-
return `${Math.round(size / Math.pow(1024, units))}${["", "K", "M", "G", "T"][units]}`;
|
|
51
|
-
}
|
|
52
|
-
/** Create a new empty WorkspaceDb */
|
|
53
|
-
async function createWorkspaceDb(args) {
|
|
54
|
-
args.writeable = true;
|
|
55
|
-
const wsFile = new core_backend_1.EditableWorkspaceDb(args, core_backend_1.IModelHost.appWorkspace.getContainer(args, args));
|
|
56
|
-
await wsFile.createDb();
|
|
57
|
-
showMessage(`created WorkspaceDb ${wsFile.sqliteDb.nativeDb.getFilePath()}`);
|
|
58
|
-
wsFile.close();
|
|
59
|
-
}
|
|
60
|
-
/** open, call a function to process, then close a WorkspaceDb */
|
|
61
|
-
async function processWorkspace(args, ws, fn) {
|
|
62
|
-
ws.open();
|
|
63
|
-
showMessage(`WorkspaceDb [${ws.sqliteDb.nativeDb.getFilePath()}]`);
|
|
64
|
-
try {
|
|
65
|
-
await fn(ws, args);
|
|
66
|
-
}
|
|
67
|
-
finally {
|
|
68
|
-
ws.close();
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
/** get a WorkspaceContainer that may or may not be a cloud container. */
|
|
72
|
-
function getContainer(args) {
|
|
73
|
-
args.writeable = true;
|
|
74
|
-
return core_backend_1.IModelHost.appWorkspace.getContainer(args, args);
|
|
75
|
-
}
|
|
76
|
-
/** get a WorkspaceContainer that is expected to be a cloud container, throw otherwise. */
|
|
77
|
-
function getCloudContainer(args) {
|
|
78
|
-
const container = getContainer(args);
|
|
79
|
-
const cloudContainer = container.cloudContainer;
|
|
80
|
-
if (!cloudContainer || !cloudContainer.isConnected)
|
|
81
|
-
throw new Error("no cloud container");
|
|
82
|
-
return cloudContainer;
|
|
83
|
-
}
|
|
84
|
-
function fixVersionArg(args) {
|
|
85
|
-
const dbParts = core_backend_1.ITwinWorkspaceContainer.parseDbFileName(args.dbName);
|
|
86
|
-
args.dbName = dbParts.dbName;
|
|
87
|
-
args.version = args.version ?? dbParts.version;
|
|
88
|
-
args.dbFileName = core_backend_1.ITwinWorkspaceContainer.makeDbFileName(dbParts.dbName, dbParts.version);
|
|
89
|
-
}
|
|
90
|
-
/** Open for write, call a function to process, then close a WorkspaceDb */
|
|
91
|
-
async function editWorkspace(args, fn) {
|
|
92
|
-
fixVersionArg(args);
|
|
93
|
-
const ws = new core_backend_1.EditableWorkspaceDb(args, getContainer(args));
|
|
94
|
-
const cloudContainer = ws.container.cloudContainer;
|
|
95
|
-
if (cloudContainer && cloudContainer.queryDatabase(ws.dbFileName)?.state !== "copied")
|
|
96
|
-
throw new Error(`${args.dbFileName} is not editable. Create a new version first`);
|
|
97
|
-
await processWorkspace(args, ws, fn);
|
|
98
|
-
}
|
|
99
|
-
/** Open for read, call a function to process, then close a WorkspaceDb */
|
|
100
|
-
async function readWorkspace(args, fn) {
|
|
101
|
-
fixVersionArg(args);
|
|
102
|
-
return processWorkspace(args, new core_backend_1.ITwinWorkspaceDb(args, getContainer(args)), fn);
|
|
103
|
-
}
|
|
104
|
-
/** List the contents of a WorkspaceDb */
|
|
105
|
-
async function listWorkspaceDb(args) {
|
|
106
|
-
await readWorkspace(args, async (file, args) => {
|
|
107
|
-
const cloudContainer = file.container.cloudContainer;
|
|
108
|
-
const timer = new core_bentley_1.StopWatch("list", true);
|
|
109
|
-
let prefetch;
|
|
110
|
-
if (args.prefetch && cloudContainer) {
|
|
111
|
-
console.log(`start prefetch`);
|
|
112
|
-
prefetch = file.prefetch({ nRequests: args.nRequests });
|
|
113
|
-
}
|
|
114
|
-
if (!args.strings && !args.blobs && !args.files)
|
|
115
|
-
args.blobs = args.files = args.strings = true;
|
|
116
|
-
const nameAndSize = (stmt, size, info) => showMessage(` name=${stmt.getValueString(0)}, size=${friendlyFileSize(size ?? stmt.getValueInteger(1))}${info ?? ""}`);
|
|
117
|
-
if (args.strings) {
|
|
118
|
-
showMessage(" strings:");
|
|
119
|
-
file.sqliteDb.withSqliteStatement("SELECT id,LENGTH(value) FROM strings ORDER BY id COLLATE NOCASE", (stmt) => {
|
|
120
|
-
while (core_bentley_1.DbResult.BE_SQLITE_ROW === stmt.step())
|
|
121
|
-
nameAndSize(stmt);
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
if (args.blobs) {
|
|
125
|
-
showMessage(" blobs:");
|
|
126
|
-
file.sqliteDb.withSqliteStatement("SELECT id,LENGTH(value) FROM blobs ORDER BY id COLLATE NOCASE", (stmt) => {
|
|
127
|
-
while (core_bentley_1.DbResult.BE_SQLITE_ROW === stmt.step())
|
|
128
|
-
nameAndSize(stmt);
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
if (args.files) {
|
|
132
|
-
showMessage(" files:");
|
|
133
|
-
file.sqliteDb.withSqliteStatement("SELECT name FROM be_EmbedFile ORDER BY name COLLATE NOCASE", (stmt) => {
|
|
134
|
-
while (core_bentley_1.DbResult.BE_SQLITE_ROW === stmt.step()) {
|
|
135
|
-
const embed = file.queryFileResource(stmt.getValueString(0));
|
|
136
|
-
if (embed) {
|
|
137
|
-
const info = embed.info;
|
|
138
|
-
const date = new Date(info.date);
|
|
139
|
-
nameAndSize(stmt, info.size, `, ext="${info.fileExt}", date=${date.toString()}`);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
if (prefetch) {
|
|
145
|
-
showMessage(`time = ${timer.elapsedSeconds.toString()}`);
|
|
146
|
-
// await askQuestion("press any key");
|
|
147
|
-
// const queryTimer = new StopWatch("list", true);
|
|
148
|
-
// showMessage(`start query`);
|
|
149
|
-
// file.sqliteDb.withSqliteStatement("SELECT id,value FROM blobs ORDER BY id COLLATE NOCASE", (stmt) => {
|
|
150
|
-
// while (DbResult.BE_SQLITE_ROW === stmt.step()) {
|
|
151
|
-
// }
|
|
152
|
-
// });
|
|
153
|
-
// showMessage(`done query, time= ${queryTimer.elapsedSeconds.toString()}`);
|
|
154
|
-
const done = await prefetch.promise;
|
|
155
|
-
showMessage(`prefetch time = ${timer.elapsedSeconds.toString()}, done=${done}`);
|
|
156
|
-
}
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
/** Add files into a WorkspaceDb. */
|
|
160
|
-
async function addResource(args) {
|
|
161
|
-
return editWorkspace(args, async (wsFile, args) => {
|
|
162
|
-
glob.sync(args.files, { cwd: args.root ?? process.cwd(), nodir: true }).forEach((filePath) => {
|
|
163
|
-
const file = args.root ? (0, path_1.join)(args.root, filePath) : filePath;
|
|
164
|
-
if (!core_backend_1.IModelJsFs.existsSync(file))
|
|
165
|
-
throw new Error(`file [${file}] does not exist`);
|
|
166
|
-
const name = args.rscName ?? filePath;
|
|
167
|
-
try {
|
|
168
|
-
if (args.type === "string") {
|
|
169
|
-
const val = fs.readFileSync(file, "utf-8");
|
|
170
|
-
wsFile.addString(name, val);
|
|
171
|
-
}
|
|
172
|
-
else if (args.type === "blob") {
|
|
173
|
-
const val = fs.readFileSync(file);
|
|
174
|
-
wsFile.addBlob(name, val);
|
|
175
|
-
}
|
|
176
|
-
else {
|
|
177
|
-
wsFile.addFile(name, file);
|
|
178
|
-
}
|
|
179
|
-
showMessage(` added "${file}" as ${args.type} resource [${name}]`);
|
|
180
|
-
}
|
|
181
|
-
catch (e) {
|
|
182
|
-
console.error(core_common_1.IModelError.getErrorMessage(e));
|
|
183
|
-
}
|
|
184
|
-
});
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
/** Replace files in a WorkspaceDb. */
|
|
188
|
-
async function replaceResource(args) {
|
|
189
|
-
return editWorkspace(args, async (wsFile, args) => {
|
|
190
|
-
glob.sync(args.files, { cwd: args.root ?? process.cwd(), nodir: true }).forEach((filePath) => {
|
|
191
|
-
const file = args.root ? (0, path_1.join)(args.root, filePath) : filePath;
|
|
192
|
-
if (!core_backend_1.IModelJsFs.existsSync(file))
|
|
193
|
-
throw new Error(`file [${file}] does not exist`);
|
|
194
|
-
const name = args.rscName ?? filePath;
|
|
195
|
-
try {
|
|
196
|
-
if (args.type === "string") {
|
|
197
|
-
const val = fs.readFileSync(file, "utf-8");
|
|
198
|
-
wsFile.updateString(name, val);
|
|
199
|
-
}
|
|
200
|
-
else if (args.type === "blob") {
|
|
201
|
-
const val = fs.readFileSync(file);
|
|
202
|
-
wsFile.updateBlob(name, val);
|
|
203
|
-
}
|
|
204
|
-
else {
|
|
205
|
-
wsFile.updateFile(name, file);
|
|
206
|
-
}
|
|
207
|
-
showMessage(` updated "${file}" as ${args.type} resource [${name}]`);
|
|
208
|
-
}
|
|
209
|
-
catch (e) {
|
|
210
|
-
console.error(core_common_1.IModelError.getErrorMessage(e));
|
|
211
|
-
}
|
|
212
|
-
});
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
/** Extract a single resource from a WorkspaceDb into a local file */
|
|
216
|
-
async function extractResource(args) {
|
|
217
|
-
return readWorkspace(args, async (file, args) => {
|
|
218
|
-
const verify = (val) => {
|
|
219
|
-
if (val === undefined)
|
|
220
|
-
throw new Error(` ${args.type} resource "${args.rscName}" does not exist`);
|
|
221
|
-
return val;
|
|
222
|
-
};
|
|
223
|
-
if (args.type === "string") {
|
|
224
|
-
fs.writeFileSync(args.fileName, verify(file.getString(args.rscName)));
|
|
225
|
-
}
|
|
226
|
-
else if (args.type === "blob") {
|
|
227
|
-
fs.writeFileSync(args.fileName, verify(file.getBlob(args.rscName)));
|
|
228
|
-
}
|
|
229
|
-
else {
|
|
230
|
-
verify(file.getFile(args.rscName, args.fileName));
|
|
231
|
-
}
|
|
232
|
-
showMessage(` ${args.type} resource [${args.rscName}] extracted to "${args.fileName}"`);
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
/** Remove a single resource from a WorkspaceDb */
|
|
236
|
-
async function removeResource(args) {
|
|
237
|
-
return editWorkspace(args, async (wsFile, args) => {
|
|
238
|
-
if (args.type === "string")
|
|
239
|
-
wsFile.removeString(args.rscName);
|
|
240
|
-
else if (args.type === "blob")
|
|
241
|
-
wsFile.removeBlob(args.rscName);
|
|
242
|
-
else
|
|
243
|
-
wsFile.removeFile(args.rscName);
|
|
244
|
-
showMessage(` removed ${args.type} resource [${args.rscName}]`);
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
/** Vacuum a WorkspaceDb. */
|
|
248
|
-
async function vacuumWorkspaceDb(args) {
|
|
249
|
-
const container = getContainer(args);
|
|
250
|
-
fixVersionArg(args);
|
|
251
|
-
const localFile = new core_backend_1.ITwinWorkspaceDb(args, container).dbFileName;
|
|
252
|
-
doVacuum(localFile, container.cloudContainer);
|
|
253
|
-
}
|
|
254
|
-
/** Either upload or download a WorkspaceDb to/from a cloud WorkspaceContainer. Shows progress % during transfer */
|
|
255
|
-
async function performTransfer(container, direction, args) {
|
|
256
|
-
fixVersionArg(args);
|
|
257
|
-
const localFileName = args.localFileName;
|
|
258
|
-
if (direction === "upload" && !args.noVacuum)
|
|
259
|
-
doVacuum(localFileName);
|
|
260
|
-
const info = `${direction === "download" ? "export" : "import"} ${localFileName}, container=${args.containerId}, dbName=${args.dbFileName} : `;
|
|
261
|
-
let last = 0;
|
|
262
|
-
const onProgress = (loaded, total) => {
|
|
263
|
-
if (loaded > last) {
|
|
264
|
-
last = loaded;
|
|
265
|
-
const message = ` ${(loaded * 100 / total).toFixed(2)}%`;
|
|
266
|
-
readline.cursorTo(process.stdout, info.length);
|
|
267
|
-
process.stdout.write(message);
|
|
268
|
-
}
|
|
269
|
-
return 0;
|
|
270
|
-
};
|
|
271
|
-
process.stdout.write(info);
|
|
272
|
-
const timer = new core_bentley_1.StopWatch(direction, true);
|
|
273
|
-
args.dbName = args.dbFileName;
|
|
274
|
-
await core_backend_1.CloudSqlite.transferDb(direction, container, { ...args, localFileName, onProgress });
|
|
275
|
-
readline.cursorTo(process.stdout, info.length);
|
|
276
|
-
process.stdout.write(`complete, ${timer.elapsedSeconds.toString()} seconds`);
|
|
277
|
-
showMessage("");
|
|
278
|
-
}
|
|
279
|
-
/** import a WorkspaceDb to a cloud WorkspaceContainer. */
|
|
280
|
-
async function importWorkspaceDb(args) {
|
|
281
|
-
const container = getCloudContainer(args);
|
|
282
|
-
if ("" === (0, path_1.extname)(args.localFileName))
|
|
283
|
-
args.localFileName = `${args.localFileName}.${core_backend_1.ITwinWorkspaceDb.fileExt}`;
|
|
284
|
-
if (!core_backend_1.IModelJsFs.existsSync(args.localFileName))
|
|
285
|
-
args.localFileName = (0, path_1.join)(args.directory ?? core_backend_1.IModelHost.appWorkspace.containerDir, args.localFileName);
|
|
286
|
-
await core_backend_1.CloudSqlite.withWriteLock(args.user, container, async () => {
|
|
287
|
-
await performTransfer(container, "upload", args);
|
|
288
|
-
});
|
|
289
|
-
container.checkForChanges(); // so we can see newly imported WorkspaceDb
|
|
290
|
-
}
|
|
291
|
-
/** export a WorkspaceDb from a cloud WorkspaceContainer. */
|
|
292
|
-
async function exportWorkspaceDb(args) {
|
|
293
|
-
if (!(0, path_1.extname)(args.localFileName))
|
|
294
|
-
args.localFileName = `${args.localFileName}.${core_backend_1.ITwinWorkspaceDb.fileExt}`;
|
|
295
|
-
const dbParts = core_backend_1.ITwinWorkspaceContainer.parseDbFileName(args.dbName);
|
|
296
|
-
if (!dbParts.version)
|
|
297
|
-
throw new Error("exportDb requires a version");
|
|
298
|
-
await performTransfer(getCloudContainer(args), "download", args);
|
|
299
|
-
}
|
|
300
|
-
/** Delete a WorkspaceDb from a cloud WorkspaceContainer. */
|
|
301
|
-
async function deleteWorkspaceDb(args) {
|
|
302
|
-
const container = getCloudContainer(args);
|
|
303
|
-
await core_backend_1.CloudSqlite.withWriteLock(args.user, container, async () => {
|
|
304
|
-
return container.deleteDatabase(args.dbName);
|
|
305
|
-
});
|
|
306
|
-
showMessage(`deleted WorkspaceDb [${args.dbName}] from ${sayContainer(args)}`);
|
|
307
|
-
}
|
|
308
|
-
function sayContainer(args) {
|
|
309
|
-
return `container [${args.containerId}]`;
|
|
310
|
-
}
|
|
311
|
-
/** initialize (empty if it exists) a cloud WorkspaceContainer. */
|
|
312
|
-
async function initializeWorkspace(args) {
|
|
313
|
-
if (undefined === args.storageType || !args.accessName)
|
|
314
|
-
throw new Error("No cloud container supplied");
|
|
315
|
-
if (!args.noPrompt) {
|
|
316
|
-
const yesNo = await askQuestion(`Are you sure you want to initialize ${sayContainer(args)}"? [y/n]: `);
|
|
317
|
-
if (yesNo[0].toUpperCase() !== "Y")
|
|
318
|
-
return;
|
|
319
|
-
}
|
|
320
|
-
const container = core_backend_1.CloudSqlite.createCloudContainer(args);
|
|
321
|
-
container.initializeContainer({ checksumBlockNames: true });
|
|
322
|
-
showMessage(`container "${args.containerId} initialized`);
|
|
323
|
-
}
|
|
324
|
-
/** purge unused (garbage) blocks from a WorkspaceContainer. */
|
|
325
|
-
async function purgeWorkspace(args) {
|
|
326
|
-
const container = getCloudContainer(args);
|
|
327
|
-
const nGarbage = container.garbageBlocks;
|
|
328
|
-
await core_backend_1.CloudSqlite.withWriteLock(args.user, container, async () => container.cleanDeletedBlocks());
|
|
329
|
-
container.checkForChanges(); // re-read manifest to get current garbage count
|
|
330
|
-
showMessage(`purged ${sayContainer(args)}. ${nGarbage - container.garbageBlocks} garbage blocks cleaned`);
|
|
331
|
-
}
|
|
332
|
-
/** detach a WorkspaceContainer from the local cache. */
|
|
333
|
-
async function detachWorkspace(args) {
|
|
334
|
-
const container = getCloudContainer(args);
|
|
335
|
-
container.detach();
|
|
336
|
-
showMessage(`detached ${sayContainer(args)}.`);
|
|
337
|
-
}
|
|
338
|
-
/** Make a copy of a WorkspaceDb with a new name. */
|
|
339
|
-
async function copyWorkspaceDb(args) {
|
|
340
|
-
const container = getCloudContainer(args);
|
|
341
|
-
const oldName = core_backend_1.ITwinWorkspaceContainer.resolveCloudFileName(container, args);
|
|
342
|
-
const newVersion = core_backend_1.ITwinWorkspaceContainer.parseDbFileName(args.newDbName);
|
|
343
|
-
core_backend_1.ITwinWorkspaceContainer.validateDbName(newVersion.dbName);
|
|
344
|
-
const newName = core_backend_1.ITwinWorkspaceContainer.makeDbFileName(newVersion.dbName, core_backend_1.ITwinWorkspaceContainer.validateVersion(newVersion.version));
|
|
345
|
-
await core_backend_1.CloudSqlite.withWriteLock(args.user, container, async () => container.copyDatabase(oldName, newName));
|
|
346
|
-
showMessage(`copied WorkspaceDb [${oldName}] to [${newName}] in ${sayContainer(args)}`);
|
|
347
|
-
}
|
|
348
|
-
/** Make a copy of a WorkspaceDb with a new name. */
|
|
349
|
-
async function versionWorkspaceDb(args) {
|
|
350
|
-
fixVersionArg(args);
|
|
351
|
-
const container = getCloudContainer(args);
|
|
352
|
-
const result = await core_backend_1.ITwinWorkspaceContainer.makeNewVersion(container, args, args.versionType);
|
|
353
|
-
showMessage(`created new version: [${result.newName}] from [${result.oldName}] in ${sayContainer(args)}`);
|
|
354
|
-
}
|
|
355
|
-
/** pin a WorkspaceDb from a WorkspaceContainer. */
|
|
356
|
-
async function preFetchWorkspaceDb(args) {
|
|
357
|
-
fixVersionArg(args);
|
|
358
|
-
const container = getCloudContainer(args);
|
|
359
|
-
const timer = new core_bentley_1.StopWatch("prefetch", true);
|
|
360
|
-
const prefetch = core_backend_1.CloudSqlite.startCloudPrefetch(container, args.dbFileName);
|
|
361
|
-
await prefetch.promise;
|
|
362
|
-
showMessage(`preFetched WorkspaceDb [${args.dbFileName}] in ${sayContainer(args)}, time=${timer.elapsedSeconds.toString()}`);
|
|
363
|
-
}
|
|
364
|
-
/** acquire the write lock for a WorkspaceContainer. */
|
|
365
|
-
async function acquireLock(args) {
|
|
366
|
-
const container = getCloudContainer(args);
|
|
367
|
-
if (container.hasWriteLock)
|
|
368
|
-
throw new Error(`write lock is already held for ${sayContainer(args)}`);
|
|
369
|
-
container.acquireWriteLock(args.user);
|
|
370
|
-
showMessage(`acquired lock for ${sayContainer(args)}`);
|
|
371
|
-
}
|
|
372
|
-
/** release the write lock for a WorkspaceContainer. */
|
|
373
|
-
async function releaseLock(args) {
|
|
374
|
-
const container = getCloudContainer(args);
|
|
375
|
-
if (!container.hasWriteLock)
|
|
376
|
-
throw new Error(`write lock is not held for ${sayContainer(args)}`);
|
|
377
|
-
container.releaseWriteLock();
|
|
378
|
-
showMessage(`released lock for ${sayContainer(args)}`);
|
|
379
|
-
}
|
|
380
|
-
/** clear the write lock for a WorkspaceContainer. */
|
|
381
|
-
async function clearWriteLock(args) {
|
|
382
|
-
const container = getCloudContainer(args);
|
|
383
|
-
container.clearWriteLock();
|
|
384
|
-
showMessage(`write lock cleared for ${sayContainer(args)}`);
|
|
385
|
-
}
|
|
386
|
-
/** query the list of WorkspaceDb in a WorkspaceContainer. */
|
|
387
|
-
async function queryWorkspaceDbs(args) {
|
|
388
|
-
const container = getCloudContainer(args);
|
|
389
|
-
const writeLockMsg = container.hasWriteLock ? ", writeLocked" : "";
|
|
390
|
-
const hasLocalMsg = container.hasLocalChanges ? ", has local changes" : "";
|
|
391
|
-
const nGarbage = container.garbageBlocks;
|
|
392
|
-
const garbageMsg = nGarbage ? `, ${nGarbage} garbage block${nGarbage > 1 ? "s" : ""}` : "";
|
|
393
|
-
const blockSize = container.blockSize;
|
|
394
|
-
showMessage(`WorkspaceDbs in ${sayContainer(args)}${writeLockMsg}${hasLocalMsg}${garbageMsg}`);
|
|
395
|
-
const dbs = container.queryDatabases(args.glob);
|
|
396
|
-
for (const dbName of dbs) {
|
|
397
|
-
const db = container.queryDatabase(dbName);
|
|
398
|
-
if (db) {
|
|
399
|
-
const dirty = db.dirtyBlocks ? `, ${db.dirtyBlocks} dirty` : "";
|
|
400
|
-
const editable = db.state === "copied" ? ", editable" : "";
|
|
401
|
-
showMessage(` "${dbName}", size=${friendlyFileSize(db.totalBlocks * blockSize)}, ${friendlyFileSize(db.localBlocks * blockSize)} downloaded (${(100 * db.localBlocks / db.totalBlocks).toFixed(0)}%)${editable}${dirty}`);
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
/** Start `IModelHost`, then run a WorkspaceEditor command. Errors are logged to console. */
|
|
406
|
-
function runCommand(cmd) {
|
|
407
|
-
return async (args) => {
|
|
408
|
-
if (inScript)
|
|
409
|
-
return cmd(args);
|
|
410
|
-
try {
|
|
411
|
-
const workspace = {
|
|
412
|
-
containerDir: args.directory,
|
|
413
|
-
cloudCacheProps: {
|
|
414
|
-
nRequests: args.nRequests,
|
|
415
|
-
curlDiagnostics: args.curlDiagnostics,
|
|
416
|
-
},
|
|
417
|
-
};
|
|
418
|
-
await core_backend_1.IModelHost.startup({ workspace });
|
|
419
|
-
if (true === args.logging) {
|
|
420
|
-
core_bentley_1.Logger.initializeToConsole();
|
|
421
|
-
core_bentley_1.Logger.setLevel("CloudSqlite", core_bentley_1.LogLevel.Trace);
|
|
422
|
-
core_backend_1.IModelHost.appWorkspace.cloudCache?.setLogMask(core_backend_1.CloudSqlite.LoggingMask.All);
|
|
423
|
-
logTimer = setInterval(() => flushLog(), 250); // logging from other threads is buffered. This causes it to appear every 1/4 second.
|
|
424
|
-
}
|
|
425
|
-
await cmd(args);
|
|
426
|
-
}
|
|
427
|
-
catch (e) {
|
|
428
|
-
if (typeof e.message === "string")
|
|
429
|
-
console.error(e.message);
|
|
430
|
-
else
|
|
431
|
-
console.log(core_bentley_1.BentleyError.getErrorMessage(e));
|
|
432
|
-
}
|
|
433
|
-
finally {
|
|
434
|
-
if (logTimer) {
|
|
435
|
-
flushLog();
|
|
436
|
-
clearInterval(logTimer);
|
|
437
|
-
}
|
|
438
|
-
await core_backend_1.IModelHost.shutdown();
|
|
439
|
-
}
|
|
440
|
-
};
|
|
441
|
-
}
|
|
442
|
-
const type = { alias: "t", describe: "Type of resource", choices: ["blob", "string", "file"], demandOption: true };
|
|
443
|
-
const addOrReplace = {
|
|
444
|
-
rscName: { alias: "n", describe: "Resource name for file", string: true },
|
|
445
|
-
root: { alias: "r", describe: "Root directory. Path parts after this will be saved in resource name", string: true },
|
|
446
|
-
type,
|
|
447
|
-
};
|
|
448
|
-
Yargs.usage("Edits or lists contents of a WorkspaceDb");
|
|
449
|
-
Yargs.wrap(Math.min(150, Yargs.terminalWidth()));
|
|
450
|
-
Yargs.env("WORKSPACE_EDITOR");
|
|
451
|
-
Yargs.config();
|
|
452
|
-
Yargs.help();
|
|
453
|
-
Yargs.version("V2.0");
|
|
454
|
-
Yargs.options({
|
|
455
|
-
directory: { alias: "d", describe: "Directory to use for WorkspaceContainers", string: true },
|
|
456
|
-
nRequests: { describe: "Number of simultaneous http requests for cloud operations", number: true, hidden: true },
|
|
457
|
-
containerId: { alias: "c", describe: "ContainerId for WorkspaceDb", string: true, demandOption: true },
|
|
458
|
-
user: { describe: "String shown in cloud container locked message", string: true, default: "workspace-editor" },
|
|
459
|
-
accessName: { alias: "a", describe: "Cloud storage account name for container", string: true },
|
|
460
|
-
accessToken: { describe: "Token that grants access to the container (either SAS or account key)", string: true, default: "" },
|
|
461
|
-
storageType: { describe: "Cloud storage module type", string: true, default: "azure?sas=1" },
|
|
462
|
-
logging: { describe: "enable log messages", boolean: true, default: false, hidden: true },
|
|
463
|
-
prefetch: { boolean: true, default: false, hidden: true },
|
|
464
|
-
curlDiagnostics: { boolean: true, default: false, hidden: true },
|
|
465
|
-
});
|
|
466
|
-
Yargs.command({
|
|
467
|
-
command: "add <dbName> <files>",
|
|
468
|
-
describe: "add files into a WorkspaceDb",
|
|
469
|
-
builder: addOrReplace,
|
|
470
|
-
handler: runCommand(addResource),
|
|
471
|
-
});
|
|
472
|
-
Yargs.command({
|
|
473
|
-
command: "replace <dbName> <files>",
|
|
474
|
-
describe: "replace files in a WorkspaceDb",
|
|
475
|
-
builder: addOrReplace,
|
|
476
|
-
handler: runCommand(replaceResource),
|
|
477
|
-
});
|
|
478
|
-
Yargs.command({
|
|
479
|
-
command: "remove <dbName> <rscName>",
|
|
480
|
-
describe: "remove a resource from a WorkspaceDb",
|
|
481
|
-
builder: { type },
|
|
482
|
-
handler: runCommand(removeResource),
|
|
483
|
-
});
|
|
484
|
-
Yargs.command({
|
|
485
|
-
command: "extract <dbName> <rscName> <fileName>",
|
|
486
|
-
describe: "extract a resource from a WorkspaceDb into a local file",
|
|
487
|
-
builder: { type },
|
|
488
|
-
handler: runCommand(extractResource),
|
|
489
|
-
});
|
|
490
|
-
Yargs.command({
|
|
491
|
-
command: "listDb <dbName>",
|
|
492
|
-
describe: "list the contents of a WorkspaceDb",
|
|
493
|
-
builder: {
|
|
494
|
-
strings: { alias: "s", describe: "list string resources", boolean: true, default: false },
|
|
495
|
-
blobs: { alias: "b", describe: "list blob resources", boolean: true, default: false },
|
|
496
|
-
files: { alias: "f", describe: "list file resources", boolean: true, default: false },
|
|
497
|
-
},
|
|
498
|
-
handler: runCommand(listWorkspaceDb),
|
|
499
|
-
});
|
|
500
|
-
Yargs.command({
|
|
501
|
-
command: "deleteDb <dbName>",
|
|
502
|
-
describe: "delete a WorkspaceDb from a cloud container",
|
|
503
|
-
handler: runCommand(deleteWorkspaceDb),
|
|
504
|
-
});
|
|
505
|
-
Yargs.command({
|
|
506
|
-
command: "createDb <dbName>",
|
|
507
|
-
describe: "create a new WorkspaceDb",
|
|
508
|
-
handler: runCommand(createWorkspaceDb),
|
|
509
|
-
});
|
|
510
|
-
Yargs.command({
|
|
511
|
-
command: "copyDb <dbName> <newDbName>",
|
|
512
|
-
describe: "make a copy of a WorkspaceDb in a cloud container with a new name",
|
|
513
|
-
handler: runCommand(copyWorkspaceDb),
|
|
514
|
-
});
|
|
515
|
-
Yargs.command({
|
|
516
|
-
command: "versionDb <dbName>",
|
|
517
|
-
describe: "make a new version of a WorkspaceDb",
|
|
518
|
-
builder: {
|
|
519
|
-
versionType: { describe: "the type of version to create", default: "patch", string: true, choices: ["major", "minor", "patch"] },
|
|
520
|
-
},
|
|
521
|
-
handler: runCommand(versionWorkspaceDb),
|
|
522
|
-
});
|
|
523
|
-
Yargs.command({
|
|
524
|
-
command: "prefetchDb <dbName>",
|
|
525
|
-
describe: false,
|
|
526
|
-
handler: runCommand(preFetchWorkspaceDb),
|
|
527
|
-
});
|
|
528
|
-
Yargs.command({
|
|
529
|
-
command: "vacuumDb <dbName>",
|
|
530
|
-
describe: "vacuum a WorkspaceDb",
|
|
531
|
-
handler: runCommand(vacuumWorkspaceDb),
|
|
532
|
-
});
|
|
533
|
-
Yargs.command({
|
|
534
|
-
command: "importDb <dbName> <localFileName>",
|
|
535
|
-
describe: "import a WorkspaceDb into a cloud container",
|
|
536
|
-
builder: {
|
|
537
|
-
noVacuum: { describe: "Don't vacuum source Db before importing", boolean: true },
|
|
538
|
-
},
|
|
539
|
-
handler: runCommand(importWorkspaceDb),
|
|
540
|
-
});
|
|
541
|
-
Yargs.command({
|
|
542
|
-
command: "exportDb <dbName> <localFileName>",
|
|
543
|
-
describe: "export a WorkspaceDb from a cloud container to a local file",
|
|
544
|
-
handler: runCommand(exportWorkspaceDb),
|
|
545
|
-
});
|
|
546
|
-
Yargs.command({
|
|
547
|
-
command: "queryDbs [glob]",
|
|
548
|
-
describe: "query the list of WorkspaceDbs in a cloud container",
|
|
549
|
-
handler: runCommand(queryWorkspaceDbs),
|
|
550
|
-
});
|
|
551
|
-
Yargs.command({
|
|
552
|
-
command: "acquireLock",
|
|
553
|
-
describe: "acquire the write lock for a cloud container",
|
|
554
|
-
handler: runCommand(acquireLock),
|
|
555
|
-
});
|
|
556
|
-
Yargs.command({
|
|
557
|
-
command: "releaseLock",
|
|
558
|
-
describe: "release the write lock for a cloud container",
|
|
559
|
-
handler: runCommand(releaseLock),
|
|
560
|
-
});
|
|
561
|
-
Yargs.command({
|
|
562
|
-
command: "clearWriteLock",
|
|
563
|
-
describe: "clear the write lock for a cloud container. Note: this can be dangerous!",
|
|
564
|
-
handler: runCommand(clearWriteLock),
|
|
565
|
-
});
|
|
566
|
-
Yargs.command({
|
|
567
|
-
command: "purgeWorkspace",
|
|
568
|
-
describe: "purge deleted blocks from a WorkspaceContainer",
|
|
569
|
-
handler: runCommand(purgeWorkspace),
|
|
570
|
-
});
|
|
571
|
-
Yargs.command({
|
|
572
|
-
command: "detachWorkspace",
|
|
573
|
-
describe: false,
|
|
574
|
-
handler: runCommand(detachWorkspace),
|
|
575
|
-
});
|
|
576
|
-
Yargs.command({
|
|
577
|
-
command: "initializeWorkspace",
|
|
578
|
-
describe: "initialize a WorkspaceContainer (empties if already initialized)",
|
|
579
|
-
builder: {
|
|
580
|
-
noPrompt: { describe: "Skip prompt", boolean: true, default: false },
|
|
581
|
-
},
|
|
582
|
-
handler: runCommand(initializeWorkspace),
|
|
583
|
-
});
|
|
584
|
-
/** execute an "@" script - a list of WorkspaceEditor commands */
|
|
585
|
-
async function runScript(arg) {
|
|
586
|
-
inScript = true;
|
|
587
|
-
const val = fs.readFileSync(arg.scriptName, "utf-8");
|
|
588
|
-
const lines = val.split(/\r?\n/);
|
|
589
|
-
let i = 0;
|
|
590
|
-
for (let line of lines) {
|
|
591
|
-
i++;
|
|
592
|
-
line = line.split("#")[0].trim(); // ignore leading/trailing whitespace and comments (anything after a "#")
|
|
593
|
-
if (line.length === 0)
|
|
594
|
-
continue; // blank line
|
|
595
|
-
await Yargs.parseAsync(line, {}, (err, _argv, _output) => {
|
|
596
|
-
if (err) {
|
|
597
|
-
console.error(`${arg.scriptName}:${i} [${line}] : ${core_bentley_1.BentleyError.getErrorMessage(err)}`);
|
|
598
|
-
process.exit(1);
|
|
599
|
-
}
|
|
600
|
-
});
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
/** Parse and execute WorkspaceEditor commands */
|
|
604
|
-
async function main() {
|
|
605
|
-
if (process.argv.length > 1 && process.argv[2]?.[0] === "@") {
|
|
606
|
-
const parsed = Yargs.parseSync(process.argv.slice(3));
|
|
607
|
-
if (parsed.config)
|
|
608
|
-
process.env.WORKSPACE_EDITOR_CONFIG = parsed.config;
|
|
609
|
-
await runCommand(runScript)({ ...parsed, scriptName: process.argv[2].substring(1) });
|
|
610
|
-
return;
|
|
611
|
-
}
|
|
612
|
-
Yargs.strictCommands();
|
|
613
|
-
Yargs.demandCommand();
|
|
614
|
-
await Yargs.parseAsync();
|
|
615
|
-
}
|
|
616
|
-
void main();
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/*---------------------------------------------------------------------------------------------
|
|
4
|
+
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
|
|
5
|
+
* See LICENSE.md in the project root for license terms and full copyright notice.
|
|
6
|
+
*--------------------------------------------------------------------------------------------*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
const fs = require("fs");
|
|
9
|
+
const glob = require("glob");
|
|
10
|
+
const path_1 = require("path");
|
|
11
|
+
const readline = require("readline");
|
|
12
|
+
const Yargs = require("yargs");
|
|
13
|
+
const core_backend_1 = require("@itwin/core-backend");
|
|
14
|
+
const core_bentley_1 = require("@itwin/core-bentley");
|
|
15
|
+
const core_common_1 = require("@itwin/core-common");
|
|
16
|
+
// cspell:ignore nodir nocase
|
|
17
|
+
/* eslint-disable id-blacklist,no-console */
|
|
18
|
+
/** Currently executing an "@" script? */
|
|
19
|
+
let inScript = false;
|
|
20
|
+
let logTimer;
|
|
21
|
+
async function askQuestion(query) {
|
|
22
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
23
|
+
return new Promise((resolve) => rl.question(query, (ans) => {
|
|
24
|
+
rl.close();
|
|
25
|
+
resolve(ans);
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
28
|
+
function flushLog() {
|
|
29
|
+
core_backend_1.IModelHost.platform.flushLog();
|
|
30
|
+
}
|
|
31
|
+
/** show a message, potentially flushing log messages first */
|
|
32
|
+
function showMessage(msg) {
|
|
33
|
+
if (logTimer)
|
|
34
|
+
flushLog();
|
|
35
|
+
console.log(msg);
|
|
36
|
+
}
|
|
37
|
+
/** perform a vacuum on a database, with a message while it's happening */
|
|
38
|
+
function doVacuum(dbName, container) {
|
|
39
|
+
process.stdout.write(`Vacuuming ${dbName} ... `);
|
|
40
|
+
const db = new core_backend_1.SQLiteDb();
|
|
41
|
+
db.withOpenDb({ dbName, openMode: core_bentley_1.OpenMode.ReadWrite, container }, () => db.vacuum());
|
|
42
|
+
process.stdout.write("done");
|
|
43
|
+
showMessage("");
|
|
44
|
+
}
|
|
45
|
+
/** Convert a file size byte value to a friendly string, rounding to nearest integer for K, M, G, T */
|
|
46
|
+
function friendlyFileSize(size) {
|
|
47
|
+
if (size < 1024)
|
|
48
|
+
return `${size}`; // less than 1K, just show number of bytes with no units
|
|
49
|
+
const units = Math.floor(Math.log(size) / Math.log(1024));
|
|
50
|
+
return `${Math.round(size / Math.pow(1024, units))}${["", "K", "M", "G", "T"][units]}`;
|
|
51
|
+
}
|
|
52
|
+
/** Create a new empty WorkspaceDb */
|
|
53
|
+
async function createWorkspaceDb(args) {
|
|
54
|
+
args.writeable = true;
|
|
55
|
+
const wsFile = new core_backend_1.EditableWorkspaceDb(args, core_backend_1.IModelHost.appWorkspace.getContainer(args, args));
|
|
56
|
+
await wsFile.createDb();
|
|
57
|
+
showMessage(`created WorkspaceDb ${wsFile.sqliteDb.nativeDb.getFilePath()}`);
|
|
58
|
+
wsFile.close();
|
|
59
|
+
}
|
|
60
|
+
/** open, call a function to process, then close a WorkspaceDb */
|
|
61
|
+
async function processWorkspace(args, ws, fn) {
|
|
62
|
+
ws.open();
|
|
63
|
+
showMessage(`WorkspaceDb [${ws.sqliteDb.nativeDb.getFilePath()}]`);
|
|
64
|
+
try {
|
|
65
|
+
await fn(ws, args);
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
ws.close();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/** get a WorkspaceContainer that may or may not be a cloud container. */
|
|
72
|
+
function getContainer(args) {
|
|
73
|
+
args.writeable = true;
|
|
74
|
+
return core_backend_1.IModelHost.appWorkspace.getContainer(args, args);
|
|
75
|
+
}
|
|
76
|
+
/** get a WorkspaceContainer that is expected to be a cloud container, throw otherwise. */
|
|
77
|
+
function getCloudContainer(args) {
|
|
78
|
+
const container = getContainer(args);
|
|
79
|
+
const cloudContainer = container.cloudContainer;
|
|
80
|
+
if (!cloudContainer || !cloudContainer.isConnected)
|
|
81
|
+
throw new Error("no cloud container");
|
|
82
|
+
return cloudContainer;
|
|
83
|
+
}
|
|
84
|
+
function fixVersionArg(args) {
|
|
85
|
+
const dbParts = core_backend_1.ITwinWorkspaceContainer.parseDbFileName(args.dbName);
|
|
86
|
+
args.dbName = dbParts.dbName;
|
|
87
|
+
args.version = args.version ?? dbParts.version;
|
|
88
|
+
args.dbFileName = core_backend_1.ITwinWorkspaceContainer.makeDbFileName(dbParts.dbName, dbParts.version);
|
|
89
|
+
}
|
|
90
|
+
/** Open for write, call a function to process, then close a WorkspaceDb */
|
|
91
|
+
async function editWorkspace(args, fn) {
|
|
92
|
+
fixVersionArg(args);
|
|
93
|
+
const ws = new core_backend_1.EditableWorkspaceDb(args, getContainer(args));
|
|
94
|
+
const cloudContainer = ws.container.cloudContainer;
|
|
95
|
+
if (cloudContainer && cloudContainer.queryDatabase(ws.dbFileName)?.state !== "copied")
|
|
96
|
+
throw new Error(`${args.dbFileName} is not editable. Create a new version first`);
|
|
97
|
+
await processWorkspace(args, ws, fn);
|
|
98
|
+
}
|
|
99
|
+
/** Open for read, call a function to process, then close a WorkspaceDb */
|
|
100
|
+
async function readWorkspace(args, fn) {
|
|
101
|
+
fixVersionArg(args);
|
|
102
|
+
return processWorkspace(args, new core_backend_1.ITwinWorkspaceDb(args, getContainer(args)), fn);
|
|
103
|
+
}
|
|
104
|
+
/** List the contents of a WorkspaceDb */
|
|
105
|
+
async function listWorkspaceDb(args) {
|
|
106
|
+
await readWorkspace(args, async (file, args) => {
|
|
107
|
+
const cloudContainer = file.container.cloudContainer;
|
|
108
|
+
const timer = new core_bentley_1.StopWatch("list", true);
|
|
109
|
+
let prefetch;
|
|
110
|
+
if (args.prefetch && cloudContainer) {
|
|
111
|
+
console.log(`start prefetch`);
|
|
112
|
+
prefetch = file.prefetch({ nRequests: args.nRequests });
|
|
113
|
+
}
|
|
114
|
+
if (!args.strings && !args.blobs && !args.files)
|
|
115
|
+
args.blobs = args.files = args.strings = true;
|
|
116
|
+
const nameAndSize = (stmt, size, info) => showMessage(` name=${stmt.getValueString(0)}, size=${friendlyFileSize(size ?? stmt.getValueInteger(1))}${info ?? ""}`);
|
|
117
|
+
if (args.strings) {
|
|
118
|
+
showMessage(" strings:");
|
|
119
|
+
file.sqliteDb.withSqliteStatement("SELECT id,LENGTH(value) FROM strings ORDER BY id COLLATE NOCASE", (stmt) => {
|
|
120
|
+
while (core_bentley_1.DbResult.BE_SQLITE_ROW === stmt.step())
|
|
121
|
+
nameAndSize(stmt);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
if (args.blobs) {
|
|
125
|
+
showMessage(" blobs:");
|
|
126
|
+
file.sqliteDb.withSqliteStatement("SELECT id,LENGTH(value) FROM blobs ORDER BY id COLLATE NOCASE", (stmt) => {
|
|
127
|
+
while (core_bentley_1.DbResult.BE_SQLITE_ROW === stmt.step())
|
|
128
|
+
nameAndSize(stmt);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
if (args.files) {
|
|
132
|
+
showMessage(" files:");
|
|
133
|
+
file.sqliteDb.withSqliteStatement("SELECT name FROM be_EmbedFile ORDER BY name COLLATE NOCASE", (stmt) => {
|
|
134
|
+
while (core_bentley_1.DbResult.BE_SQLITE_ROW === stmt.step()) {
|
|
135
|
+
const embed = file.queryFileResource(stmt.getValueString(0));
|
|
136
|
+
if (embed) {
|
|
137
|
+
const info = embed.info;
|
|
138
|
+
const date = new Date(info.date);
|
|
139
|
+
nameAndSize(stmt, info.size, `, ext="${info.fileExt}", date=${date.toString()}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
if (prefetch) {
|
|
145
|
+
showMessage(`time = ${timer.elapsedSeconds.toString()}`);
|
|
146
|
+
// await askQuestion("press any key");
|
|
147
|
+
// const queryTimer = new StopWatch("list", true);
|
|
148
|
+
// showMessage(`start query`);
|
|
149
|
+
// file.sqliteDb.withSqliteStatement("SELECT id,value FROM blobs ORDER BY id COLLATE NOCASE", (stmt) => {
|
|
150
|
+
// while (DbResult.BE_SQLITE_ROW === stmt.step()) {
|
|
151
|
+
// }
|
|
152
|
+
// });
|
|
153
|
+
// showMessage(`done query, time= ${queryTimer.elapsedSeconds.toString()}`);
|
|
154
|
+
const done = await prefetch.promise;
|
|
155
|
+
showMessage(`prefetch time = ${timer.elapsedSeconds.toString()}, done=${done}`);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
/** Add files into a WorkspaceDb. */
|
|
160
|
+
async function addResource(args) {
|
|
161
|
+
return editWorkspace(args, async (wsFile, args) => {
|
|
162
|
+
glob.sync(args.files, { cwd: args.root ?? process.cwd(), nodir: true }).forEach((filePath) => {
|
|
163
|
+
const file = args.root ? (0, path_1.join)(args.root, filePath) : filePath;
|
|
164
|
+
if (!core_backend_1.IModelJsFs.existsSync(file))
|
|
165
|
+
throw new Error(`file [${file}] does not exist`);
|
|
166
|
+
const name = args.rscName ?? filePath;
|
|
167
|
+
try {
|
|
168
|
+
if (args.type === "string") {
|
|
169
|
+
const val = fs.readFileSync(file, "utf-8");
|
|
170
|
+
wsFile.addString(name, val);
|
|
171
|
+
}
|
|
172
|
+
else if (args.type === "blob") {
|
|
173
|
+
const val = fs.readFileSync(file);
|
|
174
|
+
wsFile.addBlob(name, val);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
wsFile.addFile(name, file);
|
|
178
|
+
}
|
|
179
|
+
showMessage(` added "${file}" as ${args.type} resource [${name}]`);
|
|
180
|
+
}
|
|
181
|
+
catch (e) {
|
|
182
|
+
console.error(core_common_1.IModelError.getErrorMessage(e));
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
/** Replace files in a WorkspaceDb. */
|
|
188
|
+
async function replaceResource(args) {
|
|
189
|
+
return editWorkspace(args, async (wsFile, args) => {
|
|
190
|
+
glob.sync(args.files, { cwd: args.root ?? process.cwd(), nodir: true }).forEach((filePath) => {
|
|
191
|
+
const file = args.root ? (0, path_1.join)(args.root, filePath) : filePath;
|
|
192
|
+
if (!core_backend_1.IModelJsFs.existsSync(file))
|
|
193
|
+
throw new Error(`file [${file}] does not exist`);
|
|
194
|
+
const name = args.rscName ?? filePath;
|
|
195
|
+
try {
|
|
196
|
+
if (args.type === "string") {
|
|
197
|
+
const val = fs.readFileSync(file, "utf-8");
|
|
198
|
+
wsFile.updateString(name, val);
|
|
199
|
+
}
|
|
200
|
+
else if (args.type === "blob") {
|
|
201
|
+
const val = fs.readFileSync(file);
|
|
202
|
+
wsFile.updateBlob(name, val);
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
wsFile.updateFile(name, file);
|
|
206
|
+
}
|
|
207
|
+
showMessage(` updated "${file}" as ${args.type} resource [${name}]`);
|
|
208
|
+
}
|
|
209
|
+
catch (e) {
|
|
210
|
+
console.error(core_common_1.IModelError.getErrorMessage(e));
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
/** Extract a single resource from a WorkspaceDb into a local file */
|
|
216
|
+
async function extractResource(args) {
|
|
217
|
+
return readWorkspace(args, async (file, args) => {
|
|
218
|
+
const verify = (val) => {
|
|
219
|
+
if (val === undefined)
|
|
220
|
+
throw new Error(` ${args.type} resource "${args.rscName}" does not exist`);
|
|
221
|
+
return val;
|
|
222
|
+
};
|
|
223
|
+
if (args.type === "string") {
|
|
224
|
+
fs.writeFileSync(args.fileName, verify(file.getString(args.rscName)));
|
|
225
|
+
}
|
|
226
|
+
else if (args.type === "blob") {
|
|
227
|
+
fs.writeFileSync(args.fileName, verify(file.getBlob(args.rscName)));
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
verify(file.getFile(args.rscName, args.fileName));
|
|
231
|
+
}
|
|
232
|
+
showMessage(` ${args.type} resource [${args.rscName}] extracted to "${args.fileName}"`);
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
/** Remove a single resource from a WorkspaceDb */
|
|
236
|
+
async function removeResource(args) {
|
|
237
|
+
return editWorkspace(args, async (wsFile, args) => {
|
|
238
|
+
if (args.type === "string")
|
|
239
|
+
wsFile.removeString(args.rscName);
|
|
240
|
+
else if (args.type === "blob")
|
|
241
|
+
wsFile.removeBlob(args.rscName);
|
|
242
|
+
else
|
|
243
|
+
wsFile.removeFile(args.rscName);
|
|
244
|
+
showMessage(` removed ${args.type} resource [${args.rscName}]`);
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
/** Vacuum a WorkspaceDb. */
|
|
248
|
+
async function vacuumWorkspaceDb(args) {
|
|
249
|
+
const container = getContainer(args);
|
|
250
|
+
fixVersionArg(args);
|
|
251
|
+
const localFile = new core_backend_1.ITwinWorkspaceDb(args, container).dbFileName;
|
|
252
|
+
doVacuum(localFile, container.cloudContainer);
|
|
253
|
+
}
|
|
254
|
+
/** Either upload or download a WorkspaceDb to/from a cloud WorkspaceContainer. Shows progress % during transfer */
|
|
255
|
+
async function performTransfer(container, direction, args) {
|
|
256
|
+
fixVersionArg(args);
|
|
257
|
+
const localFileName = args.localFileName;
|
|
258
|
+
if (direction === "upload" && !args.noVacuum)
|
|
259
|
+
doVacuum(localFileName);
|
|
260
|
+
const info = `${direction === "download" ? "export" : "import"} ${localFileName}, container=${args.containerId}, dbName=${args.dbFileName} : `;
|
|
261
|
+
let last = 0;
|
|
262
|
+
const onProgress = (loaded, total) => {
|
|
263
|
+
if (loaded > last) {
|
|
264
|
+
last = loaded;
|
|
265
|
+
const message = ` ${(loaded * 100 / total).toFixed(2)}%`;
|
|
266
|
+
readline.cursorTo(process.stdout, info.length);
|
|
267
|
+
process.stdout.write(message);
|
|
268
|
+
}
|
|
269
|
+
return 0;
|
|
270
|
+
};
|
|
271
|
+
process.stdout.write(info);
|
|
272
|
+
const timer = new core_bentley_1.StopWatch(direction, true);
|
|
273
|
+
args.dbName = args.dbFileName;
|
|
274
|
+
await core_backend_1.CloudSqlite.transferDb(direction, container, { ...args, localFileName, onProgress });
|
|
275
|
+
readline.cursorTo(process.stdout, info.length);
|
|
276
|
+
process.stdout.write(`complete, ${timer.elapsedSeconds.toString()} seconds`);
|
|
277
|
+
showMessage("");
|
|
278
|
+
}
|
|
279
|
+
/** import a WorkspaceDb to a cloud WorkspaceContainer. */
|
|
280
|
+
async function importWorkspaceDb(args) {
|
|
281
|
+
const container = getCloudContainer(args);
|
|
282
|
+
if ("" === (0, path_1.extname)(args.localFileName))
|
|
283
|
+
args.localFileName = `${args.localFileName}.${core_backend_1.ITwinWorkspaceDb.fileExt}`;
|
|
284
|
+
if (!core_backend_1.IModelJsFs.existsSync(args.localFileName))
|
|
285
|
+
args.localFileName = (0, path_1.join)(args.directory ?? core_backend_1.IModelHost.appWorkspace.containerDir, args.localFileName);
|
|
286
|
+
await core_backend_1.CloudSqlite.withWriteLock(args.user, container, async () => {
|
|
287
|
+
await performTransfer(container, "upload", args);
|
|
288
|
+
});
|
|
289
|
+
container.checkForChanges(); // so we can see newly imported WorkspaceDb
|
|
290
|
+
}
|
|
291
|
+
/** export a WorkspaceDb from a cloud WorkspaceContainer. */
|
|
292
|
+
async function exportWorkspaceDb(args) {
|
|
293
|
+
if (!(0, path_1.extname)(args.localFileName))
|
|
294
|
+
args.localFileName = `${args.localFileName}.${core_backend_1.ITwinWorkspaceDb.fileExt}`;
|
|
295
|
+
const dbParts = core_backend_1.ITwinWorkspaceContainer.parseDbFileName(args.dbName);
|
|
296
|
+
if (!dbParts.version)
|
|
297
|
+
throw new Error("exportDb requires a version");
|
|
298
|
+
await performTransfer(getCloudContainer(args), "download", args);
|
|
299
|
+
}
|
|
300
|
+
/** Delete a WorkspaceDb from a cloud WorkspaceContainer. */
|
|
301
|
+
async function deleteWorkspaceDb(args) {
|
|
302
|
+
const container = getCloudContainer(args);
|
|
303
|
+
await core_backend_1.CloudSqlite.withWriteLock(args.user, container, async () => {
|
|
304
|
+
return container.deleteDatabase(args.dbName);
|
|
305
|
+
});
|
|
306
|
+
showMessage(`deleted WorkspaceDb [${args.dbName}] from ${sayContainer(args)}`);
|
|
307
|
+
}
|
|
308
|
+
function sayContainer(args) {
|
|
309
|
+
return `container [${args.containerId}]`;
|
|
310
|
+
}
|
|
311
|
+
/** initialize (empty if it exists) a cloud WorkspaceContainer. */
|
|
312
|
+
async function initializeWorkspace(args) {
|
|
313
|
+
if (undefined === args.storageType || !args.accessName)
|
|
314
|
+
throw new Error("No cloud container supplied");
|
|
315
|
+
if (!args.noPrompt) {
|
|
316
|
+
const yesNo = await askQuestion(`Are you sure you want to initialize ${sayContainer(args)}"? [y/n]: `);
|
|
317
|
+
if (yesNo[0].toUpperCase() !== "Y")
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
const container = core_backend_1.CloudSqlite.createCloudContainer(args);
|
|
321
|
+
container.initializeContainer({ checksumBlockNames: true });
|
|
322
|
+
showMessage(`container "${args.containerId} initialized`);
|
|
323
|
+
}
|
|
324
|
+
/** purge unused (garbage) blocks from a WorkspaceContainer. */
|
|
325
|
+
async function purgeWorkspace(args) {
|
|
326
|
+
const container = getCloudContainer(args);
|
|
327
|
+
const nGarbage = container.garbageBlocks;
|
|
328
|
+
await core_backend_1.CloudSqlite.withWriteLock(args.user, container, async () => container.cleanDeletedBlocks());
|
|
329
|
+
container.checkForChanges(); // re-read manifest to get current garbage count
|
|
330
|
+
showMessage(`purged ${sayContainer(args)}. ${nGarbage - container.garbageBlocks} garbage blocks cleaned`);
|
|
331
|
+
}
|
|
332
|
+
/** detach a WorkspaceContainer from the local cache. */
|
|
333
|
+
async function detachWorkspace(args) {
|
|
334
|
+
const container = getCloudContainer(args);
|
|
335
|
+
container.detach();
|
|
336
|
+
showMessage(`detached ${sayContainer(args)}.`);
|
|
337
|
+
}
|
|
338
|
+
/** Make a copy of a WorkspaceDb with a new name. */
|
|
339
|
+
async function copyWorkspaceDb(args) {
|
|
340
|
+
const container = getCloudContainer(args);
|
|
341
|
+
const oldName = core_backend_1.ITwinWorkspaceContainer.resolveCloudFileName(container, args);
|
|
342
|
+
const newVersion = core_backend_1.ITwinWorkspaceContainer.parseDbFileName(args.newDbName);
|
|
343
|
+
core_backend_1.ITwinWorkspaceContainer.validateDbName(newVersion.dbName);
|
|
344
|
+
const newName = core_backend_1.ITwinWorkspaceContainer.makeDbFileName(newVersion.dbName, core_backend_1.ITwinWorkspaceContainer.validateVersion(newVersion.version));
|
|
345
|
+
await core_backend_1.CloudSqlite.withWriteLock(args.user, container, async () => container.copyDatabase(oldName, newName));
|
|
346
|
+
showMessage(`copied WorkspaceDb [${oldName}] to [${newName}] in ${sayContainer(args)}`);
|
|
347
|
+
}
|
|
348
|
+
/** Make a copy of a WorkspaceDb with a new name. */
|
|
349
|
+
async function versionWorkspaceDb(args) {
|
|
350
|
+
fixVersionArg(args);
|
|
351
|
+
const container = getCloudContainer(args);
|
|
352
|
+
const result = await core_backend_1.ITwinWorkspaceContainer.makeNewVersion(container, args, args.versionType);
|
|
353
|
+
showMessage(`created new version: [${result.newName}] from [${result.oldName}] in ${sayContainer(args)}`);
|
|
354
|
+
}
|
|
355
|
+
/** pin a WorkspaceDb from a WorkspaceContainer. */
|
|
356
|
+
async function preFetchWorkspaceDb(args) {
|
|
357
|
+
fixVersionArg(args);
|
|
358
|
+
const container = getCloudContainer(args);
|
|
359
|
+
const timer = new core_bentley_1.StopWatch("prefetch", true);
|
|
360
|
+
const prefetch = core_backend_1.CloudSqlite.startCloudPrefetch(container, args.dbFileName);
|
|
361
|
+
await prefetch.promise;
|
|
362
|
+
showMessage(`preFetched WorkspaceDb [${args.dbFileName}] in ${sayContainer(args)}, time=${timer.elapsedSeconds.toString()}`);
|
|
363
|
+
}
|
|
364
|
+
/** acquire the write lock for a WorkspaceContainer. */
|
|
365
|
+
async function acquireLock(args) {
|
|
366
|
+
const container = getCloudContainer(args);
|
|
367
|
+
if (container.hasWriteLock)
|
|
368
|
+
throw new Error(`write lock is already held for ${sayContainer(args)}`);
|
|
369
|
+
container.acquireWriteLock(args.user);
|
|
370
|
+
showMessage(`acquired lock for ${sayContainer(args)}`);
|
|
371
|
+
}
|
|
372
|
+
/** release the write lock for a WorkspaceContainer. */
|
|
373
|
+
async function releaseLock(args) {
|
|
374
|
+
const container = getCloudContainer(args);
|
|
375
|
+
if (!container.hasWriteLock)
|
|
376
|
+
throw new Error(`write lock is not held for ${sayContainer(args)}`);
|
|
377
|
+
container.releaseWriteLock();
|
|
378
|
+
showMessage(`released lock for ${sayContainer(args)}`);
|
|
379
|
+
}
|
|
380
|
+
/** clear the write lock for a WorkspaceContainer. */
|
|
381
|
+
async function clearWriteLock(args) {
|
|
382
|
+
const container = getCloudContainer(args);
|
|
383
|
+
container.clearWriteLock();
|
|
384
|
+
showMessage(`write lock cleared for ${sayContainer(args)}`);
|
|
385
|
+
}
|
|
386
|
+
/** query the list of WorkspaceDb in a WorkspaceContainer. */
|
|
387
|
+
async function queryWorkspaceDbs(args) {
|
|
388
|
+
const container = getCloudContainer(args);
|
|
389
|
+
const writeLockMsg = container.hasWriteLock ? ", writeLocked" : "";
|
|
390
|
+
const hasLocalMsg = container.hasLocalChanges ? ", has local changes" : "";
|
|
391
|
+
const nGarbage = container.garbageBlocks;
|
|
392
|
+
const garbageMsg = nGarbage ? `, ${nGarbage} garbage block${nGarbage > 1 ? "s" : ""}` : "";
|
|
393
|
+
const blockSize = container.blockSize;
|
|
394
|
+
showMessage(`WorkspaceDbs in ${sayContainer(args)}${writeLockMsg}${hasLocalMsg}${garbageMsg}`);
|
|
395
|
+
const dbs = container.queryDatabases(args.glob);
|
|
396
|
+
for (const dbName of dbs) {
|
|
397
|
+
const db = container.queryDatabase(dbName);
|
|
398
|
+
if (db) {
|
|
399
|
+
const dirty = db.dirtyBlocks ? `, ${db.dirtyBlocks} dirty` : "";
|
|
400
|
+
const editable = db.state === "copied" ? ", editable" : "";
|
|
401
|
+
showMessage(` "${dbName}", size=${friendlyFileSize(db.totalBlocks * blockSize)}, ${friendlyFileSize(db.localBlocks * blockSize)} downloaded (${(100 * db.localBlocks / db.totalBlocks).toFixed(0)}%)${editable}${dirty}`);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
/** Start `IModelHost`, then run a WorkspaceEditor command. Errors are logged to console. */
|
|
406
|
+
function runCommand(cmd) {
|
|
407
|
+
return async (args) => {
|
|
408
|
+
if (inScript)
|
|
409
|
+
return cmd(args);
|
|
410
|
+
try {
|
|
411
|
+
const workspace = {
|
|
412
|
+
containerDir: args.directory,
|
|
413
|
+
cloudCacheProps: {
|
|
414
|
+
nRequests: args.nRequests,
|
|
415
|
+
curlDiagnostics: args.curlDiagnostics,
|
|
416
|
+
},
|
|
417
|
+
};
|
|
418
|
+
await core_backend_1.IModelHost.startup({ workspace });
|
|
419
|
+
if (true === args.logging) {
|
|
420
|
+
core_bentley_1.Logger.initializeToConsole();
|
|
421
|
+
core_bentley_1.Logger.setLevel("CloudSqlite", core_bentley_1.LogLevel.Trace);
|
|
422
|
+
core_backend_1.IModelHost.appWorkspace.cloudCache?.setLogMask(core_backend_1.CloudSqlite.LoggingMask.All);
|
|
423
|
+
logTimer = setInterval(() => flushLog(), 250); // logging from other threads is buffered. This causes it to appear every 1/4 second.
|
|
424
|
+
}
|
|
425
|
+
await cmd(args);
|
|
426
|
+
}
|
|
427
|
+
catch (e) {
|
|
428
|
+
if (typeof e.message === "string")
|
|
429
|
+
console.error(e.message);
|
|
430
|
+
else
|
|
431
|
+
console.log(core_bentley_1.BentleyError.getErrorMessage(e));
|
|
432
|
+
}
|
|
433
|
+
finally {
|
|
434
|
+
if (logTimer) {
|
|
435
|
+
flushLog();
|
|
436
|
+
clearInterval(logTimer);
|
|
437
|
+
}
|
|
438
|
+
await core_backend_1.IModelHost.shutdown();
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
const type = { alias: "t", describe: "Type of resource", choices: ["blob", "string", "file"], demandOption: true };
|
|
443
|
+
const addOrReplace = {
|
|
444
|
+
rscName: { alias: "n", describe: "Resource name for file", string: true },
|
|
445
|
+
root: { alias: "r", describe: "Root directory. Path parts after this will be saved in resource name", string: true },
|
|
446
|
+
type,
|
|
447
|
+
};
|
|
448
|
+
Yargs.usage("Edits or lists contents of a WorkspaceDb");
|
|
449
|
+
Yargs.wrap(Math.min(150, Yargs.terminalWidth()));
|
|
450
|
+
Yargs.env("WORKSPACE_EDITOR");
|
|
451
|
+
Yargs.config();
|
|
452
|
+
Yargs.help();
|
|
453
|
+
Yargs.version("V2.0");
|
|
454
|
+
Yargs.options({
|
|
455
|
+
directory: { alias: "d", describe: "Directory to use for WorkspaceContainers", string: true },
|
|
456
|
+
nRequests: { describe: "Number of simultaneous http requests for cloud operations", number: true, hidden: true },
|
|
457
|
+
containerId: { alias: "c", describe: "ContainerId for WorkspaceDb", string: true, demandOption: true },
|
|
458
|
+
user: { describe: "String shown in cloud container locked message", string: true, default: "workspace-editor" },
|
|
459
|
+
accessName: { alias: "a", describe: "Cloud storage account name for container", string: true },
|
|
460
|
+
accessToken: { describe: "Token that grants access to the container (either SAS or account key)", string: true, default: "" },
|
|
461
|
+
storageType: { describe: "Cloud storage module type", string: true, default: "azure?sas=1" },
|
|
462
|
+
logging: { describe: "enable log messages", boolean: true, default: false, hidden: true },
|
|
463
|
+
prefetch: { boolean: true, default: false, hidden: true },
|
|
464
|
+
curlDiagnostics: { boolean: true, default: false, hidden: true },
|
|
465
|
+
});
|
|
466
|
+
Yargs.command({
|
|
467
|
+
command: "add <dbName> <files>",
|
|
468
|
+
describe: "add files into a WorkspaceDb",
|
|
469
|
+
builder: addOrReplace,
|
|
470
|
+
handler: runCommand(addResource),
|
|
471
|
+
});
|
|
472
|
+
Yargs.command({
|
|
473
|
+
command: "replace <dbName> <files>",
|
|
474
|
+
describe: "replace files in a WorkspaceDb",
|
|
475
|
+
builder: addOrReplace,
|
|
476
|
+
handler: runCommand(replaceResource),
|
|
477
|
+
});
|
|
478
|
+
Yargs.command({
|
|
479
|
+
command: "remove <dbName> <rscName>",
|
|
480
|
+
describe: "remove a resource from a WorkspaceDb",
|
|
481
|
+
builder: { type },
|
|
482
|
+
handler: runCommand(removeResource),
|
|
483
|
+
});
|
|
484
|
+
Yargs.command({
|
|
485
|
+
command: "extract <dbName> <rscName> <fileName>",
|
|
486
|
+
describe: "extract a resource from a WorkspaceDb into a local file",
|
|
487
|
+
builder: { type },
|
|
488
|
+
handler: runCommand(extractResource),
|
|
489
|
+
});
|
|
490
|
+
Yargs.command({
|
|
491
|
+
command: "listDb <dbName>",
|
|
492
|
+
describe: "list the contents of a WorkspaceDb",
|
|
493
|
+
builder: {
|
|
494
|
+
strings: { alias: "s", describe: "list string resources", boolean: true, default: false },
|
|
495
|
+
blobs: { alias: "b", describe: "list blob resources", boolean: true, default: false },
|
|
496
|
+
files: { alias: "f", describe: "list file resources", boolean: true, default: false },
|
|
497
|
+
},
|
|
498
|
+
handler: runCommand(listWorkspaceDb),
|
|
499
|
+
});
|
|
500
|
+
Yargs.command({
|
|
501
|
+
command: "deleteDb <dbName>",
|
|
502
|
+
describe: "delete a WorkspaceDb from a cloud container",
|
|
503
|
+
handler: runCommand(deleteWorkspaceDb),
|
|
504
|
+
});
|
|
505
|
+
Yargs.command({
|
|
506
|
+
command: "createDb <dbName>",
|
|
507
|
+
describe: "create a new WorkspaceDb",
|
|
508
|
+
handler: runCommand(createWorkspaceDb),
|
|
509
|
+
});
|
|
510
|
+
Yargs.command({
|
|
511
|
+
command: "copyDb <dbName> <newDbName>",
|
|
512
|
+
describe: "make a copy of a WorkspaceDb in a cloud container with a new name",
|
|
513
|
+
handler: runCommand(copyWorkspaceDb),
|
|
514
|
+
});
|
|
515
|
+
Yargs.command({
|
|
516
|
+
command: "versionDb <dbName>",
|
|
517
|
+
describe: "make a new version of a WorkspaceDb",
|
|
518
|
+
builder: {
|
|
519
|
+
versionType: { describe: "the type of version to create", default: "patch", string: true, choices: ["major", "minor", "patch"] },
|
|
520
|
+
},
|
|
521
|
+
handler: runCommand(versionWorkspaceDb),
|
|
522
|
+
});
|
|
523
|
+
Yargs.command({
|
|
524
|
+
command: "prefetchDb <dbName>",
|
|
525
|
+
describe: false,
|
|
526
|
+
handler: runCommand(preFetchWorkspaceDb),
|
|
527
|
+
});
|
|
528
|
+
Yargs.command({
|
|
529
|
+
command: "vacuumDb <dbName>",
|
|
530
|
+
describe: "vacuum a WorkspaceDb",
|
|
531
|
+
handler: runCommand(vacuumWorkspaceDb),
|
|
532
|
+
});
|
|
533
|
+
Yargs.command({
|
|
534
|
+
command: "importDb <dbName> <localFileName>",
|
|
535
|
+
describe: "import a WorkspaceDb into a cloud container",
|
|
536
|
+
builder: {
|
|
537
|
+
noVacuum: { describe: "Don't vacuum source Db before importing", boolean: true },
|
|
538
|
+
},
|
|
539
|
+
handler: runCommand(importWorkspaceDb),
|
|
540
|
+
});
|
|
541
|
+
Yargs.command({
|
|
542
|
+
command: "exportDb <dbName> <localFileName>",
|
|
543
|
+
describe: "export a WorkspaceDb from a cloud container to a local file",
|
|
544
|
+
handler: runCommand(exportWorkspaceDb),
|
|
545
|
+
});
|
|
546
|
+
Yargs.command({
|
|
547
|
+
command: "queryDbs [glob]",
|
|
548
|
+
describe: "query the list of WorkspaceDbs in a cloud container",
|
|
549
|
+
handler: runCommand(queryWorkspaceDbs),
|
|
550
|
+
});
|
|
551
|
+
Yargs.command({
|
|
552
|
+
command: "acquireLock",
|
|
553
|
+
describe: "acquire the write lock for a cloud container",
|
|
554
|
+
handler: runCommand(acquireLock),
|
|
555
|
+
});
|
|
556
|
+
Yargs.command({
|
|
557
|
+
command: "releaseLock",
|
|
558
|
+
describe: "release the write lock for a cloud container",
|
|
559
|
+
handler: runCommand(releaseLock),
|
|
560
|
+
});
|
|
561
|
+
Yargs.command({
|
|
562
|
+
command: "clearWriteLock",
|
|
563
|
+
describe: "clear the write lock for a cloud container. Note: this can be dangerous!",
|
|
564
|
+
handler: runCommand(clearWriteLock),
|
|
565
|
+
});
|
|
566
|
+
Yargs.command({
|
|
567
|
+
command: "purgeWorkspace",
|
|
568
|
+
describe: "purge deleted blocks from a WorkspaceContainer",
|
|
569
|
+
handler: runCommand(purgeWorkspace),
|
|
570
|
+
});
|
|
571
|
+
Yargs.command({
|
|
572
|
+
command: "detachWorkspace",
|
|
573
|
+
describe: false,
|
|
574
|
+
handler: runCommand(detachWorkspace),
|
|
575
|
+
});
|
|
576
|
+
Yargs.command({
|
|
577
|
+
command: "initializeWorkspace",
|
|
578
|
+
describe: "initialize a WorkspaceContainer (empties if already initialized)",
|
|
579
|
+
builder: {
|
|
580
|
+
noPrompt: { describe: "Skip prompt", boolean: true, default: false },
|
|
581
|
+
},
|
|
582
|
+
handler: runCommand(initializeWorkspace),
|
|
583
|
+
});
|
|
584
|
+
/** execute an "@" script - a list of WorkspaceEditor commands */
|
|
585
|
+
async function runScript(arg) {
|
|
586
|
+
inScript = true;
|
|
587
|
+
const val = fs.readFileSync(arg.scriptName, "utf-8");
|
|
588
|
+
const lines = val.split(/\r?\n/);
|
|
589
|
+
let i = 0;
|
|
590
|
+
for (let line of lines) {
|
|
591
|
+
i++;
|
|
592
|
+
line = line.split("#")[0].trim(); // ignore leading/trailing whitespace and comments (anything after a "#")
|
|
593
|
+
if (line.length === 0)
|
|
594
|
+
continue; // blank line
|
|
595
|
+
await Yargs.parseAsync(line, {}, (err, _argv, _output) => {
|
|
596
|
+
if (err) {
|
|
597
|
+
console.error(`${arg.scriptName}:${i} [${line}] : ${core_bentley_1.BentleyError.getErrorMessage(err)}`);
|
|
598
|
+
process.exit(1);
|
|
599
|
+
}
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
/** Parse and execute WorkspaceEditor commands */
|
|
604
|
+
async function main() {
|
|
605
|
+
if (process.argv.length > 1 && process.argv[2]?.[0] === "@") {
|
|
606
|
+
const parsed = Yargs.parseSync(process.argv.slice(3));
|
|
607
|
+
if (parsed.config)
|
|
608
|
+
process.env.WORKSPACE_EDITOR_CONFIG = parsed.config;
|
|
609
|
+
await runCommand(runScript)({ ...parsed, scriptName: process.argv[2].substring(1) });
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
Yargs.strictCommands();
|
|
613
|
+
Yargs.demandCommand();
|
|
614
|
+
await Yargs.parseAsync();
|
|
615
|
+
}
|
|
616
|
+
void main();
|
|
617
617
|
//# sourceMappingURL=WorkspaceEditor.js.map
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@itwin/workspace-editor",
|
|
3
3
|
"license": "MIT",
|
|
4
4
|
"main": "lib/WorkspaceEditor.js",
|
|
5
|
-
"version": "4.0.0-dev.
|
|
5
|
+
"version": "4.0.0-dev.54",
|
|
6
6
|
"bin": {
|
|
7
7
|
"WorkspaceEditor": "./lib/WorkspaceEditor.js"
|
|
8
8
|
},
|
|
@@ -14,15 +14,15 @@
|
|
|
14
14
|
"url": "https://github.com/iTwin/itwinjs-core/tree/master/utils/workspace-editor"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@itwin/core-bentley": "4.0.0-dev.
|
|
18
|
-
"@itwin/core-common": "4.0.0-dev.
|
|
19
|
-
"@itwin/core-backend": "4.0.0-dev.
|
|
17
|
+
"@itwin/core-bentley": "4.0.0-dev.54",
|
|
18
|
+
"@itwin/core-common": "4.0.0-dev.54",
|
|
19
|
+
"@itwin/core-backend": "4.0.0-dev.54",
|
|
20
20
|
"glob": "^7.1.2",
|
|
21
21
|
"yargs": "^17.4.0"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@itwin/build-tools": "4.0.0-dev.
|
|
25
|
-
"@itwin/eslint-plugin": "
|
|
24
|
+
"@itwin/build-tools": "4.0.0-dev.54",
|
|
25
|
+
"@itwin/eslint-plugin": "^4.0.0-dev.32",
|
|
26
26
|
"@types/chai": "4.3.1",
|
|
27
27
|
"@types/mocha": "^8.2.2",
|
|
28
28
|
"@types/yargs": "17.0.19",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"eslint": "^7.11.0",
|
|
31
31
|
"mocha": "^10.0.0",
|
|
32
32
|
"rimraf": "^3.0.2",
|
|
33
|
-
"typescript": "~
|
|
33
|
+
"typescript": "~5.0.2"
|
|
34
34
|
},
|
|
35
35
|
"eslintConfig": {
|
|
36
36
|
"plugins": [
|