@geolonia/geonicdb-cli 0.3.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/dist/index.js +293 -159
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -584,6 +584,36 @@ 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
|
|
588
618
|
var DryRunSignal = class extends Error {
|
|
589
619
|
constructor() {
|
|
@@ -597,6 +627,8 @@ var GdbClient = class _GdbClient {
|
|
|
597
627
|
token;
|
|
598
628
|
refreshToken;
|
|
599
629
|
apiKey;
|
|
630
|
+
clientId;
|
|
631
|
+
clientSecret;
|
|
600
632
|
onTokenRefresh;
|
|
601
633
|
verbose;
|
|
602
634
|
dryRun;
|
|
@@ -607,6 +639,8 @@ var GdbClient = class _GdbClient {
|
|
|
607
639
|
this.token = options.token;
|
|
608
640
|
this.refreshToken = options.refreshToken;
|
|
609
641
|
this.apiKey = options.apiKey;
|
|
642
|
+
this.clientId = options.clientId;
|
|
643
|
+
this.clientSecret = options.clientSecret;
|
|
610
644
|
this.onTokenRefresh = options.onTokenRefresh;
|
|
611
645
|
this.verbose = options.verbose ?? false;
|
|
612
646
|
this.dryRun = options.dryRun ?? false;
|
|
@@ -713,7 +747,7 @@ var GdbClient = class _GdbClient {
|
|
|
713
747
|
throw new DryRunSignal();
|
|
714
748
|
}
|
|
715
749
|
canRefresh() {
|
|
716
|
-
return !!this.refreshToken && !this.apiKey;
|
|
750
|
+
return (!!this.refreshToken || !!this.clientId && !!this.clientSecret) && !this.apiKey;
|
|
717
751
|
}
|
|
718
752
|
async performTokenRefresh() {
|
|
719
753
|
if (this.refreshPromise) return this.refreshPromise;
|
|
@@ -725,26 +759,43 @@ var GdbClient = class _GdbClient {
|
|
|
725
759
|
}
|
|
726
760
|
}
|
|
727
761
|
async doRefresh() {
|
|
728
|
-
if (
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
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
|
+
}
|
|
747
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;
|
|
748
799
|
}
|
|
749
800
|
async executeRequest(method, path, options) {
|
|
750
801
|
const url = this.buildUrl(`${this.getBasePath()}${path}`, options?.params);
|
|
@@ -877,6 +928,8 @@ function createClient(cmd) {
|
|
|
877
928
|
service: opts.service,
|
|
878
929
|
token: opts.token,
|
|
879
930
|
refreshToken: usingCliToken ? void 0 : config.refreshToken,
|
|
931
|
+
clientId: usingCliToken ? void 0 : config.clientId,
|
|
932
|
+
clientSecret: usingCliToken ? void 0 : config.clientSecret,
|
|
880
933
|
apiKey: opts.apiKey,
|
|
881
934
|
onTokenRefresh: usingCliToken ? void 0 : (token, refreshToken) => {
|
|
882
935
|
const cfg = loadConfig(opts.profile);
|
|
@@ -1057,35 +1110,217 @@ function formatDuration(ms) {
|
|
|
1057
1110
|
return parts.join(" ");
|
|
1058
1111
|
}
|
|
1059
1112
|
|
|
1060
|
-
// src/
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
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);
|
|
1068
1122
|
}
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
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> "
|
|
1076
1136
|
});
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
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--;
|
|
1085
1221
|
}
|
|
1086
|
-
throw new Error(`OAuth token request failed: ${message}`);
|
|
1087
1222
|
}
|
|
1088
|
-
return
|
|
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
|
+
]);
|
|
1089
1324
|
}
|
|
1090
1325
|
|
|
1091
1326
|
// src/commands/auth.ts
|
|
@@ -1245,13 +1480,25 @@ function registerAuthCommands(program2) {
|
|
|
1245
1480
|
}
|
|
1246
1481
|
]);
|
|
1247
1482
|
auth.addCommand(logout);
|
|
1248
|
-
const me = program2.command("me").description("Display current authenticated user")
|
|
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());
|
|
1249
1485
|
addExamples(me, [
|
|
1250
1486
|
{
|
|
1251
1487
|
description: "Show current user info",
|
|
1252
1488
|
command: "geonic me"
|
|
1489
|
+
},
|
|
1490
|
+
{
|
|
1491
|
+
description: "List your OAuth clients",
|
|
1492
|
+
command: "geonic me oauth-clients list"
|
|
1253
1493
|
}
|
|
1254
1494
|
]);
|
|
1495
|
+
addExamples(meInfo, [
|
|
1496
|
+
{
|
|
1497
|
+
description: "Show current user info",
|
|
1498
|
+
command: "geonic me"
|
|
1499
|
+
}
|
|
1500
|
+
]);
|
|
1501
|
+
addMeOAuthClientsSubcommand(me);
|
|
1255
1502
|
program2.addCommand(createLoginCommand(), { hidden: true });
|
|
1256
1503
|
program2.addCommand(createLogoutCommand(), { hidden: true });
|
|
1257
1504
|
const hiddenWhoami = new Command("whoami").description("Display current authenticated user").action(createMeAction());
|
|
@@ -1347,119 +1594,6 @@ function registerProfileCommands(program2) {
|
|
|
1347
1594
|
]);
|
|
1348
1595
|
}
|
|
1349
1596
|
|
|
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
1597
|
// src/commands/attrs.ts
|
|
1464
1598
|
function addAttrsSubcommands(attrs) {
|
|
1465
1599
|
const list = attrs.command("list").description("List all attributes of an entity").argument("<entityId>", "Entity ID").action(
|