@geolonia/geonicdb-cli 0.3.0 → 0.4.1
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/dist/index.js +330 -167
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -240,14 +240,34 @@ function collectKeys(items) {
|
|
|
240
240
|
sorted.push(...Array.from(keySet).sort());
|
|
241
241
|
return sorted;
|
|
242
242
|
}
|
|
243
|
+
function formatGeoJSON(geo) {
|
|
244
|
+
const geoType = String(geo.type);
|
|
245
|
+
const coords = geo.coordinates;
|
|
246
|
+
if (geoType === "Point" && Array.isArray(coords) && coords.length >= 2) {
|
|
247
|
+
return `Point(${Number(coords[0]).toFixed(2)}, ${Number(coords[1]).toFixed(2)})`;
|
|
248
|
+
}
|
|
249
|
+
if (Array.isArray(coords)) {
|
|
250
|
+
const count = geoType === "Polygon" ? Array.isArray(coords[0]) ? coords[0].length : 0 : coords.length;
|
|
251
|
+
return `${geoType}(${count} coords)`;
|
|
252
|
+
}
|
|
253
|
+
return `${geoType}(...)`;
|
|
254
|
+
}
|
|
255
|
+
function isGeoJSON(v) {
|
|
256
|
+
if (typeof v !== "object" || v === null) return false;
|
|
257
|
+
const o = v;
|
|
258
|
+
return typeof o.type === "string" && "coordinates" in o;
|
|
259
|
+
}
|
|
243
260
|
function cellValue(val) {
|
|
244
261
|
if (val === void 0 || val === null) return "";
|
|
245
|
-
if (typeof val
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
262
|
+
if (typeof val !== "object") return String(val);
|
|
263
|
+
const obj = val;
|
|
264
|
+
if (isGeoJSON(obj)) return formatGeoJSON(obj);
|
|
265
|
+
if ("value" in obj) {
|
|
266
|
+
const v = obj.value;
|
|
267
|
+
if (isGeoJSON(v)) return formatGeoJSON(v);
|
|
268
|
+
return String(v);
|
|
249
269
|
}
|
|
250
|
-
return
|
|
270
|
+
return JSON.stringify(val);
|
|
251
271
|
}
|
|
252
272
|
function toGeoJSON(data) {
|
|
253
273
|
if (Array.isArray(data)) {
|
|
@@ -584,6 +604,36 @@ function registerConfigCommand(program2) {
|
|
|
584
604
|
// src/commands/auth.ts
|
|
585
605
|
import { Command } from "commander";
|
|
586
606
|
|
|
607
|
+
// src/oauth.ts
|
|
608
|
+
async function clientCredentialsGrant(options) {
|
|
609
|
+
const url = new URL("/oauth/token", options.baseUrl).toString();
|
|
610
|
+
const credentials = Buffer.from(`${options.clientId}:${options.clientSecret}`).toString("base64");
|
|
611
|
+
const body = { grant_type: "client_credentials" };
|
|
612
|
+
if (options.scope) {
|
|
613
|
+
body.scope = options.scope;
|
|
614
|
+
}
|
|
615
|
+
const response = await fetch(url, {
|
|
616
|
+
method: "POST",
|
|
617
|
+
headers: {
|
|
618
|
+
"Content-Type": "application/json",
|
|
619
|
+
Authorization: `Basic ${credentials}`
|
|
620
|
+
},
|
|
621
|
+
body: JSON.stringify(body)
|
|
622
|
+
});
|
|
623
|
+
if (!response.ok) {
|
|
624
|
+
const text = await response.text();
|
|
625
|
+
let message;
|
|
626
|
+
try {
|
|
627
|
+
const err = JSON.parse(text);
|
|
628
|
+
message = err.error_description ?? err.error ?? text;
|
|
629
|
+
} catch {
|
|
630
|
+
message = text || `HTTP ${response.status}`;
|
|
631
|
+
}
|
|
632
|
+
throw new Error(`OAuth token request failed: ${message}`);
|
|
633
|
+
}
|
|
634
|
+
return await response.json();
|
|
635
|
+
}
|
|
636
|
+
|
|
587
637
|
// src/client.ts
|
|
588
638
|
var DryRunSignal = class extends Error {
|
|
589
639
|
constructor() {
|
|
@@ -597,6 +647,8 @@ var GdbClient = class _GdbClient {
|
|
|
597
647
|
token;
|
|
598
648
|
refreshToken;
|
|
599
649
|
apiKey;
|
|
650
|
+
clientId;
|
|
651
|
+
clientSecret;
|
|
600
652
|
onTokenRefresh;
|
|
601
653
|
verbose;
|
|
602
654
|
dryRun;
|
|
@@ -607,6 +659,8 @@ var GdbClient = class _GdbClient {
|
|
|
607
659
|
this.token = options.token;
|
|
608
660
|
this.refreshToken = options.refreshToken;
|
|
609
661
|
this.apiKey = options.apiKey;
|
|
662
|
+
this.clientId = options.clientId;
|
|
663
|
+
this.clientSecret = options.clientSecret;
|
|
610
664
|
this.onTokenRefresh = options.onTokenRefresh;
|
|
611
665
|
this.verbose = options.verbose ?? false;
|
|
612
666
|
this.dryRun = options.dryRun ?? false;
|
|
@@ -713,7 +767,7 @@ var GdbClient = class _GdbClient {
|
|
|
713
767
|
throw new DryRunSignal();
|
|
714
768
|
}
|
|
715
769
|
canRefresh() {
|
|
716
|
-
return !!this.refreshToken && !this.apiKey;
|
|
770
|
+
return (!!this.refreshToken || !!this.clientId && !!this.clientSecret) && !this.apiKey;
|
|
717
771
|
}
|
|
718
772
|
async performTokenRefresh() {
|
|
719
773
|
if (this.refreshPromise) return this.refreshPromise;
|
|
@@ -725,26 +779,43 @@ var GdbClient = class _GdbClient {
|
|
|
725
779
|
}
|
|
726
780
|
}
|
|
727
781
|
async doRefresh() {
|
|
728
|
-
if (
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
782
|
+
if (this.refreshToken) {
|
|
783
|
+
try {
|
|
784
|
+
const url = this.buildUrl("/auth/refresh");
|
|
785
|
+
const response = await fetch(url, {
|
|
786
|
+
method: "POST",
|
|
787
|
+
headers: { "Content-Type": "application/json" },
|
|
788
|
+
body: JSON.stringify({ refreshToken: this.refreshToken })
|
|
789
|
+
});
|
|
790
|
+
if (response.ok) {
|
|
791
|
+
const data = await response.json();
|
|
792
|
+
const newToken = data.accessToken ?? data.token;
|
|
793
|
+
const newRefreshToken = data.refreshToken;
|
|
794
|
+
if (newToken) {
|
|
795
|
+
this.token = newToken;
|
|
796
|
+
if (newRefreshToken) this.refreshToken = newRefreshToken;
|
|
797
|
+
this.onTokenRefresh?.(newToken, newRefreshToken);
|
|
798
|
+
return true;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
} catch {
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
if (this.clientId && this.clientSecret) {
|
|
805
|
+
try {
|
|
806
|
+
const result = await clientCredentialsGrant({
|
|
807
|
+
baseUrl: this.baseUrl,
|
|
808
|
+
clientId: this.clientId,
|
|
809
|
+
clientSecret: this.clientSecret
|
|
810
|
+
});
|
|
811
|
+
this.token = result.access_token;
|
|
812
|
+
this.onTokenRefresh?.(result.access_token);
|
|
813
|
+
return true;
|
|
814
|
+
} catch {
|
|
815
|
+
return false;
|
|
816
|
+
}
|
|
747
817
|
}
|
|
818
|
+
return false;
|
|
748
819
|
}
|
|
749
820
|
async executeRequest(method, path, options) {
|
|
750
821
|
const url = this.buildUrl(`${this.getBasePath()}${path}`, options?.params);
|
|
@@ -877,6 +948,8 @@ function createClient(cmd) {
|
|
|
877
948
|
service: opts.service,
|
|
878
949
|
token: opts.token,
|
|
879
950
|
refreshToken: usingCliToken ? void 0 : config.refreshToken,
|
|
951
|
+
clientId: usingCliToken ? void 0 : config.clientId,
|
|
952
|
+
clientSecret: usingCliToken ? void 0 : config.clientSecret,
|
|
880
953
|
apiKey: opts.apiKey,
|
|
881
954
|
onTokenRefresh: usingCliToken ? void 0 : (token, refreshToken) => {
|
|
882
955
|
const cfg = loadConfig(opts.profile);
|
|
@@ -1057,35 +1130,217 @@ function formatDuration(ms) {
|
|
|
1057
1130
|
return parts.join(" ");
|
|
1058
1131
|
}
|
|
1059
1132
|
|
|
1060
|
-
// src/
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1133
|
+
// src/input.ts
|
|
1134
|
+
import JSON5 from "json5";
|
|
1135
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
1136
|
+
import { createInterface as createInterface2 } from "readline";
|
|
1137
|
+
async function parseJsonInput(input) {
|
|
1138
|
+
if (input !== void 0) {
|
|
1139
|
+
if (input === "-") return parseData(readFileSync2(0, "utf-8"));
|
|
1140
|
+
if (input.startsWith("@")) return parseData(readFileSync2(input.slice(1), "utf-8"));
|
|
1141
|
+
return parseData(input);
|
|
1068
1142
|
}
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1143
|
+
if (!process.stdin.isTTY) {
|
|
1144
|
+
return parseData(readFileSync2(0, "utf-8"));
|
|
1145
|
+
}
|
|
1146
|
+
return readInteractiveJson();
|
|
1147
|
+
}
|
|
1148
|
+
function parseData(text) {
|
|
1149
|
+
return JSON5.parse(text.trim());
|
|
1150
|
+
}
|
|
1151
|
+
async function readInteractiveJson() {
|
|
1152
|
+
const rl = createInterface2({
|
|
1153
|
+
input: process.stdin,
|
|
1154
|
+
output: process.stderr,
|
|
1155
|
+
prompt: "json> "
|
|
1076
1156
|
});
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1157
|
+
process.stderr.write("Enter JSON (auto-submits when braces close, Ctrl+C to cancel):\n");
|
|
1158
|
+
rl.prompt();
|
|
1159
|
+
const lines = [];
|
|
1160
|
+
let depth = 0;
|
|
1161
|
+
let started = false;
|
|
1162
|
+
let inBlockComment = false;
|
|
1163
|
+
let inString = false;
|
|
1164
|
+
let stringChar = "";
|
|
1165
|
+
let cancelled = false;
|
|
1166
|
+
return new Promise((resolve, reject) => {
|
|
1167
|
+
rl.on("SIGINT", () => {
|
|
1168
|
+
cancelled = true;
|
|
1169
|
+
rl.close();
|
|
1170
|
+
});
|
|
1171
|
+
rl.on("line", (line) => {
|
|
1172
|
+
lines.push(line);
|
|
1173
|
+
const result = trackDepth(line, depth, started, inBlockComment, inString, stringChar);
|
|
1174
|
+
depth = result.depth;
|
|
1175
|
+
started = result.started;
|
|
1176
|
+
inBlockComment = result.inBlockComment;
|
|
1177
|
+
inString = result.inString;
|
|
1178
|
+
stringChar = result.stringChar;
|
|
1179
|
+
if (started && depth <= 0 && !inBlockComment && !inString) {
|
|
1180
|
+
rl.close();
|
|
1181
|
+
try {
|
|
1182
|
+
resolve(parseData(lines.join("\n")));
|
|
1183
|
+
} catch (err) {
|
|
1184
|
+
reject(err);
|
|
1185
|
+
}
|
|
1186
|
+
} else {
|
|
1187
|
+
rl.setPrompt("... ");
|
|
1188
|
+
rl.prompt();
|
|
1189
|
+
}
|
|
1190
|
+
});
|
|
1191
|
+
rl.on("close", () => {
|
|
1192
|
+
if (cancelled) {
|
|
1193
|
+
reject(new Error("Input cancelled."));
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
if (lines.length > 0 && (!started || depth > 0 || inBlockComment || inString)) {
|
|
1197
|
+
try {
|
|
1198
|
+
resolve(parseData(lines.join("\n")));
|
|
1199
|
+
} catch (err) {
|
|
1200
|
+
reject(err);
|
|
1201
|
+
}
|
|
1202
|
+
} else if (lines.length === 0) {
|
|
1203
|
+
reject(new Error("No input provided."));
|
|
1204
|
+
}
|
|
1205
|
+
});
|
|
1206
|
+
});
|
|
1207
|
+
}
|
|
1208
|
+
function trackDepth(line, depth, started, inBlockComment, inString, stringChar) {
|
|
1209
|
+
for (let i = 0; i < line.length; i++) {
|
|
1210
|
+
const ch = line[i];
|
|
1211
|
+
const next = i + 1 < line.length ? line[i + 1] : "";
|
|
1212
|
+
if (inBlockComment) {
|
|
1213
|
+
if (ch === "*" && next === "/") {
|
|
1214
|
+
inBlockComment = false;
|
|
1215
|
+
i++;
|
|
1216
|
+
}
|
|
1217
|
+
continue;
|
|
1218
|
+
}
|
|
1219
|
+
if (inString) {
|
|
1220
|
+
if (ch === "\\" && i + 1 < line.length) {
|
|
1221
|
+
i++;
|
|
1222
|
+
} else if (ch === stringChar) {
|
|
1223
|
+
inString = false;
|
|
1224
|
+
}
|
|
1225
|
+
continue;
|
|
1226
|
+
}
|
|
1227
|
+
if (ch === "/" && next === "/") break;
|
|
1228
|
+
if (ch === "/" && next === "*") {
|
|
1229
|
+
inBlockComment = true;
|
|
1230
|
+
i++;
|
|
1231
|
+
continue;
|
|
1232
|
+
}
|
|
1233
|
+
if (ch === '"' || ch === "'") {
|
|
1234
|
+
inString = true;
|
|
1235
|
+
stringChar = ch;
|
|
1236
|
+
} else if (ch === "{" || ch === "[") {
|
|
1237
|
+
depth++;
|
|
1238
|
+
started = true;
|
|
1239
|
+
} else if (ch === "}" || ch === "]") {
|
|
1240
|
+
depth--;
|
|
1085
1241
|
}
|
|
1086
|
-
throw new Error(`OAuth token request failed: ${message}`);
|
|
1087
1242
|
}
|
|
1088
|
-
return
|
|
1243
|
+
return { depth, started, inBlockComment, inString, stringChar };
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
// src/commands/me-oauth-clients.ts
|
|
1247
|
+
function addMeOAuthClientsSubcommand(me) {
|
|
1248
|
+
const oauthClients = me.command("oauth-clients").description("Manage your OAuth clients");
|
|
1249
|
+
const list = oauthClients.command("list").description("List your OAuth clients").action(
|
|
1250
|
+
withErrorHandler(async (_opts, cmd) => {
|
|
1251
|
+
const client = createClient(cmd);
|
|
1252
|
+
const format = getFormat(cmd);
|
|
1253
|
+
const response = await client.rawRequest("GET", "/me/oauth-clients");
|
|
1254
|
+
outputResponse(response, format);
|
|
1255
|
+
})
|
|
1256
|
+
);
|
|
1257
|
+
addExamples(list, [
|
|
1258
|
+
{
|
|
1259
|
+
description: "List your OAuth clients",
|
|
1260
|
+
command: "geonic me oauth-clients list"
|
|
1261
|
+
}
|
|
1262
|
+
]);
|
|
1263
|
+
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(
|
|
1264
|
+
withErrorHandler(async (json, _opts, cmd) => {
|
|
1265
|
+
const opts = cmd.opts();
|
|
1266
|
+
let body;
|
|
1267
|
+
if (json) {
|
|
1268
|
+
body = await parseJsonInput(json);
|
|
1269
|
+
} else if (opts.name || opts.scopes) {
|
|
1270
|
+
const payload = {};
|
|
1271
|
+
if (opts.name) payload.clientName = opts.name;
|
|
1272
|
+
if (opts.scopes) payload.allowedScopes = opts.scopes.split(",").map((s) => s.trim());
|
|
1273
|
+
body = payload;
|
|
1274
|
+
} else {
|
|
1275
|
+
body = await parseJsonInput();
|
|
1276
|
+
}
|
|
1277
|
+
const client = createClient(cmd);
|
|
1278
|
+
const format = getFormat(cmd);
|
|
1279
|
+
const response = await client.rawRequest("POST", "/me/oauth-clients", { body });
|
|
1280
|
+
const data = response.data;
|
|
1281
|
+
if (opts.save) {
|
|
1282
|
+
const globalOpts = resolveOptions(cmd);
|
|
1283
|
+
const clientId = data.clientId;
|
|
1284
|
+
const clientSecret = data.clientSecret;
|
|
1285
|
+
if (!clientId || !clientSecret) {
|
|
1286
|
+
printError("Response missing clientId or clientSecret. Cannot save credentials.");
|
|
1287
|
+
outputResponse(response, format);
|
|
1288
|
+
printSuccess("OAuth client created.");
|
|
1289
|
+
return;
|
|
1290
|
+
}
|
|
1291
|
+
const baseUrl = validateUrl(globalOpts.url);
|
|
1292
|
+
const tokenResult = await clientCredentialsGrant({
|
|
1293
|
+
baseUrl,
|
|
1294
|
+
clientId,
|
|
1295
|
+
clientSecret,
|
|
1296
|
+
scope: data.allowedScopes?.join(" ")
|
|
1297
|
+
});
|
|
1298
|
+
const config = loadConfig(globalOpts.profile);
|
|
1299
|
+
config.clientId = clientId;
|
|
1300
|
+
config.clientSecret = clientSecret;
|
|
1301
|
+
config.token = tokenResult.access_token;
|
|
1302
|
+
delete config.refreshToken;
|
|
1303
|
+
saveConfig(config, globalOpts.profile);
|
|
1304
|
+
printInfo("Client credentials saved to config. Auto-reauth enabled.");
|
|
1305
|
+
} else {
|
|
1306
|
+
printWarning(
|
|
1307
|
+
"Save the clientSecret now \u2014 it will not be shown again."
|
|
1308
|
+
);
|
|
1309
|
+
}
|
|
1310
|
+
outputResponse(response, format);
|
|
1311
|
+
printSuccess("OAuth client created.");
|
|
1312
|
+
})
|
|
1313
|
+
);
|
|
1314
|
+
addExamples(create, [
|
|
1315
|
+
{
|
|
1316
|
+
description: "Create an OAuth client with flags",
|
|
1317
|
+
command: "geonic me oauth-clients create --name my-ci-bot --scopes read:entities,write:entities"
|
|
1318
|
+
},
|
|
1319
|
+
{
|
|
1320
|
+
description: "Create and save credentials for auto-reauth",
|
|
1321
|
+
command: "geonic me oauth-clients create --name my-ci-bot --save"
|
|
1322
|
+
},
|
|
1323
|
+
{
|
|
1324
|
+
description: "Create an OAuth client from JSON",
|
|
1325
|
+
command: `geonic me oauth-clients create '{"clientName":"my-bot","allowedScopes":["read:entities"]}'`
|
|
1326
|
+
}
|
|
1327
|
+
]);
|
|
1328
|
+
const del = oauthClients.command("delete <id>").description("Delete an OAuth client").action(
|
|
1329
|
+
withErrorHandler(async (id, _opts, cmd) => {
|
|
1330
|
+
const client = createClient(cmd);
|
|
1331
|
+
await client.rawRequest(
|
|
1332
|
+
"DELETE",
|
|
1333
|
+
`/me/oauth-clients/${encodeURIComponent(String(id))}`
|
|
1334
|
+
);
|
|
1335
|
+
printSuccess("OAuth client deleted.");
|
|
1336
|
+
})
|
|
1337
|
+
);
|
|
1338
|
+
addExamples(del, [
|
|
1339
|
+
{
|
|
1340
|
+
description: "Delete an OAuth client",
|
|
1341
|
+
command: "geonic me oauth-clients delete <client-id>"
|
|
1342
|
+
}
|
|
1343
|
+
]);
|
|
1089
1344
|
}
|
|
1090
1345
|
|
|
1091
1346
|
// src/commands/auth.ts
|
|
@@ -1245,13 +1500,25 @@ function registerAuthCommands(program2) {
|
|
|
1245
1500
|
}
|
|
1246
1501
|
]);
|
|
1247
1502
|
auth.addCommand(logout);
|
|
1248
|
-
const me = program2.command("me").description("Display current authenticated user")
|
|
1503
|
+
const me = program2.command("me").description("Display current authenticated user and manage user resources");
|
|
1504
|
+
const meInfo = me.command("info", { isDefault: true, hidden: true }).description("Display current authenticated user").action(createMeAction());
|
|
1249
1505
|
addExamples(me, [
|
|
1506
|
+
{
|
|
1507
|
+
description: "Show current user info",
|
|
1508
|
+
command: "geonic me"
|
|
1509
|
+
},
|
|
1510
|
+
{
|
|
1511
|
+
description: "List your OAuth clients",
|
|
1512
|
+
command: "geonic me oauth-clients list"
|
|
1513
|
+
}
|
|
1514
|
+
]);
|
|
1515
|
+
addExamples(meInfo, [
|
|
1250
1516
|
{
|
|
1251
1517
|
description: "Show current user info",
|
|
1252
1518
|
command: "geonic me"
|
|
1253
1519
|
}
|
|
1254
1520
|
]);
|
|
1521
|
+
addMeOAuthClientsSubcommand(me);
|
|
1255
1522
|
program2.addCommand(createLoginCommand(), { hidden: true });
|
|
1256
1523
|
program2.addCommand(createLogoutCommand(), { hidden: true });
|
|
1257
1524
|
const hiddenWhoami = new Command("whoami").description("Display current authenticated user").action(createMeAction());
|
|
@@ -1347,119 +1614,6 @@ function registerProfileCommands(program2) {
|
|
|
1347
1614
|
]);
|
|
1348
1615
|
}
|
|
1349
1616
|
|
|
1350
|
-
// src/input.ts
|
|
1351
|
-
import JSON5 from "json5";
|
|
1352
|
-
import { readFileSync as readFileSync2 } from "fs";
|
|
1353
|
-
import { createInterface as createInterface2 } from "readline";
|
|
1354
|
-
async function parseJsonInput(input) {
|
|
1355
|
-
if (input !== void 0) {
|
|
1356
|
-
if (input === "-") return parseData(readFileSync2(0, "utf-8"));
|
|
1357
|
-
if (input.startsWith("@")) return parseData(readFileSync2(input.slice(1), "utf-8"));
|
|
1358
|
-
return parseData(input);
|
|
1359
|
-
}
|
|
1360
|
-
if (!process.stdin.isTTY) {
|
|
1361
|
-
return parseData(readFileSync2(0, "utf-8"));
|
|
1362
|
-
}
|
|
1363
|
-
return readInteractiveJson();
|
|
1364
|
-
}
|
|
1365
|
-
function parseData(text) {
|
|
1366
|
-
return JSON5.parse(text.trim());
|
|
1367
|
-
}
|
|
1368
|
-
async function readInteractiveJson() {
|
|
1369
|
-
const rl = createInterface2({
|
|
1370
|
-
input: process.stdin,
|
|
1371
|
-
output: process.stderr,
|
|
1372
|
-
prompt: "json> "
|
|
1373
|
-
});
|
|
1374
|
-
process.stderr.write("Enter JSON (auto-submits when braces close, Ctrl+C to cancel):\n");
|
|
1375
|
-
rl.prompt();
|
|
1376
|
-
const lines = [];
|
|
1377
|
-
let depth = 0;
|
|
1378
|
-
let started = false;
|
|
1379
|
-
let inBlockComment = false;
|
|
1380
|
-
let inString = false;
|
|
1381
|
-
let stringChar = "";
|
|
1382
|
-
let cancelled = false;
|
|
1383
|
-
return new Promise((resolve, reject) => {
|
|
1384
|
-
rl.on("SIGINT", () => {
|
|
1385
|
-
cancelled = true;
|
|
1386
|
-
rl.close();
|
|
1387
|
-
});
|
|
1388
|
-
rl.on("line", (line) => {
|
|
1389
|
-
lines.push(line);
|
|
1390
|
-
const result = trackDepth(line, depth, started, inBlockComment, inString, stringChar);
|
|
1391
|
-
depth = result.depth;
|
|
1392
|
-
started = result.started;
|
|
1393
|
-
inBlockComment = result.inBlockComment;
|
|
1394
|
-
inString = result.inString;
|
|
1395
|
-
stringChar = result.stringChar;
|
|
1396
|
-
if (started && depth <= 0 && !inBlockComment && !inString) {
|
|
1397
|
-
rl.close();
|
|
1398
|
-
try {
|
|
1399
|
-
resolve(parseData(lines.join("\n")));
|
|
1400
|
-
} catch (err) {
|
|
1401
|
-
reject(err);
|
|
1402
|
-
}
|
|
1403
|
-
} else {
|
|
1404
|
-
rl.setPrompt("... ");
|
|
1405
|
-
rl.prompt();
|
|
1406
|
-
}
|
|
1407
|
-
});
|
|
1408
|
-
rl.on("close", () => {
|
|
1409
|
-
if (cancelled) {
|
|
1410
|
-
reject(new Error("Input cancelled."));
|
|
1411
|
-
return;
|
|
1412
|
-
}
|
|
1413
|
-
if (lines.length > 0 && (!started || depth > 0 || inBlockComment || inString)) {
|
|
1414
|
-
try {
|
|
1415
|
-
resolve(parseData(lines.join("\n")));
|
|
1416
|
-
} catch (err) {
|
|
1417
|
-
reject(err);
|
|
1418
|
-
}
|
|
1419
|
-
} else if (lines.length === 0) {
|
|
1420
|
-
reject(new Error("No input provided."));
|
|
1421
|
-
}
|
|
1422
|
-
});
|
|
1423
|
-
});
|
|
1424
|
-
}
|
|
1425
|
-
function trackDepth(line, depth, started, inBlockComment, inString, stringChar) {
|
|
1426
|
-
for (let i = 0; i < line.length; i++) {
|
|
1427
|
-
const ch = line[i];
|
|
1428
|
-
const next = i + 1 < line.length ? line[i + 1] : "";
|
|
1429
|
-
if (inBlockComment) {
|
|
1430
|
-
if (ch === "*" && next === "/") {
|
|
1431
|
-
inBlockComment = false;
|
|
1432
|
-
i++;
|
|
1433
|
-
}
|
|
1434
|
-
continue;
|
|
1435
|
-
}
|
|
1436
|
-
if (inString) {
|
|
1437
|
-
if (ch === "\\" && i + 1 < line.length) {
|
|
1438
|
-
i++;
|
|
1439
|
-
} else if (ch === stringChar) {
|
|
1440
|
-
inString = false;
|
|
1441
|
-
}
|
|
1442
|
-
continue;
|
|
1443
|
-
}
|
|
1444
|
-
if (ch === "/" && next === "/") break;
|
|
1445
|
-
if (ch === "/" && next === "*") {
|
|
1446
|
-
inBlockComment = true;
|
|
1447
|
-
i++;
|
|
1448
|
-
continue;
|
|
1449
|
-
}
|
|
1450
|
-
if (ch === '"' || ch === "'") {
|
|
1451
|
-
inString = true;
|
|
1452
|
-
stringChar = ch;
|
|
1453
|
-
} else if (ch === "{" || ch === "[") {
|
|
1454
|
-
depth++;
|
|
1455
|
-
started = true;
|
|
1456
|
-
} else if (ch === "}" || ch === "]") {
|
|
1457
|
-
depth--;
|
|
1458
|
-
}
|
|
1459
|
-
}
|
|
1460
|
-
return { depth, started, inBlockComment, inString, stringChar };
|
|
1461
|
-
}
|
|
1462
|
-
|
|
1463
1617
|
// src/commands/attrs.ts
|
|
1464
1618
|
function addAttrsSubcommands(attrs) {
|
|
1465
1619
|
const list = attrs.command("list").description("List all attributes of an entity").argument("<entityId>", "Entity ID").action(
|
|
@@ -1562,7 +1716,7 @@ function registerAttrsSubcommand(entitiesCmd) {
|
|
|
1562
1716
|
// src/commands/entities.ts
|
|
1563
1717
|
function registerEntitiesCommand(program2) {
|
|
1564
1718
|
const entities = program2.command("entities").description("Manage context entities");
|
|
1565
|
-
const list = entities.command("list").description("List entities with optional filters").option("--type <type>", "Filter by entity type").option("--id-pattern <pat>", "Filter by entity ID pattern (regex)").option("--query <q>", "NGSI query expression").option("--attrs <a,b>", "Comma-separated list of attributes to include").option("--georel <rel>", "Geo-relationship (e.g. near;maxDistance:1000)").option("--geometry <geo>", "Geometry type for geo-query (e.g. point)").option("--coords <coords>", "Coordinates for geo-query").option("--spatial-id <zfxy>", "Spatial ID filter (ZFXY tile)").option("--limit <n>", "Maximum number of entities to return", parseInt).option("--offset <n>", "Skip first N entities", parseInt).option("--order-by <field>", "Order results by field").option("--count", "Include total count in response").option("--key-values", "Request simplified key-value format").action(
|
|
1719
|
+
const list = entities.command("list").description("List entities with optional filters").option("--type <type>", "Filter by entity type").option("--id-pattern <pat>", "Filter by entity ID pattern (regex)").option("--query <q>", "NGSI query expression").option("--attrs <a,b>", "Comma-separated list of attributes to include").option("--georel <rel>", "Geo-relationship (e.g. near;maxDistance:1000)").option("--geometry <geo>", "Geometry type for geo-query (e.g. point)").option("--coords <coords>", "Coordinates for geo-query").option("--spatial-id <zfxy>", "Spatial ID filter (ZFXY tile)").option("--limit <n>", "Maximum number of entities to return", parseInt).option("--offset <n>", "Skip first N entities", parseInt).option("--order-by <field>", "Order results by field").option("--count", "Include total count in response").option("--count-only", "Only show the total count without listing entities").option("--key-values", "Request simplified key-value format").action(
|
|
1566
1720
|
withErrorHandler(async (opts, cmd) => {
|
|
1567
1721
|
const client = createClient(cmd);
|
|
1568
1722
|
const format = getFormat(cmd);
|
|
@@ -1578,10 +1732,15 @@ function registerEntitiesCommand(program2) {
|
|
|
1578
1732
|
if (opts.limit !== void 0) params.limit = String(opts.limit);
|
|
1579
1733
|
if (opts.offset !== void 0) params.offset = String(opts.offset);
|
|
1580
1734
|
if (opts.orderBy) params.orderBy = String(opts.orderBy);
|
|
1581
|
-
if (opts.count) params.count = "true";
|
|
1735
|
+
if (opts.count || opts.countOnly) params.count = "true";
|
|
1736
|
+
if (opts.countOnly) params.limit = "0";
|
|
1582
1737
|
if (opts.keyValues) params.options = "keyValues";
|
|
1583
1738
|
const response = await client.get("/entities", params);
|
|
1584
|
-
|
|
1739
|
+
if (opts.countOnly) {
|
|
1740
|
+
printCount(response.count ?? 0);
|
|
1741
|
+
} else {
|
|
1742
|
+
outputResponse(response, format, !!opts.count);
|
|
1743
|
+
}
|
|
1585
1744
|
})
|
|
1586
1745
|
);
|
|
1587
1746
|
addExamples(list, [
|
|
@@ -1640,6 +1799,10 @@ function registerEntitiesCommand(program2) {
|
|
|
1640
1799
|
{
|
|
1641
1800
|
description: "Get total count with results",
|
|
1642
1801
|
command: "geonic entities list --type Sensor --count"
|
|
1802
|
+
},
|
|
1803
|
+
{
|
|
1804
|
+
description: "Get only the total count (no entity data)",
|
|
1805
|
+
command: "geonic entities list --type Sensor --count-only"
|
|
1643
1806
|
}
|
|
1644
1807
|
]);
|
|
1645
1808
|
const get = entities.command("get").description("Get a single entity by ID").argument("<id>", "Entity ID").option("--key-values", "Request simplified key-value format").action(
|