@tomvandig/mosaic 0.1.0 → 0.2.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.
Files changed (3) hide show
  1. package/README.md +6 -6
  2. package/dist/mosaic.cjs +195 -121
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -5,6 +5,9 @@ incremental collaboration on geometric, structured data.
5
5
 
6
6
  ```bash
7
7
  npx @tomvandig/mosaic help
8
+
9
+ # convert a glTF model, straight from a URL
10
+ npx @tomvandig/mosaic pack https://tomvandig.github.io/mosaic/dist/docs/models/DamagedHelmet.glb
8
11
  ```
9
12
 
10
13
  Or install it, and the command is `mosaic`:
@@ -20,12 +23,9 @@ Needs Node.js 24 or later.
20
23
 
21
24
  | Command | |
22
25
  | --- | --- |
23
- | `mosaic pack <input.json> [output.tsr]` | Pack a Mosaic source document into a `.tsr` archive |
24
- | `mosaic gltf <input.gltf\|.glb> [output.mosaic.json] [--stable-ids]` | Convert glTF into a source document |
25
- | `mosaic gltf-pack <input.gltf\|.glb> [output.tsr] [--stable-ids]` | Convert glTF straight into an archive |
26
- | `mosaic ifc <input.ifc> [output.mosaic.json]` | Convert an IFC model into a source document |
27
- | `mosaic ifc-pack <input.ifc> [output.tsr]` | Convert an IFC model straight into an archive |
28
- | `mosaic svg-pack <input.svg> [output.tsr]` | Pack an SVG into an archive of one node |
26
+ | `mosaic pack <input> [output.tsr] [flags]` | Pack into a `.tsr` archive, by extension: a source document (`.json`), glTF (`.gltf`, `.glb`, or a URL), IFC (`.ifc`) or SVG (`.svg`) |
27
+ | `mosaic gltf <input.gltf\|.glb\|url> [output.mosaic.json] [--stable-ids]` | Convert glTF into an editable source document |
28
+ | `mosaic ifc <input.ifc> [output.mosaic.json] [--center] [--no-geometry] [--seed=<text>]` | Convert an IFC model into an editable source document |
29
29
  | `mosaic compose <input.tsr\|.duckdb> [output.glb]` | Compose an archive and its imports into a `.glb` |
30
30
  | `mosaic serve <database.duckdb> [--port <n>] [--host <name>]` | Serve the Mosaic API, and a viewer, over a database |
31
31
  | `mosaic codegen <input_dir> <output_dir> <ts\|cs>` | Generate typed classes from component schemas |
