@geolonia/geonicdb-cli 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -77,6 +77,7 @@ geonic entities list --help
77
77
  | `-f, --format <fmt>` | Output format: `json`, `table`, `geojson` |
78
78
  | `--no-color` | Disable color output |
79
79
  | `-v, --verbose` | Verbose output |
80
+ | `--dry-run` | Print the equivalent `curl` command without executing |
80
81
 
81
82
  Options are resolved in this order (first wins):
82
83
 
@@ -388,6 +389,31 @@ Specify the output format with `--format` or `geonic config set format <fmt>`.
388
389
 
389
390
  Use `--key-values` on `entities list` and `entities get` to request simplified key-value format from the API.
390
391
 
392
+ ## Dry Run
393
+
394
+ Use `--dry-run` on any command to print the equivalent `curl` command instead of executing the request. The output can be copied and run directly in a terminal.
395
+
396
+ ```bash
397
+ $ geonic entities list --type Sensor --dry-run
398
+ curl \
399
+ -H 'Content-Type: application/ld+json' \
400
+ -H 'Accept: application/ld+json' \
401
+ -H 'Authorization: Bearer <token>' \
402
+ 'http://localhost:3000/ngsi-ld/v1/entities?type=Sensor'
403
+ ```
404
+
405
+ Works with all operations including POST with body:
406
+
407
+ ```bash
408
+ $ geonic entities create '{"id":"Room1","type":"Room"}' --dry-run
409
+ curl \
410
+ -X POST \
411
+ -H 'Content-Type: application/ld+json' \
412
+ -H 'Accept: application/ld+json' \
413
+ -d '{"id":"Room1","type":"Room"}' \
414
+ 'http://localhost:3000/ngsi-ld/v1/entities'
415
+ ```
416
+
391
417
  ## Configuration
392
418
 
393
419
  The CLI stores configuration in `~/.config/geonic/config.json`.
package/dist/index.js CHANGED
@@ -584,15 +584,54 @@ function registerConfigCommand(program2) {
584
584
  // src/commands/auth.ts
585
585
  import { Command } from "commander";
586
586
 
587
+ // src/oauth.ts
588
+ async function clientCredentialsGrant(options) {
589
+ const url = new URL("/oauth/token", options.baseUrl).toString();
590
+ const credentials = Buffer.from(`${options.clientId}:${options.clientSecret}`).toString("base64");
591
+ const body = { grant_type: "client_credentials" };
592
+ if (options.scope) {
593
+ body.scope = options.scope;
594
+ }
595
+ const response = await fetch(url, {
596
+ method: "POST",
597
+ headers: {
598
+ "Content-Type": "application/json",
599
+ Authorization: `Basic ${credentials}`
600
+ },
601
+ body: JSON.stringify(body)
602
+ });
603
+ if (!response.ok) {
604
+ const text = await response.text();
605
+ let message;
606
+ try {
607
+ const err = JSON.parse(text);
608
+ message = err.error_description ?? err.error ?? text;
609
+ } catch {
610
+ message = text || `HTTP ${response.status}`;
611
+ }
612
+ throw new Error(`OAuth token request failed: ${message}`);
613
+ }
614
+ return await response.json();
615
+ }
616
+
587
617
  // src/client.ts
618
+ var DryRunSignal = class extends Error {
619
+ constructor() {
620
+ super("dry-run");
621
+ this.name = "DryRunSignal";
622
+ }
623
+ };
588
624
  var GdbClient = class _GdbClient {
589
625
  baseUrl;
590
626
  service;
591
627
  token;
592
628
  refreshToken;
593
629
  apiKey;
630
+ clientId;
631
+ clientSecret;
594
632
  onTokenRefresh;
595
633
  verbose;
634
+ dryRun;
596
635
  refreshPromise;
597
636
  constructor(options) {
598
637
  this.baseUrl = options.baseUrl.replace(/\/+$/, "");
@@ -600,8 +639,11 @@ var GdbClient = class _GdbClient {
600
639
  this.token = options.token;
601
640
  this.refreshToken = options.refreshToken;
602
641
  this.apiKey = options.apiKey;
642
+ this.clientId = options.clientId;
643
+ this.clientSecret = options.clientSecret;
603
644
  this.onTokenRefresh = options.onTokenRefresh;
604
645
  this.verbose = options.verbose ?? false;
646
+ this.dryRun = options.dryRun ?? false;
605
647
  }
606
648
  buildHeaders(extra) {
607
649
  const headers = {};
@@ -682,8 +724,30 @@ var GdbClient = class _GdbClient {
682
724
  });
683
725
  process.stderr.write("\n");
684
726
  }
727
+ static shellQuote(value) {
728
+ return `'${value.split("'").join(`'"'"'`)}'`;
729
+ }
730
+ static buildCurlCommand(method, url, headers, body) {
731
+ const parts = ["curl"];
732
+ if (method !== "GET") {
733
+ parts.push(`-X ${method}`);
734
+ }
735
+ for (const [key, value] of Object.entries(headers)) {
736
+ parts.push(`-H ${_GdbClient.shellQuote(`${key}: ${value}`)}`);
737
+ }
738
+ if (body) {
739
+ parts.push(`-d ${_GdbClient.shellQuote(body)}`);
740
+ }
741
+ parts.push(_GdbClient.shellQuote(url));
742
+ return parts.join(" \\\n ");
743
+ }
744
+ handleDryRun(method, url, headers, body) {
745
+ if (!this.dryRun) return;
746
+ console.log(_GdbClient.buildCurlCommand(method, url, headers, body));
747
+ throw new DryRunSignal();
748
+ }
685
749
  canRefresh() {
686
- return !!this.refreshToken && !this.apiKey;
750
+ return (!!this.refreshToken || !!this.clientId && !!this.clientSecret) && !this.apiKey;
687
751
  }
688
752
  async performTokenRefresh() {
689
753
  if (this.refreshPromise) return this.refreshPromise;
@@ -695,32 +759,50 @@ var GdbClient = class _GdbClient {
695
759
  }
696
760
  }
697
761
  async doRefresh() {
698
- if (!this.refreshToken) return false;
699
- try {
700
- const url = this.buildUrl("/auth/refresh");
701
- const response = await fetch(url, {
702
- method: "POST",
703
- headers: { "Content-Type": "application/json" },
704
- body: JSON.stringify({ refreshToken: this.refreshToken })
705
- });
706
- if (!response.ok) return false;
707
- const data = await response.json();
708
- const newToken = data.accessToken ?? data.token;
709
- const newRefreshToken = data.refreshToken;
710
- if (!newToken) return false;
711
- this.token = newToken;
712
- if (newRefreshToken) this.refreshToken = newRefreshToken;
713
- this.onTokenRefresh?.(newToken, newRefreshToken);
714
- return true;
715
- } catch {
716
- return false;
762
+ if (this.refreshToken) {
763
+ try {
764
+ const url = this.buildUrl("/auth/refresh");
765
+ const response = await fetch(url, {
766
+ method: "POST",
767
+ headers: { "Content-Type": "application/json" },
768
+ body: JSON.stringify({ refreshToken: this.refreshToken })
769
+ });
770
+ if (response.ok) {
771
+ const data = await response.json();
772
+ const newToken = data.accessToken ?? data.token;
773
+ const newRefreshToken = data.refreshToken;
774
+ if (newToken) {
775
+ this.token = newToken;
776
+ if (newRefreshToken) this.refreshToken = newRefreshToken;
777
+ this.onTokenRefresh?.(newToken, newRefreshToken);
778
+ return true;
779
+ }
780
+ }
781
+ } catch {
782
+ }
717
783
  }
784
+ if (this.clientId && this.clientSecret) {
785
+ try {
786
+ const result = await clientCredentialsGrant({
787
+ baseUrl: this.baseUrl,
788
+ clientId: this.clientId,
789
+ clientSecret: this.clientSecret
790
+ });
791
+ this.token = result.access_token;
792
+ this.onTokenRefresh?.(result.access_token);
793
+ return true;
794
+ } catch {
795
+ return false;
796
+ }
797
+ }
798
+ return false;
718
799
  }
719
800
  async executeRequest(method, path, options) {
720
801
  const url = this.buildUrl(`${this.getBasePath()}${path}`, options?.params);
721
802
  const headers = this.buildHeaders(options?.headers);
722
803
  const body = options?.body ? JSON.stringify(options.body) : void 0;
723
804
  this.logRequest(method, url, headers, body);
805
+ this.handleDryRun(method, url, headers, body);
724
806
  const response = await fetch(url, { method, headers, body });
725
807
  this.logResponse(response);
726
808
  const countHeader = response.headers.get("NGSILD-Results-Count");
@@ -745,6 +827,7 @@ var GdbClient = class _GdbClient {
745
827
  const headers = this.buildHeaders(options?.headers);
746
828
  const body = options?.body ? JSON.stringify(options.body) : void 0;
747
829
  this.logRequest(method, url, headers, body);
830
+ this.handleDryRun(method, url, headers, body);
748
831
  const response = await fetch(url, { method, headers, body });
749
832
  this.logResponse(response);
750
833
  let data;
@@ -826,7 +909,8 @@ function resolveOptions(cmd) {
826
909
  color: opts.color,
827
910
  verbose: opts.verbose,
828
911
  profile: opts.profile,
829
- apiKey: opts.apiKey ?? process.env.GDB_API_KEY ?? config.apiKey
912
+ apiKey: opts.apiKey ?? process.env.GDB_API_KEY ?? config.apiKey,
913
+ dryRun: opts.dryRun
830
914
  };
831
915
  }
832
916
  function createClient(cmd) {
@@ -844,6 +928,8 @@ function createClient(cmd) {
844
928
  service: opts.service,
845
929
  token: opts.token,
846
930
  refreshToken: usingCliToken ? void 0 : config.refreshToken,
931
+ clientId: usingCliToken ? void 0 : config.clientId,
932
+ clientSecret: usingCliToken ? void 0 : config.clientSecret,
847
933
  apiKey: opts.apiKey,
848
934
  onTokenRefresh: usingCliToken ? void 0 : (token, refreshToken) => {
849
935
  const cfg = loadConfig(opts.profile);
@@ -851,7 +937,8 @@ function createClient(cmd) {
851
937
  if (refreshToken) cfg.refreshToken = refreshToken;
852
938
  saveConfig(cfg, opts.profile);
853
939
  },
854
- verbose: opts.verbose
940
+ verbose: opts.verbose,
941
+ dryRun: opts.dryRun
855
942
  });
856
943
  }
857
944
  function getFormat(cmd) {
@@ -871,6 +958,9 @@ function withErrorHandler(fn) {
871
958
  try {
872
959
  await fn(...args);
873
960
  } catch (err) {
961
+ if (err instanceof DryRunSignal) {
962
+ return;
963
+ }
874
964
  if (err instanceof GdbClientError && err.status === 401) {
875
965
  printError("Authentication failed. Please run `geonic login` to re-authenticate.");
876
966
  } else if (err instanceof Error) {
@@ -1020,35 +1110,217 @@ function formatDuration(ms) {
1020
1110
  return parts.join(" ");
1021
1111
  }
1022
1112
 
1023
- // src/oauth.ts
1024
- async function clientCredentialsGrant(options) {
1025
- const url = new URL("/oauth/token", options.baseUrl).toString();
1026
- const credentials = Buffer.from(`${options.clientId}:${options.clientSecret}`).toString("base64");
1027
- const params = new URLSearchParams();
1028
- params.set("grant_type", "client_credentials");
1029
- if (options.scope) {
1030
- params.set("scope", options.scope);
1113
+ // src/input.ts
1114
+ import JSON5 from "json5";
1115
+ import { readFileSync as readFileSync2 } from "fs";
1116
+ import { createInterface as createInterface2 } from "readline";
1117
+ async function parseJsonInput(input) {
1118
+ if (input !== void 0) {
1119
+ if (input === "-") return parseData(readFileSync2(0, "utf-8"));
1120
+ if (input.startsWith("@")) return parseData(readFileSync2(input.slice(1), "utf-8"));
1121
+ return parseData(input);
1031
1122
  }
1032
- const response = await fetch(url, {
1033
- method: "POST",
1034
- headers: {
1035
- "Content-Type": "application/x-www-form-urlencoded",
1036
- Authorization: `Basic ${credentials}`
1037
- },
1038
- body: params.toString()
1123
+ if (!process.stdin.isTTY) {
1124
+ return parseData(readFileSync2(0, "utf-8"));
1125
+ }
1126
+ return readInteractiveJson();
1127
+ }
1128
+ function parseData(text) {
1129
+ return JSON5.parse(text.trim());
1130
+ }
1131
+ async function readInteractiveJson() {
1132
+ const rl = createInterface2({
1133
+ input: process.stdin,
1134
+ output: process.stderr,
1135
+ prompt: "json> "
1039
1136
  });
1040
- if (!response.ok) {
1041
- const text = await response.text();
1042
- let message;
1043
- try {
1044
- const err = JSON.parse(text);
1045
- message = err.error_description ?? err.error ?? text;
1046
- } catch {
1047
- message = text || `HTTP ${response.status}`;
1137
+ process.stderr.write("Enter JSON (auto-submits when braces close, Ctrl+C to cancel):\n");
1138
+ rl.prompt();
1139
+ const lines = [];
1140
+ let depth = 0;
1141
+ let started = false;
1142
+ let inBlockComment = false;
1143
+ let inString = false;
1144
+ let stringChar = "";
1145
+ let cancelled = false;
1146
+ return new Promise((resolve, reject) => {
1147
+ rl.on("SIGINT", () => {
1148
+ cancelled = true;
1149
+ rl.close();
1150
+ });
1151
+ rl.on("line", (line) => {
1152
+ lines.push(line);
1153
+ const result = trackDepth(line, depth, started, inBlockComment, inString, stringChar);
1154
+ depth = result.depth;
1155
+ started = result.started;
1156
+ inBlockComment = result.inBlockComment;
1157
+ inString = result.inString;
1158
+ stringChar = result.stringChar;
1159
+ if (started && depth <= 0 && !inBlockComment && !inString) {
1160
+ rl.close();
1161
+ try {
1162
+ resolve(parseData(lines.join("\n")));
1163
+ } catch (err) {
1164
+ reject(err);
1165
+ }
1166
+ } else {
1167
+ rl.setPrompt("... ");
1168
+ rl.prompt();
1169
+ }
1170
+ });
1171
+ rl.on("close", () => {
1172
+ if (cancelled) {
1173
+ reject(new Error("Input cancelled."));
1174
+ return;
1175
+ }
1176
+ if (lines.length > 0 && (!started || depth > 0 || inBlockComment || inString)) {
1177
+ try {
1178
+ resolve(parseData(lines.join("\n")));
1179
+ } catch (err) {
1180
+ reject(err);
1181
+ }
1182
+ } else if (lines.length === 0) {
1183
+ reject(new Error("No input provided."));
1184
+ }
1185
+ });
1186
+ });
1187
+ }
1188
+ function trackDepth(line, depth, started, inBlockComment, inString, stringChar) {
1189
+ for (let i = 0; i < line.length; i++) {
1190
+ const ch = line[i];
1191
+ const next = i + 1 < line.length ? line[i + 1] : "";
1192
+ if (inBlockComment) {
1193
+ if (ch === "*" && next === "/") {
1194
+ inBlockComment = false;
1195
+ i++;
1196
+ }
1197
+ continue;
1198
+ }
1199
+ if (inString) {
1200
+ if (ch === "\\" && i + 1 < line.length) {
1201
+ i++;
1202
+ } else if (ch === stringChar) {
1203
+ inString = false;
1204
+ }
1205
+ continue;
1206
+ }
1207
+ if (ch === "/" && next === "/") break;
1208
+ if (ch === "/" && next === "*") {
1209
+ inBlockComment = true;
1210
+ i++;
1211
+ continue;
1212
+ }
1213
+ if (ch === '"' || ch === "'") {
1214
+ inString = true;
1215
+ stringChar = ch;
1216
+ } else if (ch === "{" || ch === "[") {
1217
+ depth++;
1218
+ started = true;
1219
+ } else if (ch === "}" || ch === "]") {
1220
+ depth--;
1048
1221
  }
1049
- throw new Error(`OAuth token request failed: ${message}`);
1050
1222
  }
1051
- return await response.json();
1223
+ return { depth, started, inBlockComment, inString, stringChar };
1224
+ }
1225
+
1226
+ // src/commands/me-oauth-clients.ts
1227
+ function addMeOAuthClientsSubcommand(me) {
1228
+ const oauthClients = me.command("oauth-clients").description("Manage your OAuth clients");
1229
+ const list = oauthClients.command("list").description("List your OAuth clients").action(
1230
+ withErrorHandler(async (_opts, cmd) => {
1231
+ const client = createClient(cmd);
1232
+ const format = getFormat(cmd);
1233
+ const response = await client.rawRequest("GET", "/me/oauth-clients");
1234
+ outputResponse(response, format);
1235
+ })
1236
+ );
1237
+ addExamples(list, [
1238
+ {
1239
+ description: "List your OAuth clients",
1240
+ command: "geonic me oauth-clients list"
1241
+ }
1242
+ ]);
1243
+ const create = oauthClients.command("create [json]").description("Create a new OAuth client").option("--name <name>", "Client name").option("--scopes <scopes>", "Allowed scopes (comma-separated)").option("--save", "Save credentials to config for automatic re-authentication").action(
1244
+ withErrorHandler(async (json, _opts, cmd) => {
1245
+ const opts = cmd.opts();
1246
+ let body;
1247
+ if (json) {
1248
+ body = await parseJsonInput(json);
1249
+ } else if (opts.name || opts.scopes) {
1250
+ const payload = {};
1251
+ if (opts.name) payload.clientName = opts.name;
1252
+ if (opts.scopes) payload.allowedScopes = opts.scopes.split(",").map((s) => s.trim());
1253
+ body = payload;
1254
+ } else {
1255
+ body = await parseJsonInput();
1256
+ }
1257
+ const client = createClient(cmd);
1258
+ const format = getFormat(cmd);
1259
+ const response = await client.rawRequest("POST", "/me/oauth-clients", { body });
1260
+ const data = response.data;
1261
+ if (opts.save) {
1262
+ const globalOpts = resolveOptions(cmd);
1263
+ const clientId = data.clientId;
1264
+ const clientSecret = data.clientSecret;
1265
+ if (!clientId || !clientSecret) {
1266
+ printError("Response missing clientId or clientSecret. Cannot save credentials.");
1267
+ outputResponse(response, format);
1268
+ printSuccess("OAuth client created.");
1269
+ return;
1270
+ }
1271
+ const baseUrl = validateUrl(globalOpts.url);
1272
+ const tokenResult = await clientCredentialsGrant({
1273
+ baseUrl,
1274
+ clientId,
1275
+ clientSecret,
1276
+ scope: data.allowedScopes?.join(" ")
1277
+ });
1278
+ const config = loadConfig(globalOpts.profile);
1279
+ config.clientId = clientId;
1280
+ config.clientSecret = clientSecret;
1281
+ config.token = tokenResult.access_token;
1282
+ delete config.refreshToken;
1283
+ saveConfig(config, globalOpts.profile);
1284
+ printInfo("Client credentials saved to config. Auto-reauth enabled.");
1285
+ } else {
1286
+ printWarning(
1287
+ "Save the clientSecret now \u2014 it will not be shown again."
1288
+ );
1289
+ }
1290
+ outputResponse(response, format);
1291
+ printSuccess("OAuth client created.");
1292
+ })
1293
+ );
1294
+ addExamples(create, [
1295
+ {
1296
+ description: "Create an OAuth client with flags",
1297
+ command: "geonic me oauth-clients create --name my-ci-bot --scopes read:entities,write:entities"
1298
+ },
1299
+ {
1300
+ description: "Create and save credentials for auto-reauth",
1301
+ command: "geonic me oauth-clients create --name my-ci-bot --save"
1302
+ },
1303
+ {
1304
+ description: "Create an OAuth client from JSON",
1305
+ command: `geonic me oauth-clients create '{"clientName":"my-bot","allowedScopes":["read:entities"]}'`
1306
+ }
1307
+ ]);
1308
+ const del = oauthClients.command("delete <id>").description("Delete an OAuth client").action(
1309
+ withErrorHandler(async (id, _opts, cmd) => {
1310
+ const client = createClient(cmd);
1311
+ await client.rawRequest(
1312
+ "DELETE",
1313
+ `/me/oauth-clients/${encodeURIComponent(String(id))}`
1314
+ );
1315
+ printSuccess("OAuth client deleted.");
1316
+ })
1317
+ );
1318
+ addExamples(del, [
1319
+ {
1320
+ description: "Delete an OAuth client",
1321
+ command: "geonic me oauth-clients delete <client-id>"
1322
+ }
1323
+ ]);
1052
1324
  }
1053
1325
 
1054
1326
  // src/commands/auth.ts
@@ -1208,13 +1480,25 @@ function registerAuthCommands(program2) {
1208
1480
  }
1209
1481
  ]);
1210
1482
  auth.addCommand(logout);
1211
- const me = program2.command("me").description("Display current authenticated user").action(createMeAction());
1483
+ const me = program2.command("me").description("Display current authenticated user and manage user resources");
1484
+ const meInfo = me.command("info", { isDefault: true, hidden: true }).description("Display current authenticated user").action(createMeAction());
1212
1485
  addExamples(me, [
1486
+ {
1487
+ description: "Show current user info",
1488
+ command: "geonic me"
1489
+ },
1490
+ {
1491
+ description: "List your OAuth clients",
1492
+ command: "geonic me oauth-clients list"
1493
+ }
1494
+ ]);
1495
+ addExamples(meInfo, [
1213
1496
  {
1214
1497
  description: "Show current user info",
1215
1498
  command: "geonic me"
1216
1499
  }
1217
1500
  ]);
1501
+ addMeOAuthClientsSubcommand(me);
1218
1502
  program2.addCommand(createLoginCommand(), { hidden: true });
1219
1503
  program2.addCommand(createLogoutCommand(), { hidden: true });
1220
1504
  const hiddenWhoami = new Command("whoami").description("Display current authenticated user").action(createMeAction());
@@ -1310,119 +1594,6 @@ function registerProfileCommands(program2) {
1310
1594
  ]);
1311
1595
  }
1312
1596
 
1313
- // src/input.ts
1314
- import JSON5 from "json5";
1315
- import { readFileSync as readFileSync2 } from "fs";
1316
- import { createInterface as createInterface2 } from "readline";
1317
- async function parseJsonInput(input) {
1318
- if (input !== void 0) {
1319
- if (input === "-") return parseData(readFileSync2(0, "utf-8"));
1320
- if (input.startsWith("@")) return parseData(readFileSync2(input.slice(1), "utf-8"));
1321
- return parseData(input);
1322
- }
1323
- if (!process.stdin.isTTY) {
1324
- return parseData(readFileSync2(0, "utf-8"));
1325
- }
1326
- return readInteractiveJson();
1327
- }
1328
- function parseData(text) {
1329
- return JSON5.parse(text.trim());
1330
- }
1331
- async function readInteractiveJson() {
1332
- const rl = createInterface2({
1333
- input: process.stdin,
1334
- output: process.stderr,
1335
- prompt: "json> "
1336
- });
1337
- process.stderr.write("Enter JSON (auto-submits when braces close, Ctrl+C to cancel):\n");
1338
- rl.prompt();
1339
- const lines = [];
1340
- let depth = 0;
1341
- let started = false;
1342
- let inBlockComment = false;
1343
- let inString = false;
1344
- let stringChar = "";
1345
- let cancelled = false;
1346
- return new Promise((resolve, reject) => {
1347
- rl.on("SIGINT", () => {
1348
- cancelled = true;
1349
- rl.close();
1350
- });
1351
- rl.on("line", (line) => {
1352
- lines.push(line);
1353
- const result = trackDepth(line, depth, started, inBlockComment, inString, stringChar);
1354
- depth = result.depth;
1355
- started = result.started;
1356
- inBlockComment = result.inBlockComment;
1357
- inString = result.inString;
1358
- stringChar = result.stringChar;
1359
- if (started && depth <= 0 && !inBlockComment && !inString) {
1360
- rl.close();
1361
- try {
1362
- resolve(parseData(lines.join("\n")));
1363
- } catch (err) {
1364
- reject(err);
1365
- }
1366
- } else {
1367
- rl.setPrompt("... ");
1368
- rl.prompt();
1369
- }
1370
- });
1371
- rl.on("close", () => {
1372
- if (cancelled) {
1373
- reject(new Error("Input cancelled."));
1374
- return;
1375
- }
1376
- if (lines.length > 0 && (!started || depth > 0 || inBlockComment || inString)) {
1377
- try {
1378
- resolve(parseData(lines.join("\n")));
1379
- } catch (err) {
1380
- reject(err);
1381
- }
1382
- } else if (lines.length === 0) {
1383
- reject(new Error("No input provided."));
1384
- }
1385
- });
1386
- });
1387
- }
1388
- function trackDepth(line, depth, started, inBlockComment, inString, stringChar) {
1389
- for (let i = 0; i < line.length; i++) {
1390
- const ch = line[i];
1391
- const next = i + 1 < line.length ? line[i + 1] : "";
1392
- if (inBlockComment) {
1393
- if (ch === "*" && next === "/") {
1394
- inBlockComment = false;
1395
- i++;
1396
- }
1397
- continue;
1398
- }
1399
- if (inString) {
1400
- if (ch === "\\" && i + 1 < line.length) {
1401
- i++;
1402
- } else if (ch === stringChar) {
1403
- inString = false;
1404
- }
1405
- continue;
1406
- }
1407
- if (ch === "/" && next === "/") break;
1408
- if (ch === "/" && next === "*") {
1409
- inBlockComment = true;
1410
- i++;
1411
- continue;
1412
- }
1413
- if (ch === '"' || ch === "'") {
1414
- inString = true;
1415
- stringChar = ch;
1416
- } else if (ch === "{" || ch === "[") {
1417
- depth++;
1418
- started = true;
1419
- } else if (ch === "}" || ch === "]") {
1420
- depth--;
1421
- }
1422
- }
1423
- return { depth, started, inBlockComment, inString, stringChar };
1424
- }
1425
-
1426
1597
  // src/commands/attrs.ts
1427
1598
  function addAttrsSubcommands(attrs) {
1428
1599
  const list = attrs.command("list").description("List all attributes of an entity").argument("<entityId>", "Entity ID").action(
@@ -3379,7 +3550,7 @@ function createProgram() {
3379
3550
  const require2 = createRequire3(import.meta.url);
3380
3551
  const pkg = require2("../package.json");
3381
3552
  const program2 = new Command2();
3382
- program2.name("geonic").description(pkg.description).option("-u, --url <url>", "Base URL of the GeonicDB server").option("-s, --service <name>", "NGSILD-Tenant header").option("--token <token>", "Authentication token").option("-p, --profile <name>", "Use a named profile").option("--api-key <key>", "API key for authentication").option("-f, --format <fmt>", "Output format: json, table, geojson").option("--no-color", "Disable color output").option("-v, --verbose", "Verbose output");
3553
+ program2.name("geonic").description(pkg.description).option("-u, --url <url>", "Base URL of the GeonicDB server").option("-s, --service <name>", "NGSILD-Tenant header").option("--token <token>", "Authentication token").option("-p, --profile <name>", "Use a named profile").option("--api-key <key>", "API key for authentication").option("-f, --format <fmt>", "Output format: json, table, geojson").option("--no-color", "Disable color output").option("-v, --verbose", "Verbose output").option("--dry-run", "Print the equivalent curl command without executing");
3383
3554
  registerHelpCommand(program2);
3384
3555
  registerConfigCommand(program2);
3385
3556
  registerAuthCommands(program2);