package/dist/mosaic.cjs CHANGED
@@ -16242,14 +16242,14 @@ var require_parser = __commonJS({
16242
16242
  case "scalar":
16243
16243
  case "single-quoted-scalar":
16244
16244
  case "double-quoted-scalar": {
16245
- const fs9 = this.flowScalar(this.type);
16245
+ const fs10 = this.flowScalar(this.type);
16246
16246
  if (atNextItem || it.value) {
16247
- map.items.push({ start, key: fs9, sep: [] });
16247
+ map.items.push({ start, key: fs10, sep: [] });
16248
16248
  this.onKeyLine = true;
16249
16249
  } else if (it.sep) {
16250
- this.stack.push(fs9);
16250
+ this.stack.push(fs10);
16251
16251
  } else {
16252
- Object.assign(it, { key: fs9, sep: [] });
16252
+ Object.assign(it, { key: fs10, sep: [] });
16253
16253
  this.onKeyLine = true;
16254
16254
  }
16255
16255
  return;
@@ -16377,13 +16377,13 @@ var require_parser = __commonJS({
16377
16377
  case "scalar":
16378
16378
  case "single-quoted-scalar":
16379
16379
  case "double-quoted-scalar": {
16380
- const fs9 = this.flowScalar(this.type);
16380
+ const fs10 = this.flowScalar(this.type);
16381
16381
  if (!it || it.value)
16382
- fc.items.push({ start: [], key: fs9, sep: [] });
16382
+ fc.items.push({ start: [], key: fs10, sep: [] });
16383
16383
  else if (it.sep)
16384
- this.stack.push(fs9);
16384
+ this.stack.push(fs10);
16385
16385
  else
16386
- Object.assign(it, { key: fs9, sep: [] });
16386
+ Object.assign(it, { key: fs10, sep: [] });
16387
16387
  return;
16388
16388
  }
16389
16389
  case "flow-map-end":
@@ -66259,11 +66259,11 @@ var require_dist4 = __commonJS({
66259
66259
  // node_modules/is-url/index.js
66260
66260
  var require_is_url = __commonJS({
66261
66261
  "node_modules/is-url/index.js"(exports2, module2) {
66262
- module2.exports = isUrl;
66262
+ module2.exports = isUrl2;
66263
66263
  var protocolAndDomainRE = /^(?:\w+:)?\/\/(\S+)$/;
66264
66264
  var localhostDomainRE = /^localhost[\:?\d]*(?:[^\:?\d]\S*)?$/;
66265
66265
  var nonLocalhostDomainRE = /^[^\s\.]+\.\S{2,}$/;
66266
- function isUrl(string) {
66266
+ function isUrl2(string) {
66267
66267
  if (typeof string !== "string") {
66268
66268
  return false;
66269
66269
  }
@@ -69046,12 +69046,12 @@ var require_lib2 = __commonJS({
69046
69046
  const dest = new URL$1(destination).protocol;
69047
69047
  return orig === dest;
69048
69048
  };
69049
- function fetch(url, opts) {
69050
- if (!fetch.Promise) {
69049
+ function fetch2(url, opts) {
69050
+ if (!fetch2.Promise) {
69051
69051
  throw new Error("native promise missing, set fetch.Promise to your favorite alternative");
69052
69052
  }
69053
- Body.Promise = fetch.Promise;
69054
- return new fetch.Promise(function(resolve, reject) {
69053
+ Body.Promise = fetch2.Promise;
69054
+ return new fetch2.Promise(function(resolve, reject) {
69055
69055
  const request = new Request(url, opts);
69056
69056
  const options = getNodeRequestOptions(request);
69057
69057
  const send2 = (options.protocol === "https:" ? https : http2).request;
@@ -69122,7 +69122,7 @@ var require_lib2 = __commonJS({
69122
69122
  req.on("response", function(res) {
69123
69123
  clearTimeout(reqTimeout);
69124
69124
  const headers = createHeadersLenient(res.headers);
69125
- if (fetch.isRedirect(res.statusCode)) {
69125
+ if (fetch2.isRedirect(res.statusCode)) {
69126
69126
  const location2 = headers.get("Location");
69127
69127
  let locationURL = null;
69128
69128
  try {
@@ -69184,7 +69184,7 @@ var require_lib2 = __commonJS({
69184
69184
  requestOpts.body = void 0;
69185
69185
  requestOpts.headers.delete("content-length");
69186
69186
  }
69187
- resolve(fetch(new Request(locationURL, requestOpts)));
69187
+ resolve(fetch2(new Request(locationURL, requestOpts)));
69188
69188
  finalize();
69189
69189
  return;
69190
69190
  }
@@ -69276,11 +69276,11 @@ var require_lib2 = __commonJS({
69276
69276
  stream.end();
69277
69277
  }
69278
69278
  }
69279
- fetch.isRedirect = function(code) {
69279
+ fetch2.isRedirect = function(code) {
69280
69280
  return code === 301 || code === 302 || code === 303 || code === 307 || code === 308;
69281
69281
  };
69282
- fetch.Promise = global.Promise;
69283
- module2.exports = exports2 = fetch;
69282
+ fetch2.Promise = global.Promise;
69283
+ module2.exports = exports2 = fetch2;
69284
69284
  Object.defineProperty(exports2, "__esModule", { value: true });
69285
69285
  exports2.default = exports2;
69286
69286
  exports2.Headers = Headers;
@@ -69296,19 +69296,19 @@ var require_node_ponyfill = __commonJS({
69296
69296
  "node_modules/cross-fetch/dist/node-ponyfill.js"(exports2, module2) {
69297
69297
  var nodeFetch = require_lib2();
69298
69298
  var realFetch = nodeFetch.default || nodeFetch;
69299
- var fetch = function(url, options) {
69299
+ var fetch2 = function(url, options) {
69300
69300
  if (/^\/\//.test(url)) {
69301
69301
  url = "https:" + url;
69302
69302
  }
69303
69303
  return realFetch.call(this, url, options);
69304
69304
  };
69305
- fetch.ponyfill = true;
69306
- module2.exports = exports2 = fetch;
69307
- exports2.fetch = fetch;
69305
+ fetch2.ponyfill = true;
69306
+ module2.exports = exports2 = fetch2;
69307
+ exports2.fetch = fetch2;
69308
69308
  exports2.Headers = nodeFetch.Headers;
69309
69309
  exports2.Request = nodeFetch.Request;
69310
69310
  exports2.Response = nodeFetch.Response;
69311
- exports2.default = fetch;
69311
+ exports2.default = fetch2;
69312
69312
  }
69313
69313
  });
69314
69314
 
@@ -69319,11 +69319,11 @@ var require_fetch = __commonJS({
69319
69319
  var _a;
69320
69320
  Object.defineProperty(exports2, "__esModule", { value: true });
69321
69321
  exports2.fetch = void 0;
69322
- var fetch;
69322
+ var fetch2;
69323
69323
  try {
69324
- exports2.fetch = fetch = (_a = global.fetch) !== null && _a !== void 0 ? _a : require_node_ponyfill().default;
69324
+ exports2.fetch = fetch2 = (_a = global.fetch) !== null && _a !== void 0 ? _a : require_node_ponyfill().default;
69325
69325
  } catch (_b) {
69326
- exports2.fetch = fetch = require_node_ponyfill().default;
69326
+ exports2.fetch = fetch2 = require_node_ponyfill().default;
69327
69327
  }
69328
69328
  }
69329
69329
  });
@@ -69446,7 +69446,7 @@ var require_NodeIO = __commonJS({
69446
69446
  Object.defineProperty(exports2, "__esModule", { value: true });
69447
69447
  exports2.readableFromFileOrURL = readableFromFileOrURL;
69448
69448
  exports2.readFromFileOrURL = readFromFileOrURL;
69449
- var fs9 = __importStar(require("fs"));
69449
+ var fs10 = __importStar(require("fs"));
69450
69450
  var path17 = __importStar(require("path"));
69451
69451
  var ts_necessities_1 = require_dist3();
69452
69452
  var browser_or_node_1 = require_dist4();
@@ -69496,10 +69496,10 @@ var require_NodeIO = __commonJS({
69496
69496
  return body;
69497
69497
  }
69498
69498
  function resolveSymbolicLink(filePath) {
69499
- if (!fs9.lstatSync(filePath).isSymbolicLink()) {
69499
+ if (!fs10.lstatSync(filePath).isSymbolicLink()) {
69500
69500
  return filePath;
69501
69501
  }
69502
- const linkPath = fs9.readlinkSync(filePath);
69502
+ const linkPath = fs10.readlinkSync(filePath);
69503
69503
  if (path17.isAbsolute(linkPath)) {
69504
69504
  return linkPath;
69505
69505
  }
@@ -69519,8 +69519,8 @@ var require_NodeIO = __commonJS({
69519
69519
  return process.stdin;
69520
69520
  }
69521
69521
  const filePath = resolveSymbolicLink(fileOrURL);
69522
- if (fs9.existsSync(filePath)) {
69523
- return fs9.createReadStream(filePath, "utf8");
69522
+ if (fs10.existsSync(filePath)) {
69523
+ return fs10.createReadStream(filePath, "utf8");
69524
69524
  }
69525
69525
  }
69526
69526
  } catch (e) {
@@ -81589,8 +81589,8 @@ var init_ComposeFs = __esm({
81589
81589
  var require_duckdb_binding = __commonJS({
81590
81590
  "src/duckdb-binding.cjs"(exports2, module2) {
81591
81591
  "use strict";
81592
- var fs9 = require("node:fs");
81593
- var os = require("node:os");
81592
+ var fs10 = require("node:fs");
81593
+ var os2 = require("node:os");
81594
81594
  var path17 = require("node:path");
81595
81595
  var zlib2 = require("node:zlib");
81596
81596
  var { createRequire: createRequire3 } = require("node:module");
@@ -81599,36 +81599,36 @@ var require_duckdb_binding = __commonJS({
81599
81599
  function cacheRoot() {
81600
81600
  const asked = process.env.MOSAIC_DUCKDB_CACHE;
81601
81601
  if (asked) return asked;
81602
- const home = os.homedir();
81602
+ const home = os2.homedir();
81603
81603
  if (process.platform === "win32") {
81604
- return process.env.LOCALAPPDATA || (home ? path17.join(home, "AppData", "Local") : os.tmpdir());
81604
+ return process.env.LOCALAPPDATA || (home ? path17.join(home, "AppData", "Local") : os2.tmpdir());
81605
81605
  }
81606
81606
  if (process.platform === "darwin") {
81607
- return home ? path17.join(home, "Library", "Caches") : os.tmpdir();
81607
+ return home ? path17.join(home, "Library", "Caches") : os2.tmpdir();
81608
81608
  }
81609
- return process.env.XDG_CACHE_HOME || (home ? path17.join(home, ".cache") : os.tmpdir());
81609
+ return process.env.XDG_CACHE_HOME || (home ? path17.join(home, ".cache") : os2.tmpdir());
81610
81610
  }
81611
81611
  function unpack(sea) {
81612
81612
  const into = path17.join(cacheRoot(), "mosaic", "duckdb-" + VERSION2);
81613
81613
  const keys = sea.getAssetKeys().filter((key) => key.startsWith(PREFIX));
81614
81614
  if (keys.length === 0) return void 0;
81615
- fs9.mkdirSync(into, { recursive: true });
81615
+ fs10.mkdirSync(into, { recursive: true });
81616
81616
  for (const key of keys) {
81617
81617
  const name = path17.basename(key).replace(/\.br$/, "");
81618
81618
  const target = path17.join(into, name);
81619
81619
  try {
81620
- if (fs9.statSync(target).size > 0) continue;
81620
+ if (fs10.statSync(target).size > 0) continue;
81621
81621
  } catch {
81622
81622
  }
81623
81623
  const packed = Buffer.from(sea.getRawAsset(key));
81624
81624
  const bytes = key.endsWith(".br") ? zlib2.brotliDecompressSync(packed) : packed;
81625
81625
  const partial = target + "." + process.pid + ".part";
81626
- fs9.writeFileSync(partial, bytes);
81626
+ fs10.writeFileSync(partial, bytes);
81627
81627
  try {
81628
- fs9.renameSync(partial, target);
81628
+ fs10.renameSync(partial, target);
81629
81629
  } catch (error) {
81630
- fs9.rmSync(partial, { force: true });
81631
- if (!fs9.existsSync(target)) throw error;
81630
+ fs10.rmSync(partial, { force: true });
81631
+ if (!fs10.existsSync(target)) throw error;
81632
81632
  }
81633
81633
  }
81634
81634
  return into;
@@ -92030,11 +92030,11 @@ async function exportArchivesToDatabase(archivePaths, databasePath) {
92030
92030
  for (const archivePath of archivePaths) inserted.push(await database.insertArchive(archivePath));
92031
92031
  return { database, inserted };
92032
92032
  }
92033
- var import_node_fs7, import_node_path13, FIXED_SCHEMA, BATCH_ROWS, FROM_JSON_ROWS, MosaicDatabase;
92033
+ var import_node_fs8, import_node_path13, FIXED_SCHEMA, BATCH_ROWS, FROM_JSON_ROWS, MosaicDatabase;
92034
92034
  var init_Export = __esm({
92035
92035
  "../ts/src/duckdb/Export.ts"() {
92036
92036
  "use strict";
92037
- import_node_fs7 = __toESM(require("node:fs"), 1);
92037
+ import_node_fs8 = __toESM(require("node:fs"), 1);
92038
92038
  import_node_path13 = __toESM(require("node:path"), 1);
92039
92039
  init_runtime();
92040
92040
  init_MosaicFile();
@@ -92110,7 +92110,7 @@ var init_Export = __esm({
92110
92110
  * so the same call makes a new database or adds to an existing one.
92111
92111
  */
92112
92112
  static async open(databasePath) {
92113
- if (databasePath !== ":memory:") import_node_fs7.default.mkdirSync(import_node_path13.default.dirname(import_node_path13.default.resolve(databasePath)), { recursive: true });
92113
+ if (databasePath !== ":memory:") import_node_fs8.default.mkdirSync(import_node_path13.default.dirname(import_node_path13.default.resolve(databasePath)), { recursive: true });
92114
92114
  const { DuckDBInstance } = await duckdb();
92115
92115
  const instance = await DuckDBInstance.create(databasePath);
92116
92116
  const connection = await instance.connect();
@@ -92156,9 +92156,9 @@ var init_Export = __esm({
92156
92156
  }
92157
92157
  /** Reads an archive and appends it. Nothing already in the database is touched. */
92158
92158
  async insertArchive(archivePath, options = {}) {
92159
- if (!import_node_fs7.default.existsSync(archivePath)) throw new Error(`File ${archivePath} does not exist`);
92159
+ if (!import_node_fs8.default.existsSync(archivePath)) throw new Error(`File ${archivePath} does not exist`);
92160
92160
  const source = import_node_path13.default.resolve(archivePath);
92161
- const file = await LoadMosaicFile(import_node_fs7.default.readFileSync(source));
92161
+ const file = await LoadMosaicFile(import_node_fs8.default.readFileSync(source));
92162
92162
  return await this.insertFile(file, options.fileId ?? import_node_path13.default.basename(source).split(".")[0], source);
92163
92163
  }
92164
92164
  /** Appends an already-loaded file. */
@@ -92789,7 +92789,7 @@ function glbOutputPathForDatabase(databasePath) {
92789
92789
  return import_node_path14.default.join(import_node_path14.default.dirname(databasePath), `${stem}${GLB_EXTENSION}`);
92790
92790
  }
92791
92791
  async function composeDatabaseToGlb(databasePath, outputPath, options = {}) {
92792
- if (!import_node_fs8.default.existsSync(databasePath)) throw new Error(`File ${databasePath} does not exist`);
92792
+ if (!import_node_fs9.default.existsSync(databasePath)) throw new Error(`File ${databasePath} does not exist`);
92793
92793
  const database = await MosaicDatabase.open(databasePath);
92794
92794
  try {
92795
92795
  const wanted = options.files;
@@ -92798,8 +92798,8 @@ async function composeDatabaseToGlb(databasePath, outputPath, options = {}) {
92798
92798
  const composed = mosaicToGltf(file);
92799
92799
  const target = import_node_path14.default.resolve(outputPath ?? glbOutputPathForDatabase(import_node_path14.default.resolve(databasePath)));
92800
92800
  const bytes = writeGlb(composed.document, composed.binary);
92801
- import_node_fs8.default.mkdirSync(import_node_path14.default.dirname(target), { recursive: true });
92802
- import_node_fs8.default.writeFileSync(target, bytes);
92801
+ import_node_fs9.default.mkdirSync(import_node_path14.default.dirname(target), { recursive: true });
92802
+ import_node_fs9.default.writeFileSync(target, bytes);
92803
92803
  return {
92804
92804
  outputPath: target,
92805
92805
  byteLength: bytes.byteLength,
@@ -92813,11 +92813,11 @@ async function composeDatabaseToGlb(databasePath, outputPath, options = {}) {
92813
92813
  await database.close();
92814
92814
  }
92815
92815
  }
92816
- var import_node_fs8, import_node_path14;
92816
+ var import_node_fs9, import_node_path14;
92817
92817
  var init_Compose = __esm({
92818
92818
  "../ts/src/duckdb/Compose.ts"() {
92819
92819
  "use strict";
92820
- import_node_fs8 = __toESM(require("node:fs"), 1);
92820
+ import_node_fs9 = __toESM(require("node:fs"), 1);
92821
92821
  import_node_path14 = __toESM(require("node:path"), 1);
92822
92822
  init_MosaicToGltf();
92823
92823
  init_GlbWriter();
@@ -95354,7 +95354,7 @@ async function runCodegen(argv) {
95354
95354
  }
95355
95355
 
95356
95356
  // src/commands/pack.ts
95357
- var import_node_path6 = __toESM(require("node:path"), 1);
95357
+ var import_node_path11 = __toESM(require("node:path"), 1);
95358
95358
 
95359
95359
  // ../ts/src/index.ts
95360
95360
  init_MosaicFile();
@@ -95989,7 +95989,7 @@ function svgToMosaic(svg, options = {}) {
95989
95989
  dataVersion: options.provenance?.dataVersion ?? "1.0.0",
95990
95990
  author: options.provenance?.author ?? "",
95991
95991
  timestamp: options.provenance?.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
95992
- application: options.provenance?.application ?? "mosaic svg-pack"
95992
+ application: options.provenance?.application ?? "mosaic pack"
95993
95993
  },
95994
95994
  nodes: [{
95995
95995
  id: nodeId,
@@ -96043,27 +96043,49 @@ init_ComposeFs();
96043
96043
  // ../ts/src/index.ts
96044
96044
  init_MosaicIndexFile();
96045
96045
 
96046
- // src/commands/pack.ts
96047
- var PACK_USAGE = `mosaic pack <input.json> [output${MOSAIC_ARCHIVE_EXTENSION}]`;
96048
- async function runPack(argv) {
96049
- const [input_path, output_path] = argv;
96050
- if (input_path === void 0) {
96051
- throw new Error(`Usage: ${PACK_USAGE}`);
96052
- }
96053
- const result = await packMosaicSourceFile(input_path, output_path);
96054
- console.log(`Packed: ${import_node_path6.default.resolve(input_path)} -> ${result.outputPath}`);
96055
- console.log();
96056
- console.log(`Component tables (${result.componentCount} rows):`);
96057
- result.tables.forEach((table) => console.log(` - ${table}`));
96058
- console.log();
96059
- console.log(`Wrote ${result.byteLength} bytes.`);
96060
- }
96061
-
96062
96046
  // src/commands/gltf.ts
96063
- var import_node_path7 = __toESM(require("node:path"), 1);
96047
+ var import_node_fs6 = __toESM(require("node:fs"), 1);
96048
+ var import_node_os = __toESM(require("node:os"), 1);
96049
+ var import_node_path6 = __toESM(require("node:path"), 1);
96064
96050
  var STABLE_IDS = "--stable-ids";
96065
- var GLTF_USAGE = `mosaic gltf <input.gltf|input.glb> [output${MOSAIC_SOURCE_EXTENSION}] [${STABLE_IDS}]`;
96066
- var GLTF_PACK_USAGE = `mosaic gltf-pack <input.gltf|input.glb> [output.tsr] [${STABLE_IDS}]`;
96051
+ var GLTF_USAGE = `mosaic gltf <input.gltf|input.glb|url> [output${MOSAIC_SOURCE_EXTENSION}] [${STABLE_IDS}]`;
96052
+ var GLTF_PACK_USAGE = `mosaic pack <input.gltf|input.glb|url> [output.tsr] [${STABLE_IDS}]`;
96053
+ var isUrl = (input) => /^https?:\/\//i.test(input);
96054
+ var isRelative = (uri) => typeof uri === "string" && uri.length > 0 && !/^[a-z][a-z0-9+.-]*:/i.test(uri);
96055
+ async function download(url, target) {
96056
+ const response = await fetch(url);
96057
+ if (!response.ok) throw new Error(`Could not download ${url}: ${response.status} ${response.statusText}`);
96058
+ import_node_fs6.default.mkdirSync(import_node_path6.default.dirname(target), { recursive: true });
96059
+ import_node_fs6.default.writeFileSync(target, new Uint8Array(await response.arrayBuffer()));
96060
+ }
96061
+ async function localInput(input) {
96062
+ if (!isUrl(input)) return { file: input, done() {
96063
+ } };
96064
+ const url = new URL(input);
96065
+ const name = decodeURIComponent(import_node_path6.default.posix.basename(url.pathname)) || "model.glb";
96066
+ const folder = import_node_fs6.default.mkdtempSync(import_node_path6.default.join(import_node_os.default.tmpdir(), "mosaic-gltf-"));
96067
+ const file = import_node_path6.default.join(folder, name);
96068
+ console.log(`Downloading ${url}`);
96069
+ await download(url, file);
96070
+ if (name.toLowerCase().endsWith(".gltf")) {
96071
+ const document = JSON.parse(import_node_fs6.default.readFileSync(file, "utf-8"));
96072
+ const beside = [...document.buffers ?? [], ...document.images ?? []].map((entry) => entry.uri).filter(isRelative);
96073
+ for (const uri of new Set(beside)) {
96074
+ const target = import_node_path6.default.resolve(folder, decodeURIComponent(uri));
96075
+ if (!target.startsWith(folder + import_node_path6.default.sep)) throw new Error(`${name} refers to ${uri}, outside its own folder`);
96076
+ await download(new URL(uri, url), target);
96077
+ }
96078
+ }
96079
+ return {
96080
+ file,
96081
+ defaultDirectory: process.cwd(),
96082
+ done: () => import_node_fs6.default.rmSync(folder, { recursive: true, force: true })
96083
+ };
96084
+ }
96085
+ function defaultOutput(file, directory, extension) {
96086
+ if (directory === void 0) return void 0;
96087
+ return import_node_path6.default.join(directory, import_node_path6.default.basename(file).split(".")[0] + extension);
96088
+ }
96067
96089
  function readArgs(argv) {
96068
96090
  return {
96069
96091
  positional: argv.filter((arg) => !arg.startsWith("--")),
@@ -96071,7 +96093,7 @@ function readArgs(argv) {
96071
96093
  };
96072
96094
  }
96073
96095
  function report(inputPath, result, stable = false) {
96074
- console.log(`Converted: ${import_node_path7.default.resolve(inputPath)} -> ${result.outputPath}`);
96096
+ console.log(`Converted: ${isUrl(inputPath) ? inputPath : import_node_path6.default.resolve(inputPath)} -> ${result.outputPath}`);
96075
96097
  console.log();
96076
96098
  console.log(`Nodes: ${result.nodeCount}`);
96077
96099
  if (stable) console.log(`Ids: derived from the file name, so this conversion repeats exactly`);
@@ -96091,17 +96113,29 @@ async function runGltf(argv) {
96091
96113
  const { positional, stableIds: stableIds2 } = readArgs(argv);
96092
96114
  const [input_path, output_path] = positional;
96093
96115
  if (input_path === void 0) throw new Error(`Usage: ${GLTF_USAGE}`);
96094
- report(input_path, convertGltfToSourceFile(input_path, output_path, { stableIds: stableIds2 }), stableIds2);
96116
+ const input = await localInput(input_path);
96117
+ try {
96118
+ const output = output_path ?? defaultOutput(input.file, input.defaultDirectory, MOSAIC_SOURCE_EXTENSION);
96119
+ report(input_path, convertGltfToSourceFile(input.file, output, { stableIds: stableIds2 }), stableIds2);
96120
+ } finally {
96121
+ input.done();
96122
+ }
96095
96123
  }
96096
96124
  async function runGltfPack(argv) {
96097
96125
  const { positional, stableIds: stableIds2 } = readArgs(argv);
96098
96126
  const [input_path, output_path] = positional;
96099
96127
  if (input_path === void 0) throw new Error(`Usage: ${GLTF_PACK_USAGE}`);
96100
- report(input_path, await convertGltfToArchiveFile(input_path, output_path, { stableIds: stableIds2 }), stableIds2);
96128
+ const input = await localInput(input_path);
96129
+ try {
96130
+ const output = output_path ?? defaultOutput(input.file, input.defaultDirectory, ".tsr");
96131
+ report(input_path, await convertGltfToArchiveFile(input.file, output, { stableIds: stableIds2 }), stableIds2);
96132
+ } finally {
96133
+ input.done();
96134
+ }
96101
96135
  }
96102
96136
 
96103
96137
  // src/commands/ifc.ts
96104
- var import_node_path10 = __toESM(require("node:path"), 1);
96138
+ var import_node_path9 = __toESM(require("node:path"), 1);
96105
96139
 
96106
96140
  // ../ifc4/src/schemas/entity.schema.json
96107
96141
  var entity_schema_default = {
@@ -97092,12 +97126,12 @@ function ifc4ToMosaic(api, modelID, options = {}) {
97092
97126
  }
97093
97127
 
97094
97128
  // ../ifc4/src/Ifc4ConvertFs.ts
97095
- var import_node_fs6 = __toESM(require("node:fs"), 1);
97096
- var import_node_path9 = __toESM(require("node:path"), 1);
97129
+ var import_node_fs7 = __toESM(require("node:fs"), 1);
97130
+ var import_node_path8 = __toESM(require("node:path"), 1);
97097
97131
 
97098
97132
  // ../ifc4/src/runtime.ts
97099
97133
  var import_node_module = require("node:module");
97100
- var import_node_path8 = __toESM(require("node:path"), 1);
97134
+ var import_node_path7 = __toESM(require("node:path"), 1);
97101
97135
  var loaded;
97102
97136
  async function webIfc() {
97103
97137
  if (loaded) return loaded;
@@ -97111,13 +97145,13 @@ async function webIfc() {
97111
97145
  const roots = [
97112
97146
  process.env.MOSAIC_WEBIFC,
97113
97147
  // Beside the bundle itself, which is where an npm install of the CLI puts it.
97114
- process.argv[1] === void 0 ? void 0 : import_node_path8.default.dirname(process.argv[1]),
97148
+ process.argv[1] === void 0 ? void 0 : import_node_path7.default.dirname(process.argv[1]),
97115
97149
  process.cwd(),
97116
- import_node_path8.default.dirname(process.execPath)
97117
- ].filter((root) => typeof root === "string" && root.length > 0).map((root) => import_node_path8.default.join(root, "index.js"));
97150
+ import_node_path7.default.dirname(process.execPath)
97151
+ ].filter((root) => typeof root === "string" && root.length > 0).map((root) => import_node_path7.default.join(root, "index.js"));
97118
97152
  for (const root of roots) {
97119
97153
  try {
97120
- const require2 = (0, import_node_module.createRequire)(import_node_path8.default.resolve(root));
97154
+ const require2 = (0, import_node_module.createRequire)(import_node_path7.default.resolve(root));
97121
97155
  loaded = require2("web-ifc");
97122
97156
  return loaded;
97123
97157
  } catch (error) {
@@ -97138,15 +97172,15 @@ function message(error) {
97138
97172
  // ../ifc4/src/Ifc4ConvertFs.ts
97139
97173
  var MOSAIC_SOURCE_EXTENSION2 = ".mosaic.json";
97140
97174
  function stemOf(inputPath) {
97141
- const base = import_node_path9.default.basename(inputPath);
97175
+ const base = import_node_path8.default.basename(inputPath);
97142
97176
  const dot = base.lastIndexOf(".");
97143
97177
  return dot > 0 ? base.slice(0, dot) : base;
97144
97178
  }
97145
97179
  function sourceOutputPath2(inputPath) {
97146
- return import_node_path9.default.join(import_node_path9.default.dirname(inputPath), `${stemOf(inputPath)}${MOSAIC_SOURCE_EXTENSION2}`);
97180
+ return import_node_path8.default.join(import_node_path8.default.dirname(inputPath), `${stemOf(inputPath)}${MOSAIC_SOURCE_EXTENSION2}`);
97147
97181
  }
97148
97182
  function archiveOutputPath3(inputPath) {
97149
- return import_node_path9.default.join(import_node_path9.default.dirname(inputPath), `${stemOf(inputPath)}${MOSAIC_ARCHIVE_EXTENSION}`);
97183
+ return import_node_path8.default.join(import_node_path8.default.dirname(inputPath), `${stemOf(inputPath)}${MOSAIC_ARCHIVE_EXTENSION}`);
97150
97184
  }
97151
97185
  async function openIfcModel(bytes, options = {}) {
97152
97186
  const module2 = await webIfc();
@@ -97160,18 +97194,18 @@ async function openIfcModel(bytes, options = {}) {
97160
97194
  };
97161
97195
  }
97162
97196
  async function convertIfcFile(inputPath, options = {}) {
97163
- if (!import_node_fs6.default.existsSync(inputPath)) throw new Error(`File ${inputPath} does not exist`);
97164
- const source = import_node_path9.default.resolve(inputPath);
97165
- const model = await openIfcModel(new Uint8Array(import_node_fs6.default.readFileSync(source)), options);
97197
+ if (!import_node_fs7.default.existsSync(inputPath)) throw new Error(`File ${inputPath} does not exist`);
97198
+ const source = import_node_path8.default.resolve(inputPath);
97199
+ const model = await openIfcModel(new Uint8Array(import_node_fs7.default.readFileSync(source)), options);
97166
97200
  try {
97167
97201
  return ifc4ToMosaic(model.api, model.modelID, {
97168
97202
  // Entities that have no GlobalId are keyed within the file they came from, and
97169
97203
  // the file name is the only name that file has.
97170
- seed: import_node_path9.default.basename(source),
97204
+ seed: import_node_path8.default.basename(source),
97171
97205
  ...options,
97172
97206
  provenance: {
97173
- id: import_node_path9.default.basename(source),
97174
- message: `Imported from ${import_node_path9.default.basename(source)}`,
97207
+ id: import_node_path8.default.basename(source),
97208
+ message: `Imported from ${import_node_path8.default.basename(source)}`,
97175
97209
  ...options.provenance
97176
97210
  }
97177
97211
  });
@@ -97180,13 +97214,13 @@ async function convertIfcFile(inputPath, options = {}) {
97180
97214
  }
97181
97215
  }
97182
97216
  function write2(target, data) {
97183
- import_node_fs6.default.mkdirSync(import_node_path9.default.dirname(target), { recursive: true });
97184
- import_node_fs6.default.writeFileSync(target, data);
97217
+ import_node_fs7.default.mkdirSync(import_node_path8.default.dirname(target), { recursive: true });
97218
+ import_node_fs7.default.writeFileSync(target, data);
97185
97219
  return typeof data === "string" ? Buffer.byteLength(data) : data.byteLength;
97186
97220
  }
97187
97221
  async function convertIfcToSourceFile(inputPath, outputPath, options) {
97188
97222
  const result = await convertIfcFile(inputPath, options);
97189
- const target = import_node_path9.default.resolve(outputPath ?? sourceOutputPath2(import_node_path9.default.resolve(inputPath)));
97223
+ const target = import_node_path8.default.resolve(outputPath ?? sourceOutputPath2(import_node_path8.default.resolve(inputPath)));
97190
97224
  return {
97191
97225
  outputPath: target,
97192
97226
  byteLength: write2(target, `${JSON.stringify(result.document, null, 2)}
@@ -97197,7 +97231,7 @@ async function convertIfcToSourceFile(inputPath, outputPath, options) {
97197
97231
  }
97198
97232
  async function convertIfcToArchiveFile(inputPath, outputPath, options) {
97199
97233
  const result = await convertIfcFile(inputPath, options);
97200
- const target = import_node_path9.default.resolve(outputPath ?? archiveOutputPath3(import_node_path9.default.resolve(inputPath)));
97234
+ const target = import_node_path8.default.resolve(outputPath ?? archiveOutputPath3(import_node_path8.default.resolve(inputPath)));
97201
97235
  return {
97202
97236
  outputPath: target,
97203
97237
  byteLength: write2(target, await packMosaicSource(result.document)),
@@ -97212,7 +97246,7 @@ var NO_GEOMETRY = "--no-geometry";
97212
97246
  var SEED = "--seed=";
97213
97247
  var FLAGS = `[${CENTER}] [${NO_GEOMETRY}] [${SEED}<text>]`;
97214
97248
  var IFC_USAGE = `mosaic ifc <input.ifc> [output${MOSAIC_SOURCE_EXTENSION2}] ${FLAGS}`;
97215
- var IFC_PACK_USAGE = `mosaic ifc-pack <input.ifc> [output.tsr] ${FLAGS}`;
97249
+ var IFC_PACK_USAGE = `mosaic pack <input.ifc> [output.tsr] ${FLAGS}`;
97216
97250
  function readArgs2(argv) {
97217
97251
  const seed = argv.find((arg) => arg.startsWith(SEED))?.slice(SEED.length);
97218
97252
  return {
@@ -97226,7 +97260,7 @@ function readArgs2(argv) {
97226
97260
  }
97227
97261
  function report2(inputPath, result) {
97228
97262
  const { stats } = result;
97229
- console.log(`Converted: ${import_node_path10.default.resolve(inputPath)} -> ${result.outputPath}`);
97263
+ console.log(`Converted: ${import_node_path9.default.resolve(inputPath)} -> ${result.outputPath}`);
97230
97264
  console.log();
97231
97265
  console.log(`Schema: ${stats.schema}`);
97232
97266
  console.log(`Nodes: ${stats.nodes} (${stats.entities} from IFC entities)`);
@@ -97264,13 +97298,13 @@ async function runIfcPack(argv) {
97264
97298
  }
97265
97299
 
97266
97300
  // src/commands/svg.ts
97267
- var import_node_path11 = __toESM(require("node:path"), 1);
97268
- var SVG_PACK_USAGE = `mosaic svg-pack <input.svg> [output.tsr]`;
97301
+ var import_node_path10 = __toESM(require("node:path"), 1);
97302
+ var SVG_PACK_USAGE = `mosaic pack <input.svg> [output.tsr]`;
97269
97303
  async function runSvgPack(argv) {
97270
97304
  const [input_path, output_path] = argv.filter((arg) => !arg.startsWith("--"));
97271
97305
  if (input_path === void 0) throw new Error(`Usage: ${SVG_PACK_USAGE}`);
97272
97306
  const result = await convertSvgToArchiveFile(input_path, output_path);
97273
- console.log(`Packed: ${import_node_path11.default.resolve(input_path)} -> ${result.outputPath}`);
97307
+ console.log(`Packed: ${import_node_path10.default.resolve(input_path)} -> ${result.outputPath}`);
97274
97308
  console.log();
97275
97309
  console.log(`Node: ${result.nodeId}`);
97276
97310
  console.log(`Name: ${result.name}`);
@@ -97282,6 +97316,59 @@ async function runSvgPack(argv) {
97282
97316
  console.log(`Wrote ${result.byteLength} bytes.`);
97283
97317
  }
97284
97318
 
97319
+ // src/commands/pack.ts
97320
+ var PACK_USAGE = `mosaic pack <input.json|.gltf|.glb|.ifc|.svg|url> [output${MOSAIC_ARCHIVE_EXTENSION}] [flags]`;
97321
+ var PACK_DETAILS = [
97322
+ `.json, .mosaic.json a Mosaic source document`,
97323
+ `.gltf, .glb a glTF model, or an http(s) URL to one [--stable-ids]`,
97324
+ `.ifc an IFC4 / IFC2X3 model [--center] [--no-geometry] [--seed=<text>]`,
97325
+ `.svg a drawing, as one node`
97326
+ ];
97327
+ function extensionOf(input) {
97328
+ const name = /^https?:\/\//i.test(input) ? new URL(input).pathname : input;
97329
+ return import_node_path11.default.extname(name).toLowerCase();
97330
+ }
97331
+ async function packSource(argv) {
97332
+ const [input_path, output_path] = argv.filter((arg) => !arg.startsWith("--"));
97333
+ if (input_path === void 0) throw new Error(`Usage: ${PACK_USAGE}`);
97334
+ const result = await packMosaicSourceFile(input_path, output_path);
97335
+ console.log(`Packed: ${import_node_path11.default.resolve(input_path)} -> ${result.outputPath}`);
97336
+ console.log();
97337
+ console.log(`Component tables (${result.componentCount} rows):`);
97338
+ result.tables.forEach((table) => console.log(` - ${table}`));
97339
+ console.log();
97340
+ console.log(`Wrote ${result.byteLength} bytes.`);
97341
+ }
97342
+ async function runPack(argv) {
97343
+ const input = argv.find((arg) => !arg.startsWith("--"));
97344
+ if (input === void 0) throw new Error(`Usage: ${PACK_USAGE}
97345
+
97346
+ ${PACK_DETAILS.join("\n ")}`);
97347
+ const isUrl2 = /^https?:\/\//i.test(input);
97348
+ const extension = extensionOf(input);
97349
+ switch (extension) {
97350
+ case ".gltf":
97351
+ case ".glb":
97352
+ return runGltfPack(argv);
97353
+ case ".json":
97354
+ if (isUrl2) break;
97355
+ return packSource(argv);
97356
+ case ".ifc":
97357
+ if (isUrl2) break;
97358
+ return runIfcPack(argv);
97359
+ case ".svg":
97360
+ if (isUrl2) break;
97361
+ return runSvgPack(argv);
97362
+ default:
97363
+ throw new Error(
97364
+ `Cannot pack ${input}: ${extension ? `"${extension}" is not` : "no extension says what it is, and it is not"} a format mosaic reads.
97365
+
97366
+ ${PACK_DETAILS.join("\n ")}`
97367
+ );
97368
+ }
97369
+ throw new Error(`Cannot pack ${input}: only glTF (.gltf, .glb) can be packed from a URL. Download it first and pack the file.`);
97370
+ }
97371
+
97285
97372
  // src/commands/compose.ts
97286
97373
  var import_node_path15 = __toESM(require("node:path"), 1);
97287
97374
  var DATABASE_EXTENSIONS = [".duckdb", ".db", ".ddb"];
@@ -97409,7 +97496,7 @@ async function runServe(argv) {
97409
97496
  }
97410
97497
 
97411
97498
  // src/mosaic.ts
97412
- var VERSION = true ? "0.1.0" : "dev";
97499
+ var VERSION = true ? "0.2.0" : "dev";
97413
97500
  var COMMANDS = {
97414
97501
  codegen: {
97415
97502
  usage: CODEGEN_USAGE,
@@ -97418,7 +97505,8 @@ var COMMANDS = {
97418
97505
  },
97419
97506
  pack: {
97420
97507
  usage: PACK_USAGE,
97421
- summary: "Pack a Mosaic source document into a .tsr archive",
97508
+ details: PACK_DETAILS,
97509
+ summary: "Pack a source document, glTF, IFC or SVG into a .tsr archive",
97422
97510
  run: runPack
97423
97511
  },
97424
97512
  gltf: {
@@ -97426,26 +97514,11 @@ var COMMANDS = {
97426
97514
  summary: "Convert a glTF or GLB file into a Mosaic source document",
97427
97515
  run: runGltf
97428
97516
  },
97429
- "gltf-pack": {
97430
- usage: GLTF_PACK_USAGE,
97431
- summary: "Convert a glTF or GLB file straight into a .tsr archive",
97432
- run: runGltfPack
97433
- },
97434
97517
  ifc: {
97435
97518
  usage: IFC_USAGE,
97436
97519
  summary: "Convert an IFC model into a Mosaic source document",
97437
97520
  run: runIfc
97438
97521
  },
97439
- "ifc-pack": {
97440
- usage: IFC_PACK_USAGE,
97441
- summary: "Convert an IFC model straight into a .tsr archive",
97442
- run: runIfcPack
97443
- },
97444
- "svg-pack": {
97445
- usage: SVG_PACK_USAGE,
97446
- summary: "Pack an SVG into a .tsr archive of one node",
97447
- run: runSvgPack
97448
- },
97449
97522
  compose: {
97450
97523
  usage: COMPOSE_USAGE,
97451
97524
  summary: "Compose an archive and its imports into a renderable .glb",
@@ -97466,6 +97539,7 @@ function printUsage() {
97466
97539
  for (const [name, command] of Object.entries(COMMANDS)) {
97467
97540
  console.log(` ${name.padEnd(10)} ${command.summary}`);
97468
97541
  console.log(` ${" ".repeat(10)} ${command.usage}`);
97542
+ for (const line of command.details ?? []) console.log(` ${" ".repeat(12)} ${line}`);
97469
97543
  }
97470
97544
  console.log();
97471
97545
  console.log(` help Show this message`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tomvandig/mosaic",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "The Mosaic CLI: pack, convert (glTF, IFC, SVG), compose and serve Mosaic archives",
5
5
  "license": "UNLICENSED",
6
6
  "keywords": [