@eide/foir-cli 0.1.46 → 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.
- package/dist/cli.js +1003 -1016
- package/dist/lib/config-helpers.d.ts +86 -206
- package/package.json +7 -14
- package/dist/schema.graphql +0 -6209
package/dist/cli.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { config } from "dotenv";
|
|
5
|
-
import { resolve as resolve6, dirname as
|
|
6
|
-
import { fileURLToPath
|
|
5
|
+
import { resolve as resolve6, dirname as dirname4 } from "path";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
7
|
import { createRequire } from "module";
|
|
8
8
|
import { Command } from "commander";
|
|
9
9
|
|
|
@@ -237,23 +237,15 @@ var DEFAULT_API_URL = "https://api.foir.dev";
|
|
|
237
237
|
function getApiUrl(options) {
|
|
238
238
|
return process.env.FOIR_API_URL ?? options?.apiUrl ?? DEFAULT_API_URL;
|
|
239
239
|
}
|
|
240
|
-
function getGraphQLEndpoint(apiUrl) {
|
|
241
|
-
const base = apiUrl.replace(/\/$/, "").replace(/\/graphql$/, "");
|
|
242
|
-
return `${base}/graphql`;
|
|
243
|
-
}
|
|
244
240
|
|
|
245
241
|
// src/lib/errors.ts
|
|
246
242
|
import chalk from "chalk";
|
|
243
|
+
import { ConnectError, Code } from "@connectrpc/connect";
|
|
247
244
|
function extractErrorMessage(error) {
|
|
248
245
|
if (!error || typeof error !== "object")
|
|
249
246
|
return String(error ?? "Unknown error");
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
if (gqlErrors && gqlErrors.length > 0) {
|
|
253
|
-
return gqlErrors[0].message;
|
|
254
|
-
}
|
|
255
|
-
if (err.message && err.message !== "undefined") {
|
|
256
|
-
return err.message;
|
|
247
|
+
if (error instanceof ConnectError) {
|
|
248
|
+
return error.message;
|
|
257
249
|
}
|
|
258
250
|
if (error instanceof Error && error.message) {
|
|
259
251
|
return error.message;
|
|
@@ -266,44 +258,25 @@ function withErrorHandler(optsFn, fn) {
|
|
|
266
258
|
await fn(...args);
|
|
267
259
|
} catch (error) {
|
|
268
260
|
const opts = optsFn();
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
const first = gqlErrors[0];
|
|
273
|
-
const code = first.extensions?.code;
|
|
274
|
-
const validationErrors = first.extensions?.validationErrors;
|
|
261
|
+
if (error instanceof ConnectError) {
|
|
262
|
+
const code = error.code;
|
|
263
|
+
const message2 = error.message;
|
|
275
264
|
if (opts?.json || opts?.jsonl) {
|
|
276
265
|
console.error(
|
|
277
266
|
JSON.stringify({
|
|
278
267
|
error: {
|
|
279
|
-
message:
|
|
280
|
-
code: code ?? "
|
|
281
|
-
...validationErrors ? { validationErrors } : {},
|
|
282
|
-
...gqlErrors.length > 1 ? {
|
|
283
|
-
additionalErrors: gqlErrors.slice(1).map((e) => e.message)
|
|
284
|
-
} : {}
|
|
268
|
+
message: message2,
|
|
269
|
+
code: Code[code] ?? "UNKNOWN"
|
|
285
270
|
}
|
|
286
271
|
})
|
|
287
272
|
);
|
|
288
273
|
} else {
|
|
289
|
-
console.error(chalk.red("Error:"),
|
|
290
|
-
|
|
291
|
-
console.error(chalk.red(" \u2022"), extra.message);
|
|
292
|
-
}
|
|
293
|
-
if (validationErrors && Object.keys(validationErrors).length > 0) {
|
|
294
|
-
console.error("");
|
|
295
|
-
console.error(chalk.yellow("Field errors:"));
|
|
296
|
-
for (const [field, messages] of Object.entries(validationErrors)) {
|
|
297
|
-
for (const msg of messages) {
|
|
298
|
-
console.error(chalk.gray(` \u2022 ${field}:`), msg);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
if (code === "UNAUTHENTICATED") {
|
|
274
|
+
console.error(chalk.red("Error:"), message2);
|
|
275
|
+
if (code === Code.Unauthenticated) {
|
|
303
276
|
console.error(
|
|
304
277
|
chalk.gray("\nHint: Run `foir login` to authenticate.")
|
|
305
278
|
);
|
|
306
|
-
} else if (code ===
|
|
279
|
+
} else if (code === Code.PermissionDenied) {
|
|
307
280
|
console.error(
|
|
308
281
|
chalk.gray(
|
|
309
282
|
"\nHint: You may not have permission. Check your API key scopes."
|
|
@@ -313,22 +286,6 @@ function withErrorHandler(optsFn, fn) {
|
|
|
313
286
|
}
|
|
314
287
|
process.exit(1);
|
|
315
288
|
}
|
|
316
|
-
const status = gqlErr?.response?.status;
|
|
317
|
-
if (status === 401 || status === 403) {
|
|
318
|
-
if (opts?.json || opts?.jsonl) {
|
|
319
|
-
console.error(
|
|
320
|
-
JSON.stringify({
|
|
321
|
-
error: { message: "Authentication failed", code: "AUTH_ERROR" }
|
|
322
|
-
})
|
|
323
|
-
);
|
|
324
|
-
} else {
|
|
325
|
-
console.error(chalk.red("Error:"), "Authentication failed.");
|
|
326
|
-
console.error(
|
|
327
|
-
chalk.gray("Hint: Run `foir login` or set FOIR_API_KEY.")
|
|
328
|
-
);
|
|
329
|
-
}
|
|
330
|
-
process.exit(1);
|
|
331
|
-
}
|
|
332
289
|
const message = extractErrorMessage(error);
|
|
333
290
|
if (opts?.json || opts?.jsonl) {
|
|
334
291
|
console.error(JSON.stringify({ error: { message } }));
|
|
@@ -489,102 +446,153 @@ function registerLogoutCommand(program2, globalOpts) {
|
|
|
489
446
|
|
|
490
447
|
// src/commands/select-project.ts
|
|
491
448
|
import inquirer from "inquirer";
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
449
|
+
|
|
450
|
+
// src/lib/client.ts
|
|
451
|
+
import { createClient } from "@connectrpc/connect";
|
|
452
|
+
import { createConnectTransport } from "@connectrpc/connect-node";
|
|
453
|
+
import {
|
|
454
|
+
IdentityService,
|
|
455
|
+
ModelsService,
|
|
456
|
+
RecordsService,
|
|
457
|
+
ConfigsService,
|
|
458
|
+
SegmentsService,
|
|
459
|
+
ExperimentsService,
|
|
460
|
+
SettingsService,
|
|
461
|
+
StorageService
|
|
462
|
+
} from "@foir/connect-clients/services";
|
|
463
|
+
import { createIdentityMethods } from "@foir/connect-clients/identity";
|
|
464
|
+
import { createModelsMethods } from "@foir/connect-clients/models";
|
|
465
|
+
import { createRecordsMethods } from "@foir/connect-clients/records";
|
|
466
|
+
import { createConfigsMethods } from "@foir/connect-clients/configs";
|
|
467
|
+
import { createSegmentsMethods } from "@foir/connect-clients/segments";
|
|
468
|
+
import { createExperimentsMethods } from "@foir/connect-clients/experiments";
|
|
469
|
+
import { createSettingsMethods } from "@foir/connect-clients/settings";
|
|
470
|
+
import { createStorageMethods } from "@foir/connect-clients/storage";
|
|
471
|
+
async function createPlatformClient(options) {
|
|
472
|
+
const apiUrl = getApiUrl(options);
|
|
473
|
+
const headers = {};
|
|
474
|
+
const envApiKey = process.env.FOIR_API_KEY;
|
|
475
|
+
if (envApiKey) {
|
|
476
|
+
headers["x-api-key"] = envApiKey;
|
|
477
|
+
} else {
|
|
478
|
+
const credentials = await getCredentials();
|
|
479
|
+
if (!credentials) {
|
|
480
|
+
throw new Error(
|
|
481
|
+
"Not authenticated. Run `foir login` or set FOIR_API_KEY."
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
if (isTokenExpired(credentials)) {
|
|
485
|
+
throw new Error("Session expired. Run `foir login` to re-authenticate.");
|
|
486
|
+
}
|
|
487
|
+
headers["Authorization"] = `Bearer ${credentials.accessToken}`;
|
|
516
488
|
}
|
|
517
|
-
const
|
|
518
|
-
if (
|
|
519
|
-
|
|
489
|
+
const resolved = await resolveProjectContext(options);
|
|
490
|
+
if (resolved) {
|
|
491
|
+
headers["x-tenant-id"] = resolved.project.tenantId;
|
|
492
|
+
headers["x-project-id"] = resolved.project.id;
|
|
520
493
|
}
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
accessToken,
|
|
545
|
-
`mutation($input: CreateApiKeyInput!) { createApiKey(input: $input) { apiKey { id name isActive } plainKey } }`,
|
|
546
|
-
{
|
|
547
|
-
input: {
|
|
548
|
-
name: CLI_API_KEY_NAME,
|
|
549
|
-
projectId,
|
|
550
|
-
keyType: "SECRET",
|
|
551
|
-
scopes: CLI_API_KEY_SCOPES
|
|
552
|
-
}
|
|
553
|
-
},
|
|
554
|
-
{ "x-tenant-id": tenantId, "x-project-id": projectId }
|
|
555
|
-
);
|
|
556
|
-
return data.createApiKey;
|
|
494
|
+
const authInterceptor = (next) => async (req) => {
|
|
495
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
496
|
+
req.header.set(key, value);
|
|
497
|
+
}
|
|
498
|
+
return next(req);
|
|
499
|
+
};
|
|
500
|
+
const transport = createConnectTransport({
|
|
501
|
+
baseUrl: apiUrl.replace(/\/$/, ""),
|
|
502
|
+
httpVersion: "1.1",
|
|
503
|
+
interceptors: [authInterceptor]
|
|
504
|
+
});
|
|
505
|
+
return {
|
|
506
|
+
identity: createIdentityMethods(createClient(IdentityService, transport)),
|
|
507
|
+
models: createModelsMethods(createClient(ModelsService, transport)),
|
|
508
|
+
records: createRecordsMethods(createClient(RecordsService, transport)),
|
|
509
|
+
configs: createConfigsMethods(createClient(ConfigsService, transport)),
|
|
510
|
+
segments: createSegmentsMethods(createClient(SegmentsService, transport)),
|
|
511
|
+
experiments: createExperimentsMethods(
|
|
512
|
+
createClient(ExperimentsService, transport)
|
|
513
|
+
),
|
|
514
|
+
settings: createSettingsMethods(createClient(SettingsService, transport)),
|
|
515
|
+
storage: createStorageMethods(createClient(StorageService, transport))
|
|
516
|
+
};
|
|
557
517
|
}
|
|
558
|
-
|
|
559
|
-
const
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
518
|
+
function createPlatformClientWithHeaders(apiUrl, headers) {
|
|
519
|
+
const authInterceptor = (next) => async (req) => {
|
|
520
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
521
|
+
req.header.set(key, value);
|
|
522
|
+
}
|
|
523
|
+
return next(req);
|
|
524
|
+
};
|
|
525
|
+
const transport = createConnectTransport({
|
|
526
|
+
baseUrl: apiUrl.replace(/\/$/, ""),
|
|
527
|
+
httpVersion: "1.1",
|
|
528
|
+
interceptors: [authInterceptor]
|
|
529
|
+
});
|
|
530
|
+
return {
|
|
531
|
+
identity: createIdentityMethods(createClient(IdentityService, transport)),
|
|
532
|
+
models: createModelsMethods(createClient(ModelsService, transport)),
|
|
533
|
+
records: createRecordsMethods(createClient(RecordsService, transport)),
|
|
534
|
+
configs: createConfigsMethods(createClient(ConfigsService, transport)),
|
|
535
|
+
segments: createSegmentsMethods(createClient(SegmentsService, transport)),
|
|
536
|
+
experiments: createExperimentsMethods(
|
|
537
|
+
createClient(ExperimentsService, transport)
|
|
538
|
+
),
|
|
539
|
+
settings: createSettingsMethods(createClient(SettingsService, transport)),
|
|
540
|
+
storage: createStorageMethods(createClient(StorageService, transport))
|
|
541
|
+
};
|
|
567
542
|
}
|
|
568
|
-
async function
|
|
569
|
-
const
|
|
570
|
-
const
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
543
|
+
async function getStorageAuth(options) {
|
|
544
|
+
const apiUrl = getApiUrl(options);
|
|
545
|
+
const baseHeaders = {
|
|
546
|
+
"Content-Type": "application/json"
|
|
547
|
+
};
|
|
548
|
+
const envApiKey = process.env.FOIR_API_KEY;
|
|
549
|
+
if (envApiKey) {
|
|
550
|
+
baseHeaders["x-api-key"] = envApiKey;
|
|
551
|
+
} else {
|
|
552
|
+
const credentials = await getCredentials();
|
|
553
|
+
if (!credentials) {
|
|
554
|
+
throw new Error(
|
|
555
|
+
"Not authenticated. Run `foir login` or set FOIR_API_KEY."
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
if (isTokenExpired(credentials)) {
|
|
559
|
+
throw new Error("Session expired. Run `foir login` to re-authenticate.");
|
|
560
|
+
}
|
|
561
|
+
baseHeaders["Authorization"] = `Bearer ${credentials.accessToken}`;
|
|
583
562
|
}
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
563
|
+
const resolved = await resolveProjectContext(options);
|
|
564
|
+
if (resolved) {
|
|
565
|
+
baseHeaders["x-tenant-id"] = resolved.project.tenantId;
|
|
566
|
+
baseHeaders["x-project-id"] = resolved.project.id;
|
|
567
|
+
}
|
|
568
|
+
let cachedToken = null;
|
|
569
|
+
const getToken = async () => {
|
|
570
|
+
if (cachedToken && Date.now() < cachedToken.expiresAt - 3e4) {
|
|
571
|
+
return cachedToken.token;
|
|
572
|
+
}
|
|
573
|
+
const tokenUrl = `${apiUrl.replace(/\/$/, "")}/api/auth/token`;
|
|
574
|
+
const res = await fetch(tokenUrl, {
|
|
575
|
+
method: "POST",
|
|
576
|
+
headers: baseHeaders,
|
|
577
|
+
body: JSON.stringify({ purpose: "storage" })
|
|
578
|
+
});
|
|
579
|
+
if (!res.ok) {
|
|
580
|
+
throw new Error(
|
|
581
|
+
`Failed to get storage token (${res.status}): ${await res.text()}`
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
const data = await res.json();
|
|
585
|
+
cachedToken = {
|
|
586
|
+
token: data.token,
|
|
587
|
+
expiresAt: new Date(data.expiresAt).getTime()
|
|
588
|
+
};
|
|
589
|
+
return cachedToken.token;
|
|
590
|
+
};
|
|
591
|
+
return { getToken };
|
|
587
592
|
}
|
|
593
|
+
|
|
594
|
+
// src/commands/select-project.ts
|
|
595
|
+
var CLI_API_KEY_NAME = "Foir CLI";
|
|
588
596
|
function registerSelectProjectCommand(program2, globalOpts) {
|
|
589
597
|
program2.command("select-project").description("Choose which project to work with").option("--project-id <id>", "Project ID to select directly").option("--save-as <name>", "Save as a named profile").action(
|
|
590
598
|
withErrorHandler(
|
|
@@ -598,11 +606,24 @@ function registerSelectProjectCommand(program2, globalOpts) {
|
|
|
598
606
|
throw new Error("Not authenticated");
|
|
599
607
|
}
|
|
600
608
|
console.log("Fetching your projects...\n");
|
|
601
|
-
const
|
|
602
|
-
|
|
603
|
-
|
|
609
|
+
const client = createPlatformClientWithHeaders(apiUrl, {
|
|
610
|
+
Authorization: `Bearer ${credentials.accessToken}`
|
|
611
|
+
});
|
|
612
|
+
const sessionContext = await client.identity.getSessionContext();
|
|
613
|
+
if (!sessionContext) {
|
|
614
|
+
throw new Error("Could not fetch session context");
|
|
615
|
+
}
|
|
616
|
+
const tenants = (sessionContext.availableTenants ?? []).map(
|
|
617
|
+
(t) => ({
|
|
618
|
+
id: t.id,
|
|
619
|
+
name: t.name
|
|
620
|
+
})
|
|
604
621
|
);
|
|
605
|
-
const
|
|
622
|
+
const projects = (sessionContext.availableProjects ?? []).map((p) => ({
|
|
623
|
+
id: p.id,
|
|
624
|
+
name: p.name,
|
|
625
|
+
tenantId: p.tenantId
|
|
626
|
+
}));
|
|
606
627
|
if (projects.length === 0) {
|
|
607
628
|
console.log("No projects found. Create one in the platform first.");
|
|
608
629
|
throw new Error("No projects available");
|
|
@@ -651,12 +672,12 @@ function registerSelectProjectCommand(program2, globalOpts) {
|
|
|
651
672
|
selectedProject = projects.find((p) => p.id === projectId);
|
|
652
673
|
}
|
|
653
674
|
console.log("\nProvisioning API key for CLI access...");
|
|
654
|
-
const
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
selectedProject.id
|
|
658
|
-
|
|
659
|
-
);
|
|
675
|
+
const projectClient = createPlatformClientWithHeaders(apiUrl, {
|
|
676
|
+
Authorization: `Bearer ${credentials.accessToken}`,
|
|
677
|
+
"x-tenant-id": selectedProject.tenantId,
|
|
678
|
+
"x-project-id": selectedProject.id
|
|
679
|
+
});
|
|
680
|
+
const { apiKey, apiKeyId } = await provisionApiKey(projectClient);
|
|
660
681
|
await writeProjectContext(
|
|
661
682
|
{
|
|
662
683
|
id: selectedProject.id,
|
|
@@ -681,6 +702,26 @@ function registerSelectProjectCommand(program2, globalOpts) {
|
|
|
681
702
|
)
|
|
682
703
|
);
|
|
683
704
|
}
|
|
705
|
+
async function provisionApiKey(client) {
|
|
706
|
+
const { items: apiKeys } = await client.identity.listApiKeys({ limit: 100 });
|
|
707
|
+
const existing = apiKeys.find(
|
|
708
|
+
(k) => k.name === CLI_API_KEY_NAME && k.isActive
|
|
709
|
+
);
|
|
710
|
+
if (existing) {
|
|
711
|
+
console.log(" Rotating existing CLI API key...");
|
|
712
|
+
const result2 = await client.identity.rotateApiKey(existing.id);
|
|
713
|
+
if (!result2.apiKey?.rawKey) {
|
|
714
|
+
throw new Error("Failed to rotate API key \u2014 no raw key returned");
|
|
715
|
+
}
|
|
716
|
+
return { apiKey: result2.apiKey.rawKey, apiKeyId: result2.apiKey.id };
|
|
717
|
+
}
|
|
718
|
+
console.log(" Creating CLI API key...");
|
|
719
|
+
const result = await client.identity.createApiKey({ name: CLI_API_KEY_NAME });
|
|
720
|
+
if (!result.apiKey?.rawKey) {
|
|
721
|
+
throw new Error("Failed to create API key \u2014 no raw key returned");
|
|
722
|
+
}
|
|
723
|
+
return { apiKey: result.apiKey.rawKey, apiKeyId: result.apiKey.id };
|
|
724
|
+
}
|
|
684
725
|
|
|
685
726
|
// src/lib/output.ts
|
|
686
727
|
import chalk2 from "chalk";
|
|
@@ -734,11 +775,11 @@ function formatList(items, options, config2) {
|
|
|
734
775
|
${items.length} of ${config2.total} shown`));
|
|
735
776
|
}
|
|
736
777
|
}
|
|
737
|
-
function pad(
|
|
738
|
-
if (
|
|
739
|
-
return
|
|
778
|
+
function pad(str2, width) {
|
|
779
|
+
if (str2.length > width) {
|
|
780
|
+
return str2.slice(0, width - 1) + "\u2026";
|
|
740
781
|
}
|
|
741
|
-
return
|
|
782
|
+
return str2.padEnd(width);
|
|
742
783
|
}
|
|
743
784
|
function timeAgo(dateStr) {
|
|
744
785
|
if (!dateStr) return "\u2014";
|
|
@@ -831,99 +872,303 @@ function registerWhoamiCommand(program2, globalOpts) {
|
|
|
831
872
|
import { promises as fs2 } from "fs";
|
|
832
873
|
import { basename } from "path";
|
|
833
874
|
import chalk3 from "chalk";
|
|
875
|
+
import { createStorageClient } from "@foir/connect-clients/storage";
|
|
834
876
|
|
|
835
|
-
// src/lib/
|
|
836
|
-
import
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
const
|
|
844
|
-
if (
|
|
845
|
-
|
|
846
|
-
return
|
|
847
|
-
}
|
|
848
|
-
const credentials = await getCredentials();
|
|
849
|
-
if (!credentials) {
|
|
850
|
-
throw new Error("Not authenticated. Run `foir login` or set FOIR_API_KEY.");
|
|
877
|
+
// src/lib/input.ts
|
|
878
|
+
import inquirer2 from "inquirer";
|
|
879
|
+
|
|
880
|
+
// src/lib/config-loader.ts
|
|
881
|
+
import { readFile } from "fs/promises";
|
|
882
|
+
import { pathToFileURL } from "url";
|
|
883
|
+
import { resolve } from "path";
|
|
884
|
+
async function loadConfig(filePath) {
|
|
885
|
+
const absPath = resolve(filePath);
|
|
886
|
+
if (filePath.endsWith(".ts")) {
|
|
887
|
+
const configModule = await import(pathToFileURL(absPath).href);
|
|
888
|
+
return configModule.default;
|
|
851
889
|
}
|
|
852
|
-
if (
|
|
853
|
-
|
|
890
|
+
if (filePath.endsWith(".js") || filePath.endsWith(".mjs")) {
|
|
891
|
+
const configModule = await import(pathToFileURL(absPath).href);
|
|
892
|
+
return configModule.default;
|
|
854
893
|
}
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
headers["x-tenant-id"] = resolved.project.tenantId;
|
|
859
|
-
headers["x-project-id"] = resolved.project.id;
|
|
894
|
+
if (filePath.endsWith(".json")) {
|
|
895
|
+
const content = await readFile(absPath, "utf-8");
|
|
896
|
+
return JSON.parse(content);
|
|
860
897
|
}
|
|
861
|
-
|
|
898
|
+
throw new Error(
|
|
899
|
+
`Unsupported file extension for "${filePath}". Supported: .ts, .js, .mjs, .json`
|
|
900
|
+
);
|
|
862
901
|
}
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
headers["x-api-key"] = envApiKey;
|
|
869
|
-
return { apiUrl, headers };
|
|
870
|
-
}
|
|
871
|
-
const credentials = await getCredentials();
|
|
872
|
-
if (!credentials) {
|
|
873
|
-
throw new Error("Not authenticated. Run `foir login` or set FOIR_API_KEY.");
|
|
902
|
+
|
|
903
|
+
// src/lib/input.ts
|
|
904
|
+
async function parseInputData(opts) {
|
|
905
|
+
if (opts.data) {
|
|
906
|
+
return JSON.parse(opts.data);
|
|
874
907
|
}
|
|
875
|
-
if (
|
|
876
|
-
|
|
908
|
+
if (opts.file) {
|
|
909
|
+
return await loadConfig(opts.file);
|
|
877
910
|
}
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
911
|
+
if (!process.stdin.isTTY) {
|
|
912
|
+
const chunks = [];
|
|
913
|
+
for await (const chunk of process.stdin) {
|
|
914
|
+
chunks.push(chunk);
|
|
915
|
+
}
|
|
916
|
+
const stdinContent = Buffer.concat(chunks).toString("utf-8").trim();
|
|
917
|
+
if (stdinContent) {
|
|
918
|
+
return JSON.parse(stdinContent);
|
|
919
|
+
}
|
|
883
920
|
}
|
|
884
|
-
|
|
921
|
+
throw new Error(
|
|
922
|
+
"No input data provided. Use --data, --file, or pipe via stdin."
|
|
923
|
+
);
|
|
924
|
+
}
|
|
925
|
+
function isUUID(value) {
|
|
926
|
+
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(
|
|
927
|
+
value
|
|
928
|
+
) || /^c[a-z0-9]{24,}$/.test(value);
|
|
929
|
+
}
|
|
930
|
+
async function confirmAction(message, opts) {
|
|
931
|
+
if (opts?.confirm) return true;
|
|
932
|
+
const { confirmed } = await inquirer2.prompt([
|
|
933
|
+
{
|
|
934
|
+
type: "confirm",
|
|
935
|
+
name: "confirmed",
|
|
936
|
+
message,
|
|
937
|
+
default: false
|
|
938
|
+
}
|
|
939
|
+
]);
|
|
940
|
+
return confirmed;
|
|
885
941
|
}
|
|
886
942
|
|
|
887
943
|
// src/commands/media.ts
|
|
944
|
+
var MIME_TYPES = {
|
|
945
|
+
".jpg": "image/jpeg",
|
|
946
|
+
".jpeg": "image/jpeg",
|
|
947
|
+
".png": "image/png",
|
|
948
|
+
".gif": "image/gif",
|
|
949
|
+
".webp": "image/webp",
|
|
950
|
+
".avif": "image/avif",
|
|
951
|
+
".svg": "image/svg+xml",
|
|
952
|
+
".mp4": "video/mp4",
|
|
953
|
+
".webm": "video/webm",
|
|
954
|
+
".mov": "video/quicktime",
|
|
955
|
+
".mp3": "audio/mpeg",
|
|
956
|
+
".wav": "audio/wav",
|
|
957
|
+
".pdf": "application/pdf",
|
|
958
|
+
".json": "application/json",
|
|
959
|
+
".csv": "text/csv",
|
|
960
|
+
".txt": "text/plain"
|
|
961
|
+
};
|
|
962
|
+
function guessMimeType(filename) {
|
|
963
|
+
const ext = filename.slice(filename.lastIndexOf(".")).toLowerCase();
|
|
964
|
+
return MIME_TYPES[ext] ?? "application/octet-stream";
|
|
965
|
+
}
|
|
966
|
+
function getStorageUrl() {
|
|
967
|
+
return process.env.FOIR_STORAGE_URL ?? "https://storage.foir.dev";
|
|
968
|
+
}
|
|
969
|
+
function createClient2(getToken) {
|
|
970
|
+
return createStorageClient({
|
|
971
|
+
baseUrl: getStorageUrl(),
|
|
972
|
+
getAuthToken: getToken
|
|
973
|
+
});
|
|
974
|
+
}
|
|
888
975
|
function registerMediaCommands(program2, globalOpts) {
|
|
889
976
|
const media = program2.command("media").description("Media file operations");
|
|
890
|
-
media.command("upload <filepath>").description("Upload a file").action(
|
|
891
|
-
withErrorHandler(
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
977
|
+
media.command("upload <filepath>").description("Upload a file").option("--folder <folder>", "Target folder").action(
|
|
978
|
+
withErrorHandler(
|
|
979
|
+
globalOpts,
|
|
980
|
+
async (filepath, flags) => {
|
|
981
|
+
const opts = globalOpts();
|
|
982
|
+
const { getToken } = await getStorageAuth(opts);
|
|
983
|
+
const storage = createClient2(getToken);
|
|
984
|
+
const fileBuffer = await fs2.readFile(filepath);
|
|
985
|
+
const filename = basename(filepath);
|
|
986
|
+
const mimeType = guessMimeType(filename);
|
|
987
|
+
const upload = await storage.createFileUpload({
|
|
988
|
+
filename,
|
|
989
|
+
mimeType,
|
|
990
|
+
size: fileBuffer.byteLength,
|
|
991
|
+
folder: flags.folder
|
|
992
|
+
});
|
|
993
|
+
const uploadResp = await fetch(upload.uploadUrl, {
|
|
994
|
+
method: "PUT",
|
|
995
|
+
headers: { "Content-Type": mimeType },
|
|
996
|
+
body: fileBuffer
|
|
997
|
+
});
|
|
998
|
+
if (!uploadResp.ok) {
|
|
999
|
+
throw new Error(
|
|
1000
|
+
`Upload to storage failed (${uploadResp.status}): ${await uploadResp.text()}`
|
|
1001
|
+
);
|
|
1002
|
+
}
|
|
1003
|
+
const file = await storage.confirmFileUpload(upload.uploadId);
|
|
1004
|
+
if (opts.json || opts.jsonl) {
|
|
1005
|
+
formatOutput(file, opts);
|
|
1006
|
+
} else {
|
|
1007
|
+
success(`Uploaded ${filename}`);
|
|
1008
|
+
if (file?.url) {
|
|
1009
|
+
console.log(chalk3.bold(` URL: ${file.url}`));
|
|
1010
|
+
}
|
|
1011
|
+
if (file?.storageKey) {
|
|
1012
|
+
console.log(chalk3.gray(` Key: ${file.storageKey}`));
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
907
1015
|
}
|
|
908
|
-
|
|
1016
|
+
)
|
|
1017
|
+
);
|
|
1018
|
+
media.command("list").description("List files").option("--folder <folder>", "Filter by folder").option("--mime-type <type>", "Filter by MIME type").option("--search <query>", "Search files").option("--include-deleted", "Include soft-deleted files").option("--limit <n>", "Max results", "50").option("--offset <n>", "Offset", "0").action(
|
|
1019
|
+
withErrorHandler(
|
|
1020
|
+
globalOpts,
|
|
1021
|
+
async (flags) => {
|
|
1022
|
+
const opts = globalOpts();
|
|
1023
|
+
const { getToken } = await getStorageAuth(opts);
|
|
1024
|
+
const storage = createClient2(getToken);
|
|
1025
|
+
const result = await storage.listFiles({
|
|
1026
|
+
folder: flags.folder,
|
|
1027
|
+
mimeType: flags["mime-type"] ?? flags.mimeType,
|
|
1028
|
+
search: flags.search,
|
|
1029
|
+
includeDeleted: !!flags.includeDeleted,
|
|
1030
|
+
limit: Number(flags.limit) || 50,
|
|
1031
|
+
offset: Number(flags.offset) || 0
|
|
1032
|
+
});
|
|
1033
|
+
formatList(result.items, opts, {
|
|
1034
|
+
columns: [
|
|
1035
|
+
{ key: "id", header: "ID", width: 28 },
|
|
1036
|
+
{ key: "filename", header: "Filename", width: 24 },
|
|
1037
|
+
{ key: "mimeType", header: "Type", width: 16 },
|
|
1038
|
+
{
|
|
1039
|
+
key: "size",
|
|
1040
|
+
header: "Size",
|
|
1041
|
+
width: 10,
|
|
1042
|
+
format: (v) => {
|
|
1043
|
+
const num2 = Number(v);
|
|
1044
|
+
if (num2 < 1024) return `${num2} B`;
|
|
1045
|
+
if (num2 < 1024 * 1024) return `${(num2 / 1024).toFixed(1)} KB`;
|
|
1046
|
+
return `${(num2 / (1024 * 1024)).toFixed(1)} MB`;
|
|
1047
|
+
}
|
|
1048
|
+
},
|
|
1049
|
+
{ key: "folder", header: "Folder", width: 16 },
|
|
1050
|
+
{
|
|
1051
|
+
key: "createdAt",
|
|
1052
|
+
header: "Created",
|
|
1053
|
+
width: 12,
|
|
1054
|
+
format: (v) => timeAgo(v)
|
|
1055
|
+
}
|
|
1056
|
+
],
|
|
1057
|
+
total: result.total
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
)
|
|
1061
|
+
);
|
|
1062
|
+
media.command("get <id>").description("Get file details").action(
|
|
1063
|
+
withErrorHandler(globalOpts, async (id) => {
|
|
1064
|
+
const opts = globalOpts();
|
|
1065
|
+
const { getToken } = await getStorageAuth(opts);
|
|
1066
|
+
const storage = createClient2(getToken);
|
|
1067
|
+
const file = await storage.getFile(id);
|
|
1068
|
+
formatOutput(file, opts);
|
|
1069
|
+
})
|
|
1070
|
+
);
|
|
1071
|
+
media.command("usage").description("Get storage usage").action(
|
|
1072
|
+
withErrorHandler(globalOpts, async () => {
|
|
1073
|
+
const opts = globalOpts();
|
|
1074
|
+
const { getToken } = await getStorageAuth(opts);
|
|
1075
|
+
const storage = createClient2(getToken);
|
|
1076
|
+
const usage = await storage.getStorageUsage();
|
|
909
1077
|
if (opts.json || opts.jsonl) {
|
|
910
|
-
formatOutput(
|
|
1078
|
+
formatOutput(usage, opts);
|
|
911
1079
|
} else {
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
1080
|
+
const mb = (Number(usage?.totalBytes ?? 0) / (1024 * 1024)).toFixed(
|
|
1081
|
+
1
|
|
1082
|
+
);
|
|
1083
|
+
console.log(chalk3.bold(`Files: ${usage?.totalFiles ?? 0}`));
|
|
1084
|
+
console.log(chalk3.bold(`Storage: ${mb} MB`));
|
|
1085
|
+
}
|
|
1086
|
+
})
|
|
1087
|
+
);
|
|
1088
|
+
media.command("update <id>").description("Update a file").option("--filename <name>", "New filename").option("--folder <folder>", "Move to folder").option("--tags <tags>", "Comma-separated tags").action(
|
|
1089
|
+
withErrorHandler(
|
|
1090
|
+
globalOpts,
|
|
1091
|
+
async (id, flags) => {
|
|
1092
|
+
const opts = globalOpts();
|
|
1093
|
+
const { getToken } = await getStorageAuth(opts);
|
|
1094
|
+
const storage = createClient2(getToken);
|
|
1095
|
+
const file = await storage.updateFile({
|
|
1096
|
+
id,
|
|
1097
|
+
filename: flags.filename,
|
|
1098
|
+
folder: flags.folder,
|
|
1099
|
+
tags: flags.tags?.split(",").map((t) => t.trim())
|
|
1100
|
+
});
|
|
1101
|
+
if (opts.json || opts.jsonl) {
|
|
1102
|
+
formatOutput(file, opts);
|
|
1103
|
+
} else {
|
|
1104
|
+
success("Updated file");
|
|
915
1105
|
}
|
|
916
|
-
|
|
917
|
-
|
|
1106
|
+
}
|
|
1107
|
+
)
|
|
1108
|
+
);
|
|
1109
|
+
media.command("update-metadata <id>").description("Update file metadata (alt text, caption, description)").option("--alt-text <text>", "Alt text").option("--caption <text>", "Caption").option("--description <text>", "Description").action(
|
|
1110
|
+
withErrorHandler(
|
|
1111
|
+
globalOpts,
|
|
1112
|
+
async (id, flags) => {
|
|
1113
|
+
const opts = globalOpts();
|
|
1114
|
+
const { getToken } = await getStorageAuth(opts);
|
|
1115
|
+
const storage = createClient2(getToken);
|
|
1116
|
+
const file = await storage.updateFileMetadata({
|
|
1117
|
+
id,
|
|
1118
|
+
altText: flags.altText ?? flags["alt-text"],
|
|
1119
|
+
caption: flags.caption,
|
|
1120
|
+
description: flags.description
|
|
1121
|
+
});
|
|
1122
|
+
if (opts.json || opts.jsonl) {
|
|
1123
|
+
formatOutput(file, opts);
|
|
1124
|
+
} else {
|
|
1125
|
+
success("Updated file metadata");
|
|
918
1126
|
}
|
|
919
1127
|
}
|
|
1128
|
+
)
|
|
1129
|
+
);
|
|
1130
|
+
media.command("delete <id>").description("Delete a file").option("--confirm", "Skip confirmation prompt").option("--permanent", "Permanently delete (cannot be restored)").action(
|
|
1131
|
+
withErrorHandler(
|
|
1132
|
+
globalOpts,
|
|
1133
|
+
async (id, flags) => {
|
|
1134
|
+
const opts = globalOpts();
|
|
1135
|
+
const confirmed = await confirmAction("Delete this file?", {
|
|
1136
|
+
confirm: !!flags.confirm
|
|
1137
|
+
});
|
|
1138
|
+
if (!confirmed) {
|
|
1139
|
+
console.log("Aborted.");
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
const { getToken } = await getStorageAuth(opts);
|
|
1143
|
+
const storage = createClient2(getToken);
|
|
1144
|
+
if (flags.permanent) {
|
|
1145
|
+
await storage.permanentlyDeleteFile(id);
|
|
1146
|
+
success("Permanently deleted file");
|
|
1147
|
+
} else {
|
|
1148
|
+
await storage.deleteFile(id);
|
|
1149
|
+
success("Deleted file");
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
)
|
|
1153
|
+
);
|
|
1154
|
+
media.command("restore <id>").description("Restore a deleted file").action(
|
|
1155
|
+
withErrorHandler(globalOpts, async (id) => {
|
|
1156
|
+
const opts = globalOpts();
|
|
1157
|
+
const { getToken } = await getStorageAuth(opts);
|
|
1158
|
+
const storage = createClient2(getToken);
|
|
1159
|
+
const file = await storage.restoreFile(id);
|
|
1160
|
+
if (opts.json || opts.jsonl) {
|
|
1161
|
+
formatOutput(file, opts);
|
|
1162
|
+
} else {
|
|
1163
|
+
success("Restored file");
|
|
1164
|
+
}
|
|
920
1165
|
})
|
|
921
1166
|
);
|
|
922
1167
|
}
|
|
923
1168
|
|
|
924
1169
|
// src/commands/create-config.ts
|
|
925
1170
|
import chalk4 from "chalk";
|
|
926
|
-
import
|
|
1171
|
+
import inquirer3 from "inquirer";
|
|
927
1172
|
|
|
928
1173
|
// src/scaffold/scaffold.ts
|
|
929
1174
|
import * as fs4 from "fs";
|
|
@@ -1347,7 +1592,7 @@ function getApiTsconfig() {
|
|
|
1347
1592
|
return JSON.stringify(config2, null, 2) + "\n";
|
|
1348
1593
|
}
|
|
1349
1594
|
function getApiEnvExample(apiUrl) {
|
|
1350
|
-
const baseUrl = apiUrl.replace(
|
|
1595
|
+
const baseUrl = apiUrl.replace(/\/$/, "");
|
|
1351
1596
|
return `# Platform API
|
|
1352
1597
|
PLATFORM_BASE_URL=${baseUrl}
|
|
1353
1598
|
PLATFORM_API_KEY=sk_your_api_key_here
|
|
@@ -1425,11 +1670,7 @@ function isValidConfigType(value) {
|
|
|
1425
1670
|
return CONFIG_TYPES.includes(value);
|
|
1426
1671
|
}
|
|
1427
1672
|
function registerCreateConfigCommand(program2, globalOpts) {
|
|
1428
|
-
program2.command("create-config [name]").description("Scaffold a new Foir config").option("--type <type>", "Config type: custom-editor, workflow, widget").option(
|
|
1429
|
-
"--api-url <url>",
|
|
1430
|
-
"Platform API URL",
|
|
1431
|
-
"http://localhost:4000/graphql"
|
|
1432
|
-
).action(
|
|
1673
|
+
program2.command("create-config [name]").description("Scaffold a new Foir config").option("--type <type>", "Config type: custom-editor, workflow, widget").option("--api-url <url>", "Platform API URL", "http://localhost:4011").action(
|
|
1433
1674
|
withErrorHandler(
|
|
1434
1675
|
globalOpts,
|
|
1435
1676
|
async (name, cmdOpts) => {
|
|
@@ -1439,7 +1680,7 @@ function registerCreateConfigCommand(program2, globalOpts) {
|
|
|
1439
1680
|
console.log();
|
|
1440
1681
|
let configName = name;
|
|
1441
1682
|
if (!configName) {
|
|
1442
|
-
const { inputName } = await
|
|
1683
|
+
const { inputName } = await inquirer3.prompt([
|
|
1443
1684
|
{
|
|
1444
1685
|
type: "input",
|
|
1445
1686
|
name: "inputName",
|
|
@@ -1453,7 +1694,7 @@ function registerCreateConfigCommand(program2, globalOpts) {
|
|
|
1453
1694
|
if (cmdOpts?.type && isValidConfigType(cmdOpts.type)) {
|
|
1454
1695
|
configType = cmdOpts.type;
|
|
1455
1696
|
} else {
|
|
1456
|
-
const { selectedType } = await
|
|
1697
|
+
const { selectedType } = await inquirer3.prompt([
|
|
1457
1698
|
{
|
|
1458
1699
|
type: "list",
|
|
1459
1700
|
name: "selectedType",
|
|
@@ -1464,7 +1705,7 @@ function registerCreateConfigCommand(program2, globalOpts) {
|
|
|
1464
1705
|
]);
|
|
1465
1706
|
configType = selectedType;
|
|
1466
1707
|
}
|
|
1467
|
-
const apiUrl = cmdOpts?.apiUrl ?? "http://localhost:
|
|
1708
|
+
const apiUrl = cmdOpts?.apiUrl ?? "http://localhost:4011";
|
|
1468
1709
|
console.log();
|
|
1469
1710
|
console.log(
|
|
1470
1711
|
` Scaffolding ${chalk4.cyan(`"${configName}"`)} (${configType})...`
|
|
@@ -1476,224 +1717,53 @@ function registerCreateConfigCommand(program2, globalOpts) {
|
|
|
1476
1717
|
);
|
|
1477
1718
|
}
|
|
1478
1719
|
|
|
1479
|
-
// src/graphql/generated.ts
|
|
1480
|
-
var GlobalSearchDocument = {
|
|
1481
|
-
kind: "Document",
|
|
1482
|
-
definitions: [
|
|
1483
|
-
{
|
|
1484
|
-
kind: "OperationDefinition",
|
|
1485
|
-
operation: "query",
|
|
1486
|
-
name: { kind: "Name", value: "GlobalSearch" },
|
|
1487
|
-
variableDefinitions: [
|
|
1488
|
-
{
|
|
1489
|
-
kind: "VariableDefinition",
|
|
1490
|
-
variable: {
|
|
1491
|
-
kind: "Variable",
|
|
1492
|
-
name: { kind: "Name", value: "query" }
|
|
1493
|
-
},
|
|
1494
|
-
type: {
|
|
1495
|
-
kind: "NonNullType",
|
|
1496
|
-
type: {
|
|
1497
|
-
kind: "NamedType",
|
|
1498
|
-
name: { kind: "Name", value: "String" }
|
|
1499
|
-
}
|
|
1500
|
-
}
|
|
1501
|
-
},
|
|
1502
|
-
{
|
|
1503
|
-
kind: "VariableDefinition",
|
|
1504
|
-
variable: {
|
|
1505
|
-
kind: "Variable",
|
|
1506
|
-
name: { kind: "Name", value: "limit" }
|
|
1507
|
-
},
|
|
1508
|
-
type: { kind: "NamedType", name: { kind: "Name", value: "Int" } }
|
|
1509
|
-
},
|
|
1510
|
-
{
|
|
1511
|
-
kind: "VariableDefinition",
|
|
1512
|
-
variable: {
|
|
1513
|
-
kind: "Variable",
|
|
1514
|
-
name: { kind: "Name", value: "modelKeys" }
|
|
1515
|
-
},
|
|
1516
|
-
type: {
|
|
1517
|
-
kind: "ListType",
|
|
1518
|
-
type: {
|
|
1519
|
-
kind: "NonNullType",
|
|
1520
|
-
type: {
|
|
1521
|
-
kind: "NamedType",
|
|
1522
|
-
name: { kind: "Name", value: "String" }
|
|
1523
|
-
}
|
|
1524
|
-
}
|
|
1525
|
-
}
|
|
1526
|
-
},
|
|
1527
|
-
{
|
|
1528
|
-
kind: "VariableDefinition",
|
|
1529
|
-
variable: {
|
|
1530
|
-
kind: "Variable",
|
|
1531
|
-
name: { kind: "Name", value: "includeMedia" }
|
|
1532
|
-
},
|
|
1533
|
-
type: { kind: "NamedType", name: { kind: "Name", value: "Boolean" } }
|
|
1534
|
-
}
|
|
1535
|
-
],
|
|
1536
|
-
selectionSet: {
|
|
1537
|
-
kind: "SelectionSet",
|
|
1538
|
-
selections: [
|
|
1539
|
-
{
|
|
1540
|
-
kind: "Field",
|
|
1541
|
-
name: { kind: "Name", value: "globalSearch" },
|
|
1542
|
-
arguments: [
|
|
1543
|
-
{
|
|
1544
|
-
kind: "Argument",
|
|
1545
|
-
name: { kind: "Name", value: "query" },
|
|
1546
|
-
value: {
|
|
1547
|
-
kind: "Variable",
|
|
1548
|
-
name: { kind: "Name", value: "query" }
|
|
1549
|
-
}
|
|
1550
|
-
},
|
|
1551
|
-
{
|
|
1552
|
-
kind: "Argument",
|
|
1553
|
-
name: { kind: "Name", value: "limit" },
|
|
1554
|
-
value: {
|
|
1555
|
-
kind: "Variable",
|
|
1556
|
-
name: { kind: "Name", value: "limit" }
|
|
1557
|
-
}
|
|
1558
|
-
},
|
|
1559
|
-
{
|
|
1560
|
-
kind: "Argument",
|
|
1561
|
-
name: { kind: "Name", value: "modelKeys" },
|
|
1562
|
-
value: {
|
|
1563
|
-
kind: "Variable",
|
|
1564
|
-
name: { kind: "Name", value: "modelKeys" }
|
|
1565
|
-
}
|
|
1566
|
-
},
|
|
1567
|
-
{
|
|
1568
|
-
kind: "Argument",
|
|
1569
|
-
name: { kind: "Name", value: "includeMedia" },
|
|
1570
|
-
value: {
|
|
1571
|
-
kind: "Variable",
|
|
1572
|
-
name: { kind: "Name", value: "includeMedia" }
|
|
1573
|
-
}
|
|
1574
|
-
}
|
|
1575
|
-
],
|
|
1576
|
-
selectionSet: {
|
|
1577
|
-
kind: "SelectionSet",
|
|
1578
|
-
selections: [
|
|
1579
|
-
{
|
|
1580
|
-
kind: "Field",
|
|
1581
|
-
name: { kind: "Name", value: "records" },
|
|
1582
|
-
selectionSet: {
|
|
1583
|
-
kind: "SelectionSet",
|
|
1584
|
-
selections: [
|
|
1585
|
-
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
|
1586
|
-
{
|
|
1587
|
-
kind: "Field",
|
|
1588
|
-
name: { kind: "Name", value: "modelKey" }
|
|
1589
|
-
},
|
|
1590
|
-
{ kind: "Field", name: { kind: "Name", value: "title" } },
|
|
1591
|
-
{
|
|
1592
|
-
kind: "Field",
|
|
1593
|
-
name: { kind: "Name", value: "naturalKey" }
|
|
1594
|
-
},
|
|
1595
|
-
{
|
|
1596
|
-
kind: "Field",
|
|
1597
|
-
name: { kind: "Name", value: "subtitle" }
|
|
1598
|
-
},
|
|
1599
|
-
{
|
|
1600
|
-
kind: "Field",
|
|
1601
|
-
name: { kind: "Name", value: "updatedAt" }
|
|
1602
|
-
}
|
|
1603
|
-
]
|
|
1604
|
-
}
|
|
1605
|
-
},
|
|
1606
|
-
{
|
|
1607
|
-
kind: "Field",
|
|
1608
|
-
name: { kind: "Name", value: "media" },
|
|
1609
|
-
selectionSet: {
|
|
1610
|
-
kind: "SelectionSet",
|
|
1611
|
-
selections: [
|
|
1612
|
-
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
|
1613
|
-
{
|
|
1614
|
-
kind: "Field",
|
|
1615
|
-
name: { kind: "Name", value: "fileName" }
|
|
1616
|
-
},
|
|
1617
|
-
{
|
|
1618
|
-
kind: "Field",
|
|
1619
|
-
name: { kind: "Name", value: "altText" }
|
|
1620
|
-
},
|
|
1621
|
-
{
|
|
1622
|
-
kind: "Field",
|
|
1623
|
-
name: { kind: "Name", value: "fileUrl" }
|
|
1624
|
-
}
|
|
1625
|
-
]
|
|
1626
|
-
}
|
|
1627
|
-
}
|
|
1628
|
-
]
|
|
1629
|
-
}
|
|
1630
|
-
}
|
|
1631
|
-
]
|
|
1632
|
-
}
|
|
1633
|
-
}
|
|
1634
|
-
]
|
|
1635
|
-
};
|
|
1636
|
-
|
|
1637
1720
|
// src/commands/search.ts
|
|
1638
1721
|
function registerSearchCommands(program2, globalOpts) {
|
|
1639
|
-
program2.command("search <query>").description("Search across all records
|
|
1722
|
+
program2.command("search <query>").description("Search across all records").option(
|
|
1640
1723
|
"--models <keys>",
|
|
1641
1724
|
"Filter to specific model keys (comma-separated)"
|
|
1642
|
-
).option("--limit <n>", "Max results", "20").
|
|
1725
|
+
).option("--limit <n>", "Max results", "20").action(
|
|
1643
1726
|
withErrorHandler(
|
|
1644
1727
|
globalOpts,
|
|
1645
1728
|
async (query, cmdOpts) => {
|
|
1646
1729
|
const opts = globalOpts();
|
|
1647
|
-
const client = await
|
|
1730
|
+
const client = await createPlatformClient(opts);
|
|
1648
1731
|
const modelKeys = typeof cmdOpts.models === "string" ? cmdOpts.models.split(",").map((k) => k.trim()) : void 0;
|
|
1649
|
-
const
|
|
1732
|
+
const result = await client.records.globalSearch({
|
|
1650
1733
|
query,
|
|
1651
1734
|
limit: parseInt(String(cmdOpts.limit ?? "20"), 10),
|
|
1652
|
-
modelKeys
|
|
1653
|
-
includeMedia: cmdOpts.media !== false
|
|
1735
|
+
modelKeys
|
|
1654
1736
|
});
|
|
1655
1737
|
if (opts.json || opts.jsonl) {
|
|
1656
|
-
formatOutput(
|
|
1738
|
+
formatOutput(result, opts);
|
|
1657
1739
|
return;
|
|
1658
1740
|
}
|
|
1659
|
-
if (
|
|
1741
|
+
if (result.items.length > 0) {
|
|
1660
1742
|
console.log(`
|
|
1661
|
-
Records (${
|
|
1743
|
+
Records (${result.items.length}):`);
|
|
1662
1744
|
formatList(
|
|
1663
|
-
|
|
1745
|
+
result.items.map((item) => ({
|
|
1746
|
+
id: item.id,
|
|
1747
|
+
modelKey: item.modelKey,
|
|
1748
|
+
naturalKey: item.naturalKey ?? "",
|
|
1749
|
+
score: item.score
|
|
1750
|
+
})),
|
|
1664
1751
|
opts,
|
|
1665
1752
|
{
|
|
1666
1753
|
columns: [
|
|
1667
1754
|
{ key: "id", header: "ID", width: 28 },
|
|
1668
1755
|
{ key: "modelKey", header: "Model", width: 18 },
|
|
1669
|
-
{ key: "title", header: "Title", width: 28 },
|
|
1670
1756
|
{ key: "naturalKey", header: "Key", width: 20 },
|
|
1671
1757
|
{
|
|
1672
|
-
key: "
|
|
1673
|
-
header: "
|
|
1674
|
-
width:
|
|
1675
|
-
format: (v) =>
|
|
1758
|
+
key: "score",
|
|
1759
|
+
header: "Score",
|
|
1760
|
+
width: 8,
|
|
1761
|
+
format: (v) => Number(v).toFixed(2)
|
|
1676
1762
|
}
|
|
1677
1763
|
]
|
|
1678
1764
|
}
|
|
1679
1765
|
);
|
|
1680
|
-
}
|
|
1681
|
-
if (data.globalSearch.media.length > 0) {
|
|
1682
|
-
console.log(`
|
|
1683
|
-
Media (${data.globalSearch.media.length}):`);
|
|
1684
|
-
formatList(
|
|
1685
|
-
data.globalSearch.media,
|
|
1686
|
-
opts,
|
|
1687
|
-
{
|
|
1688
|
-
columns: [
|
|
1689
|
-
{ key: "id", header: "ID", width: 28 },
|
|
1690
|
-
{ key: "fileName", header: "File", width: 30 },
|
|
1691
|
-
{ key: "altText", header: "Alt", width: 24 }
|
|
1692
|
-
]
|
|
1693
|
-
}
|
|
1694
|
-
);
|
|
1695
|
-
}
|
|
1696
|
-
if (data.globalSearch.records.length === 0 && data.globalSearch.media.length === 0) {
|
|
1766
|
+
} else {
|
|
1697
1767
|
console.log("No results found.");
|
|
1698
1768
|
}
|
|
1699
1769
|
}
|
|
@@ -1704,9 +1774,9 @@ Media (${data.globalSearch.media.length}):`);
|
|
|
1704
1774
|
// src/commands/init.ts
|
|
1705
1775
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
1706
1776
|
import { writeFile } from "fs/promises";
|
|
1707
|
-
import { resolve as
|
|
1777
|
+
import { resolve as resolve3, join as join4 } from "path";
|
|
1708
1778
|
import chalk5 from "chalk";
|
|
1709
|
-
import
|
|
1779
|
+
import inquirer4 from "inquirer";
|
|
1710
1780
|
var FIELD_DEFAULTS = {
|
|
1711
1781
|
text: "",
|
|
1712
1782
|
richtext: "",
|
|
@@ -1768,9 +1838,9 @@ function generateRecordSeed(model) {
|
|
|
1768
1838
|
)) {
|
|
1769
1839
|
continue;
|
|
1770
1840
|
}
|
|
1771
|
-
if (field.type === "list" && field.
|
|
1841
|
+
if (field.type === "list" && field.itemType) {
|
|
1772
1842
|
data[field.key] = [
|
|
1773
|
-
defaultValueForField({ key: field.key, type: field.
|
|
1843
|
+
defaultValueForField({ key: field.key, type: field.itemType })
|
|
1774
1844
|
];
|
|
1775
1845
|
} else {
|
|
1776
1846
|
data[field.key] = defaultValueForField(field);
|
|
@@ -1795,7 +1865,7 @@ function registerInitCommands(program2, globalOpts) {
|
|
|
1795
1865
|
async (key, opts) => {
|
|
1796
1866
|
const globalFlags = globalOpts();
|
|
1797
1867
|
const template = generateModelTemplate(key);
|
|
1798
|
-
const outDir =
|
|
1868
|
+
const outDir = resolve3(opts.output);
|
|
1799
1869
|
if (!existsSync3(outDir)) {
|
|
1800
1870
|
mkdirSync2(outDir, { recursive: true });
|
|
1801
1871
|
}
|
|
@@ -1826,10 +1896,19 @@ Edit the file, then run:
|
|
|
1826
1896
|
globalOpts,
|
|
1827
1897
|
async (opts) => {
|
|
1828
1898
|
const globalFlags = globalOpts();
|
|
1829
|
-
const client = await
|
|
1830
|
-
const
|
|
1831
|
-
const
|
|
1832
|
-
|
|
1899
|
+
const client = await createPlatformClient(globalFlags);
|
|
1900
|
+
const result = await client.models.listModels({ limit: 100 });
|
|
1901
|
+
const models = result.items.map((m) => ({
|
|
1902
|
+
key: m.key,
|
|
1903
|
+
name: m.name,
|
|
1904
|
+
fields: (m.fields ?? []).map((f) => ({
|
|
1905
|
+
key: f.key,
|
|
1906
|
+
type: f.type,
|
|
1907
|
+
label: f.label,
|
|
1908
|
+
required: f.required,
|
|
1909
|
+
itemType: f.itemType
|
|
1910
|
+
}))
|
|
1911
|
+
}));
|
|
1833
1912
|
if (models.length === 0) {
|
|
1834
1913
|
console.log(
|
|
1835
1914
|
chalk5.yellow(
|
|
@@ -1854,7 +1933,7 @@ Edit the file, then run:
|
|
|
1854
1933
|
selectedModels.push(found);
|
|
1855
1934
|
}
|
|
1856
1935
|
} else {
|
|
1857
|
-
const { selected } = await
|
|
1936
|
+
const { selected } = await inquirer4.prompt([
|
|
1858
1937
|
{
|
|
1859
1938
|
type: "checkbox",
|
|
1860
1939
|
name: "selected",
|
|
@@ -1873,7 +1952,7 @@ Edit the file, then run:
|
|
|
1873
1952
|
console.log("No models selected.");
|
|
1874
1953
|
return;
|
|
1875
1954
|
}
|
|
1876
|
-
const outDir =
|
|
1955
|
+
const outDir = resolve3(opts.output);
|
|
1877
1956
|
if (!existsSync3(outDir)) {
|
|
1878
1957
|
mkdirSync2(outDir, { recursive: true });
|
|
1879
1958
|
}
|
|
@@ -1909,64 +1988,12 @@ Edit the files, then run:
|
|
|
1909
1988
|
import chalk6 from "chalk";
|
|
1910
1989
|
import { existsSync as existsSync4 } from "fs";
|
|
1911
1990
|
import { resolve as resolve4 } from "path";
|
|
1912
|
-
|
|
1913
|
-
// src/lib/config-loader.ts
|
|
1914
|
-
import { readFile } from "fs/promises";
|
|
1915
|
-
import { pathToFileURL } from "url";
|
|
1916
|
-
import { resolve as resolve3 } from "path";
|
|
1917
|
-
async function loadConfig(filePath) {
|
|
1918
|
-
const absPath = resolve3(filePath);
|
|
1919
|
-
if (filePath.endsWith(".ts")) {
|
|
1920
|
-
const configModule = await import(pathToFileURL(absPath).href);
|
|
1921
|
-
return configModule.default;
|
|
1922
|
-
}
|
|
1923
|
-
if (filePath.endsWith(".js") || filePath.endsWith(".mjs")) {
|
|
1924
|
-
const configModule = await import(pathToFileURL(absPath).href);
|
|
1925
|
-
return configModule.default;
|
|
1926
|
-
}
|
|
1927
|
-
if (filePath.endsWith(".json")) {
|
|
1928
|
-
const content = await readFile(absPath, "utf-8");
|
|
1929
|
-
return JSON.parse(content);
|
|
1930
|
-
}
|
|
1931
|
-
throw new Error(
|
|
1932
|
-
`Unsupported file extension for "${filePath}". Supported: .ts, .js, .mjs, .json`
|
|
1933
|
-
);
|
|
1934
|
-
}
|
|
1935
|
-
|
|
1936
|
-
// src/commands/push.ts
|
|
1937
1991
|
var CONFIG_FILE_NAMES = [
|
|
1938
1992
|
"foir.config.ts",
|
|
1939
1993
|
"foir.config.js",
|
|
1940
1994
|
"foir.config.mjs",
|
|
1941
1995
|
"foir.config.json"
|
|
1942
1996
|
];
|
|
1943
|
-
var APPLY_CONFIG_MUTATION = (
|
|
1944
|
-
/* GraphQL */
|
|
1945
|
-
`
|
|
1946
|
-
mutation ApplyConfig($input: ApplyConfigInput!) {
|
|
1947
|
-
applyConfig(input: $input) {
|
|
1948
|
-
configId
|
|
1949
|
-
configKey
|
|
1950
|
-
credentials {
|
|
1951
|
-
platformApiKey
|
|
1952
|
-
platformEditorKey
|
|
1953
|
-
webhookSecret
|
|
1954
|
-
}
|
|
1955
|
-
modelsCreated
|
|
1956
|
-
modelsUpdated
|
|
1957
|
-
operationsCreated
|
|
1958
|
-
operationsUpdated
|
|
1959
|
-
segmentsCreated
|
|
1960
|
-
segmentsUpdated
|
|
1961
|
-
schedulesCreated
|
|
1962
|
-
schedulesUpdated
|
|
1963
|
-
hooksCreated
|
|
1964
|
-
hooksUpdated
|
|
1965
|
-
isUpdate
|
|
1966
|
-
}
|
|
1967
|
-
}
|
|
1968
|
-
`
|
|
1969
|
-
);
|
|
1970
1997
|
function discoverConfigFile() {
|
|
1971
1998
|
for (const name of CONFIG_FILE_NAMES) {
|
|
1972
1999
|
const path3 = resolve4(process.cwd(), name);
|
|
@@ -1991,69 +2018,32 @@ function registerPushCommand(program2, globalOpts) {
|
|
|
1991
2018
|
console.log(chalk6.dim(`Loading ${configPath}...`));
|
|
1992
2019
|
const config2 = await loadConfig(configPath);
|
|
1993
2020
|
if (!config2?.key || !config2?.name) {
|
|
1994
|
-
throw new Error(
|
|
1995
|
-
'Config must have at least "key" and "name" fields.'
|
|
1996
|
-
);
|
|
1997
|
-
}
|
|
1998
|
-
if (opts.force) {
|
|
1999
|
-
config2.force = true;
|
|
2000
|
-
}
|
|
2001
|
-
const client = await createClient(globalOpts());
|
|
2002
|
-
console.log(
|
|
2003
|
-
chalk6.dim(`Pushing config "${config2.key}" to platform...`)
|
|
2004
|
-
);
|
|
2005
|
-
const data = await client.request(
|
|
2006
|
-
APPLY_CONFIG_MUTATION,
|
|
2007
|
-
{ input: config2 }
|
|
2008
|
-
);
|
|
2009
|
-
const result = data.applyConfig;
|
|
2010
|
-
console.log();
|
|
2011
|
-
if (result.isUpdate) {
|
|
2012
|
-
console.log(chalk6.green("Config updated successfully."));
|
|
2013
|
-
} else {
|
|
2014
|
-
console.log(chalk6.green("Config applied successfully."));
|
|
2015
|
-
}
|
|
2016
|
-
console.log();
|
|
2017
|
-
console.log(` Config ID: ${chalk6.cyan(result.configId)}`);
|
|
2018
|
-
console.log(` Config Key: ${chalk6.cyan(result.configKey)}`);
|
|
2019
|
-
console.log();
|
|
2020
|
-
const stats = [
|
|
2021
|
-
["Models", result.modelsCreated, result.modelsUpdated],
|
|
2022
|
-
["Operations", result.operationsCreated, result.operationsUpdated],
|
|
2023
|
-
["Segments", result.segmentsCreated, result.segmentsUpdated],
|
|
2024
|
-
["Schedules", result.schedulesCreated, result.schedulesUpdated],
|
|
2025
|
-
["Hooks", result.hooksCreated, result.hooksUpdated]
|
|
2026
|
-
].filter(([, c, u]) => c > 0 || u > 0);
|
|
2027
|
-
if (stats.length > 0) {
|
|
2028
|
-
for (const [label, created, updated] of stats) {
|
|
2029
|
-
const parts = [];
|
|
2030
|
-
if (created > 0)
|
|
2031
|
-
parts.push(chalk6.green(`${created} created`));
|
|
2032
|
-
if (updated > 0)
|
|
2033
|
-
parts.push(chalk6.yellow(`${updated} updated`));
|
|
2034
|
-
console.log(` ${label}: ${parts.join(", ")}`);
|
|
2035
|
-
}
|
|
2036
|
-
console.log();
|
|
2037
|
-
}
|
|
2038
|
-
if (result.credentials) {
|
|
2039
|
-
console.log(chalk6.bold.yellow("Credentials (save these now):"));
|
|
2040
|
-
console.log();
|
|
2041
|
-
console.log(
|
|
2042
|
-
` PLATFORM_API_KEY: ${chalk6.cyan(result.credentials.platformApiKey)}`
|
|
2043
|
-
);
|
|
2044
|
-
console.log(
|
|
2045
|
-
` PLATFORM_EDITOR_KEY: ${chalk6.cyan(result.credentials.platformEditorKey)}`
|
|
2046
|
-
);
|
|
2047
|
-
console.log(
|
|
2048
|
-
` WEBHOOK_SECRET: ${chalk6.cyan(result.credentials.webhookSecret)}`
|
|
2021
|
+
throw new Error(
|
|
2022
|
+
'Config must have at least "key" and "name" fields.'
|
|
2049
2023
|
);
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2024
|
+
}
|
|
2025
|
+
if (opts.force) {
|
|
2026
|
+
config2.force = true;
|
|
2027
|
+
}
|
|
2028
|
+
const client = await createPlatformClient(globalOpts());
|
|
2029
|
+
console.log(
|
|
2030
|
+
chalk6.dim(`Pushing config "${config2.key}" to platform...`)
|
|
2031
|
+
);
|
|
2032
|
+
const result = await client.configs.applyConfig(
|
|
2033
|
+
config2.key,
|
|
2034
|
+
config2
|
|
2035
|
+
);
|
|
2036
|
+
if (!result) {
|
|
2037
|
+
throw new Error(
|
|
2038
|
+
"Failed to apply config \u2014 no response from server."
|
|
2055
2039
|
);
|
|
2056
2040
|
}
|
|
2041
|
+
console.log();
|
|
2042
|
+
console.log(chalk6.green("Config applied successfully."));
|
|
2043
|
+
console.log();
|
|
2044
|
+
console.log(` Config ID: ${chalk6.cyan(result.id)}`);
|
|
2045
|
+
console.log(` Config Key: ${chalk6.cyan(result.key)}`);
|
|
2046
|
+
console.log();
|
|
2057
2047
|
}
|
|
2058
2048
|
)
|
|
2059
2049
|
);
|
|
@@ -2061,40 +2051,19 @@ function registerPushCommand(program2, globalOpts) {
|
|
|
2061
2051
|
|
|
2062
2052
|
// src/commands/remove.ts
|
|
2063
2053
|
import chalk7 from "chalk";
|
|
2064
|
-
import
|
|
2065
|
-
var GET_CONFIG_QUERY = (
|
|
2066
|
-
/* GraphQL */
|
|
2067
|
-
`
|
|
2068
|
-
query GetConfigByKey($key: String!) {
|
|
2069
|
-
configByKey(key: $key) {
|
|
2070
|
-
id
|
|
2071
|
-
key
|
|
2072
|
-
name
|
|
2073
|
-
configType
|
|
2074
|
-
}
|
|
2075
|
-
}
|
|
2076
|
-
`
|
|
2077
|
-
);
|
|
2078
|
-
var UNREGISTER_MUTATION = (
|
|
2079
|
-
/* GraphQL */
|
|
2080
|
-
`
|
|
2081
|
-
mutation UnregisterConfig($id: ID!) {
|
|
2082
|
-
unregisterConfig(id: $id)
|
|
2083
|
-
}
|
|
2084
|
-
`
|
|
2085
|
-
);
|
|
2054
|
+
import inquirer5 from "inquirer";
|
|
2086
2055
|
function registerRemoveCommand(program2, globalOpts) {
|
|
2087
2056
|
program2.command("remove <key>").description("Remove a config and all its provisioned resources").option("--force", "Skip confirmation prompt", false).action(
|
|
2088
2057
|
withErrorHandler(
|
|
2089
2058
|
globalOpts,
|
|
2090
2059
|
async (key, opts) => {
|
|
2091
|
-
const client = await
|
|
2092
|
-
const
|
|
2060
|
+
const client = await createPlatformClient(globalOpts());
|
|
2061
|
+
const config2 = await client.configs.getConfigByKey(key);
|
|
2093
2062
|
if (!config2) {
|
|
2094
2063
|
throw new Error(`Config not found: ${key}`);
|
|
2095
2064
|
}
|
|
2096
2065
|
if (!opts.force) {
|
|
2097
|
-
const { confirmed } = await
|
|
2066
|
+
const { confirmed } = await inquirer5.prompt([
|
|
2098
2067
|
{
|
|
2099
2068
|
type: "confirm",
|
|
2100
2069
|
name: "confirmed",
|
|
@@ -2107,7 +2076,7 @@ function registerRemoveCommand(program2, globalOpts) {
|
|
|
2107
2076
|
return;
|
|
2108
2077
|
}
|
|
2109
2078
|
}
|
|
2110
|
-
await client.
|
|
2079
|
+
await client.configs.deleteConfig(config2.id);
|
|
2111
2080
|
console.log(
|
|
2112
2081
|
chalk7.green(`Removed config "${config2.name}" (${config2.key}).`)
|
|
2113
2082
|
);
|
|
@@ -2118,49 +2087,6 @@ function registerRemoveCommand(program2, globalOpts) {
|
|
|
2118
2087
|
|
|
2119
2088
|
// src/commands/profiles.ts
|
|
2120
2089
|
import chalk8 from "chalk";
|
|
2121
|
-
|
|
2122
|
-
// src/lib/input.ts
|
|
2123
|
-
import inquirer5 from "inquirer";
|
|
2124
|
-
async function parseInputData(opts) {
|
|
2125
|
-
if (opts.data) {
|
|
2126
|
-
return JSON.parse(opts.data);
|
|
2127
|
-
}
|
|
2128
|
-
if (opts.file) {
|
|
2129
|
-
return await loadConfig(opts.file);
|
|
2130
|
-
}
|
|
2131
|
-
if (!process.stdin.isTTY) {
|
|
2132
|
-
const chunks = [];
|
|
2133
|
-
for await (const chunk of process.stdin) {
|
|
2134
|
-
chunks.push(chunk);
|
|
2135
|
-
}
|
|
2136
|
-
const stdinContent = Buffer.concat(chunks).toString("utf-8").trim();
|
|
2137
|
-
if (stdinContent) {
|
|
2138
|
-
return JSON.parse(stdinContent);
|
|
2139
|
-
}
|
|
2140
|
-
}
|
|
2141
|
-
throw new Error(
|
|
2142
|
-
"No input data provided. Use --data, --file, or pipe via stdin."
|
|
2143
|
-
);
|
|
2144
|
-
}
|
|
2145
|
-
function isUUID(value) {
|
|
2146
|
-
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(
|
|
2147
|
-
value
|
|
2148
|
-
) || /^c[a-z0-9]{24,}$/.test(value);
|
|
2149
|
-
}
|
|
2150
|
-
async function confirmAction(message, opts) {
|
|
2151
|
-
if (opts?.confirm) return true;
|
|
2152
|
-
const { confirmed } = await inquirer5.prompt([
|
|
2153
|
-
{
|
|
2154
|
-
type: "confirm",
|
|
2155
|
-
name: "confirmed",
|
|
2156
|
-
message,
|
|
2157
|
-
default: false
|
|
2158
|
-
}
|
|
2159
|
-
]);
|
|
2160
|
-
return confirmed;
|
|
2161
|
-
}
|
|
2162
|
-
|
|
2163
|
-
// src/commands/profiles.ts
|
|
2164
2090
|
function registerProfilesCommand(program2, globalOpts) {
|
|
2165
2091
|
const profiles = program2.command("profiles").description("Manage named project profiles");
|
|
2166
2092
|
profiles.command("list").description("List all saved project profiles").action(
|
|
@@ -2328,9 +2254,8 @@ function registerProfilesCommand(program2, globalOpts) {
|
|
|
2328
2254
|
}
|
|
2329
2255
|
|
|
2330
2256
|
// src/commands/register-commands.ts
|
|
2331
|
-
import {
|
|
2332
|
-
import { resolve as resolve5
|
|
2333
|
-
import { fileURLToPath } from "url";
|
|
2257
|
+
import { readdirSync } from "fs";
|
|
2258
|
+
import { resolve as resolve5 } from "path";
|
|
2334
2259
|
import chalk9 from "chalk";
|
|
2335
2260
|
|
|
2336
2261
|
// ../command-registry/src/command-map.ts
|
|
@@ -3108,68 +3033,6 @@ var COMMANDS = [
|
|
|
3108
3033
|
successMessage: "Updated customer profile"
|
|
3109
3034
|
},
|
|
3110
3035
|
// =========================================================================
|
|
3111
|
-
// FILES
|
|
3112
|
-
// =========================================================================
|
|
3113
|
-
{
|
|
3114
|
-
group: "files",
|
|
3115
|
-
name: "list",
|
|
3116
|
-
description: "List files",
|
|
3117
|
-
operation: "files",
|
|
3118
|
-
operationType: "query",
|
|
3119
|
-
columns: [
|
|
3120
|
-
{ key: "id", header: "ID", width: 28 },
|
|
3121
|
-
{ key: "filename", header: "Filename", width: 24 },
|
|
3122
|
-
{ key: "mimeType", header: "Type", width: 16 },
|
|
3123
|
-
{ key: "size", header: "Size", width: 10, format: "bytes" },
|
|
3124
|
-
{ key: "folder", header: "Folder", width: 16 },
|
|
3125
|
-
{ key: "createdAt", header: "Created", width: 12, format: "timeAgo" }
|
|
3126
|
-
]
|
|
3127
|
-
},
|
|
3128
|
-
{
|
|
3129
|
-
group: "files",
|
|
3130
|
-
name: "get",
|
|
3131
|
-
description: "Get a file",
|
|
3132
|
-
operation: "file",
|
|
3133
|
-
operationType: "query",
|
|
3134
|
-
positionalArgs: [{ name: "id", graphqlArg: "id" }]
|
|
3135
|
-
},
|
|
3136
|
-
{
|
|
3137
|
-
group: "files",
|
|
3138
|
-
name: "usage",
|
|
3139
|
-
description: "Get file storage usage",
|
|
3140
|
-
operation: "fileStorageUsage",
|
|
3141
|
-
operationType: "query"
|
|
3142
|
-
},
|
|
3143
|
-
{
|
|
3144
|
-
group: "files",
|
|
3145
|
-
name: "update",
|
|
3146
|
-
description: "Update a file",
|
|
3147
|
-
operation: "updateFile",
|
|
3148
|
-
operationType: "mutation",
|
|
3149
|
-
positionalArgs: [{ name: "id", graphqlArg: "id" }],
|
|
3150
|
-
successMessage: "Updated file"
|
|
3151
|
-
},
|
|
3152
|
-
{
|
|
3153
|
-
group: "files",
|
|
3154
|
-
name: "update-metadata",
|
|
3155
|
-
description: "Update file metadata",
|
|
3156
|
-
operation: "updateFileMetadata",
|
|
3157
|
-
operationType: "mutation",
|
|
3158
|
-
positionalArgs: [{ name: "id", graphqlArg: "id" }],
|
|
3159
|
-
successMessage: "Updated file metadata"
|
|
3160
|
-
},
|
|
3161
|
-
{
|
|
3162
|
-
group: "files",
|
|
3163
|
-
name: "delete",
|
|
3164
|
-
description: "Delete a file",
|
|
3165
|
-
operation: "deleteFile",
|
|
3166
|
-
operationType: "mutation",
|
|
3167
|
-
positionalArgs: [{ name: "id", graphqlArg: "id" }],
|
|
3168
|
-
requiresConfirmation: true,
|
|
3169
|
-
scalarResult: true,
|
|
3170
|
-
successMessage: "Deleted file"
|
|
3171
|
-
},
|
|
3172
|
-
// =========================================================================
|
|
3173
3036
|
// OPERATIONS
|
|
3174
3037
|
// =========================================================================
|
|
3175
3038
|
{
|
|
@@ -3737,230 +3600,397 @@ var COMMANDS = [
|
|
|
3737
3600
|
}
|
|
3738
3601
|
];
|
|
3739
3602
|
|
|
3740
|
-
//
|
|
3741
|
-
import {
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
}
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
}
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3603
|
+
// src/commands/register-commands.ts
|
|
3604
|
+
import { RecordType } from "@foir/connect-clients/records";
|
|
3605
|
+
function buildDispatchTable() {
|
|
3606
|
+
return {
|
|
3607
|
+
// ── Models ──────────────────────────────────────────────────
|
|
3608
|
+
models: {
|
|
3609
|
+
models: async (_v, c) => wrapList(
|
|
3610
|
+
await c.models.listModels({
|
|
3611
|
+
limit: num(_v.limit, 50),
|
|
3612
|
+
search: str(_v.search),
|
|
3613
|
+
category: str(_v.category),
|
|
3614
|
+
offset: num(_v.offset, 0)
|
|
3615
|
+
})
|
|
3616
|
+
),
|
|
3617
|
+
modelByKey: async (v, c) => await c.models.getModelByKey(str(v.key)),
|
|
3618
|
+
model: async (v, c) => await c.models.getModel(str(v.id)),
|
|
3619
|
+
createModel: async (v, c) => {
|
|
3620
|
+
const input = v.input;
|
|
3621
|
+
if (!input) throw new Error("Input required (--data or --file)");
|
|
3622
|
+
return await c.models.createModel({
|
|
3623
|
+
key: str(input.key),
|
|
3624
|
+
name: str(input.name),
|
|
3625
|
+
fields: input.fields ?? [],
|
|
3626
|
+
config: input.config,
|
|
3627
|
+
configId: str(input.configId)
|
|
3628
|
+
});
|
|
3629
|
+
},
|
|
3630
|
+
updateModel: async (v, c) => {
|
|
3631
|
+
const input = v.input;
|
|
3632
|
+
if (!input) throw new Error("Input required (--data or --file)");
|
|
3633
|
+
return await c.models.updateModel({
|
|
3634
|
+
id: str(input.id ?? v.id),
|
|
3635
|
+
name: str(input.name),
|
|
3636
|
+
fields: input.fields,
|
|
3637
|
+
config: input.config,
|
|
3638
|
+
changeDescription: str(input.changeDescription)
|
|
3639
|
+
});
|
|
3640
|
+
},
|
|
3641
|
+
deleteModel: async (v, c) => await c.models.deleteModel(str(v.id)),
|
|
3642
|
+
modelVersions: async (v, c) => wrapList(
|
|
3643
|
+
await c.models.listModelVersions(str(v.modelId), {
|
|
3644
|
+
limit: num(v.limit, 50)
|
|
3645
|
+
})
|
|
3646
|
+
)
|
|
3647
|
+
},
|
|
3648
|
+
// ── Records ─────────────────────────────────────────────────
|
|
3649
|
+
records: {
|
|
3650
|
+
records: async (v, c) => wrapList(
|
|
3651
|
+
await c.records.listRecords({
|
|
3652
|
+
modelKey: str(v.modelKey),
|
|
3653
|
+
limit: num(v.limit, 50),
|
|
3654
|
+
offset: num(v.offset, 0),
|
|
3655
|
+
search: str(v.search)
|
|
3656
|
+
})
|
|
3657
|
+
),
|
|
3658
|
+
record: async (v, c) => await c.records.getRecord(str(v.id)),
|
|
3659
|
+
recordByKey: async (v, c) => await c.records.getRecordByKey(str(v.modelKey), str(v.naturalKey)),
|
|
3660
|
+
createRecord: async (v, c) => {
|
|
3661
|
+
const input = v.input;
|
|
3662
|
+
if (!input) throw new Error("Input required (--data or --file)");
|
|
3663
|
+
return await c.records.createRecord({
|
|
3664
|
+
modelKey: str(input.modelKey),
|
|
3665
|
+
naturalKey: str(input.naturalKey),
|
|
3666
|
+
data: input.data,
|
|
3667
|
+
customerId: str(input.customerId),
|
|
3668
|
+
changeDescription: str(input.changeDescription)
|
|
3669
|
+
});
|
|
3670
|
+
},
|
|
3671
|
+
updateRecord: async (v, c) => {
|
|
3672
|
+
const input = v.input;
|
|
3673
|
+
if (!input) throw new Error("Input required (--data or --file)");
|
|
3674
|
+
return await c.records.updateRecord({
|
|
3675
|
+
id: str(input.id ?? v.id),
|
|
3676
|
+
data: input.data ?? input,
|
|
3677
|
+
naturalKey: str(input.naturalKey)
|
|
3678
|
+
});
|
|
3679
|
+
},
|
|
3680
|
+
deleteRecord: async (v, c) => await c.records.deleteRecord(str(v.id)),
|
|
3681
|
+
publishVersion: async (v, c) => await c.records.publishVersion(str(v.versionId)),
|
|
3682
|
+
unpublishRecord: async (v, c) => await c.records.unpublishRecord(str(v.id)),
|
|
3683
|
+
duplicateRecord: async (v, c) => {
|
|
3684
|
+
const input = v.input;
|
|
3685
|
+
return await c.records.duplicateRecord(
|
|
3686
|
+
str(input?.id ?? v.id),
|
|
3687
|
+
str(input?.newNaturalKey)
|
|
3688
|
+
);
|
|
3689
|
+
},
|
|
3690
|
+
recordVersions: async (v, c) => wrapList(
|
|
3691
|
+
await c.records.listRecordVersions(str(v.parentId), {
|
|
3692
|
+
limit: num(v.limit, 50)
|
|
3693
|
+
})
|
|
3694
|
+
),
|
|
3695
|
+
recordVariants: async (v, c) => wrapList(
|
|
3696
|
+
await c.records.listRecordVariants(str(v.recordId), {
|
|
3697
|
+
limit: num(v.limit, 50)
|
|
3698
|
+
})
|
|
3699
|
+
),
|
|
3700
|
+
createVersion: async (v, c) => {
|
|
3701
|
+
const input = v.input;
|
|
3702
|
+
if (!input) throw new Error("Input required (--data or --file)");
|
|
3703
|
+
return await c.records.createVersion(
|
|
3704
|
+
str(input.parentId),
|
|
3705
|
+
input.data,
|
|
3706
|
+
str(input.changeDescription)
|
|
3707
|
+
);
|
|
3708
|
+
},
|
|
3709
|
+
createVariant: async (v, c) => {
|
|
3710
|
+
const input = v.input;
|
|
3711
|
+
if (!input) throw new Error("Input required (--data or --file)");
|
|
3712
|
+
return await c.records.createVariant(
|
|
3713
|
+
str(input.recordId),
|
|
3714
|
+
str(input.variantKey),
|
|
3715
|
+
input.data
|
|
3716
|
+
);
|
|
3838
3717
|
}
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3718
|
+
},
|
|
3719
|
+
// ── Locales ─────────────────────────────────────────────────
|
|
3720
|
+
locales: {
|
|
3721
|
+
locales: async (v, c) => {
|
|
3722
|
+
const resp = await c.settings.listLocales({ limit: num(v.limit, 50) });
|
|
3723
|
+
return { items: resp.locales ?? [], total: resp.total ?? 0 };
|
|
3724
|
+
},
|
|
3725
|
+
locale: async (v, c) => await c.settings.getLocale(str(v.id)),
|
|
3726
|
+
createLocale: async (v, c) => {
|
|
3727
|
+
const input = v.input;
|
|
3728
|
+
if (!input) throw new Error("Input required");
|
|
3729
|
+
return await c.settings.createLocale({
|
|
3730
|
+
locale: str(input.locale),
|
|
3731
|
+
displayName: str(input.displayName),
|
|
3732
|
+
nativeName: str(input.nativeName),
|
|
3733
|
+
isDefault: input.isDefault,
|
|
3734
|
+
isRtl: input.isRtl,
|
|
3735
|
+
fallbackLocale: str(input.fallbackLocale)
|
|
3736
|
+
});
|
|
3737
|
+
},
|
|
3738
|
+
updateLocale: async (v, c) => {
|
|
3739
|
+
const input = v.input;
|
|
3740
|
+
if (!input) throw new Error("Input required");
|
|
3741
|
+
return await c.settings.updateLocale({
|
|
3742
|
+
id: str(input.id ?? v.id),
|
|
3743
|
+
displayName: str(input.displayName),
|
|
3744
|
+
nativeName: str(input.nativeName),
|
|
3745
|
+
isDefault: input.isDefault,
|
|
3746
|
+
isActive: input.isActive,
|
|
3747
|
+
isRtl: input.isRtl,
|
|
3748
|
+
fallbackLocale: str(input.fallbackLocale)
|
|
3749
|
+
});
|
|
3750
|
+
},
|
|
3751
|
+
deleteLocale: async (v, c) => await c.settings.deleteLocale(str(v.id))
|
|
3752
|
+
},
|
|
3753
|
+
// ── Segments ────────────────────────────────────────────────
|
|
3754
|
+
segments: {
|
|
3755
|
+
segments: async (v, c) => {
|
|
3756
|
+
const resp = await c.segments.listSegments({ limit: num(v.limit, 50) });
|
|
3757
|
+
return { items: resp.segments ?? [], total: resp.total ?? 0 };
|
|
3758
|
+
},
|
|
3759
|
+
segment: async (v, c) => await c.segments.getSegment(str(v.id)),
|
|
3760
|
+
segmentByKey: async (v, c) => await c.segments.getSegmentByKey(str(v.key)),
|
|
3761
|
+
createSegment: async (v, c) => {
|
|
3762
|
+
const input = v.input;
|
|
3763
|
+
if (!input) throw new Error("Input required");
|
|
3764
|
+
return await c.segments.createSegment({
|
|
3765
|
+
key: str(input.key),
|
|
3766
|
+
name: str(input.name),
|
|
3767
|
+
description: str(input.description),
|
|
3768
|
+
rules: input.rules,
|
|
3769
|
+
evaluationMode: str(input.evaluationMode),
|
|
3770
|
+
isActive: input.isActive
|
|
3771
|
+
});
|
|
3772
|
+
},
|
|
3773
|
+
updateSegment: async (v, c) => {
|
|
3774
|
+
const input = v.input;
|
|
3775
|
+
if (!input) throw new Error("Input required");
|
|
3776
|
+
return await c.segments.updateSegment({
|
|
3777
|
+
id: str(input.id ?? v.id),
|
|
3778
|
+
name: str(input.name),
|
|
3779
|
+
description: str(input.description),
|
|
3780
|
+
rules: input.rules,
|
|
3781
|
+
evaluationMode: str(input.evaluationMode),
|
|
3782
|
+
isActive: input.isActive
|
|
3783
|
+
});
|
|
3784
|
+
},
|
|
3785
|
+
deleteSegment: async (v, c) => await c.segments.deleteSegment(str(v.id)),
|
|
3786
|
+
previewSegmentRules: async (v, c) => {
|
|
3787
|
+
const input = v.rules;
|
|
3788
|
+
return await c.segments.previewSegmentRules(input);
|
|
3789
|
+
},
|
|
3790
|
+
testSegmentEvaluation: async (v, c) => await c.segments.testSegmentEvaluation(
|
|
3791
|
+
str(v.segmentId),
|
|
3792
|
+
str(v.customerId)
|
|
3793
|
+
)
|
|
3794
|
+
},
|
|
3795
|
+
// ── Experiments ─────────────────────────────────────────────
|
|
3796
|
+
experiments: {
|
|
3797
|
+
experiments: async (v, c) => {
|
|
3798
|
+
const resp = await c.experiments.listExperiments({
|
|
3799
|
+
limit: num(v.limit, 50)
|
|
3800
|
+
});
|
|
3801
|
+
return { items: resp.experiments ?? [], total: resp.total ?? 0 };
|
|
3802
|
+
},
|
|
3803
|
+
experiment: async (v, c) => await c.experiments.getExperiment(str(v.id)),
|
|
3804
|
+
experimentByKey: async (v, c) => await c.experiments.getExperimentByKey(str(v.key)),
|
|
3805
|
+
createExperiment: async (v, c) => {
|
|
3806
|
+
const input = v.input;
|
|
3807
|
+
if (!input) throw new Error("Input required");
|
|
3808
|
+
return await c.experiments.createExperiment({
|
|
3809
|
+
key: str(input.key),
|
|
3810
|
+
name: str(input.name),
|
|
3811
|
+
description: str(input.description),
|
|
3812
|
+
targeting: input.targeting,
|
|
3813
|
+
controlPercent: input.controlPercent,
|
|
3814
|
+
variants: input.variants
|
|
3815
|
+
});
|
|
3816
|
+
},
|
|
3817
|
+
updateExperiment: async (v, c) => {
|
|
3818
|
+
const input = v.input;
|
|
3819
|
+
if (!input) throw new Error("Input required");
|
|
3820
|
+
return await c.experiments.updateExperiment({
|
|
3821
|
+
id: str(input.id ?? v.id),
|
|
3822
|
+
name: str(input.name),
|
|
3823
|
+
description: str(input.description),
|
|
3824
|
+
targeting: input.targeting,
|
|
3825
|
+
controlPercent: input.controlPercent,
|
|
3826
|
+
variants: input.variants
|
|
3827
|
+
});
|
|
3828
|
+
},
|
|
3829
|
+
deleteExperiment: async (v, c) => await c.experiments.deleteExperiment(str(v.id)),
|
|
3830
|
+
startExperiment: async (v, c) => await c.experiments.startExperiment(str(v.experimentId)),
|
|
3831
|
+
pauseExperiment: async (v, c) => await c.experiments.pauseExperiment(str(v.experimentId)),
|
|
3832
|
+
resumeExperiment: async (v, c) => await c.experiments.resumeExperiment(str(v.experimentId)),
|
|
3833
|
+
endExperiment: async (v, c) => await c.experiments.endExperiment(str(v.experimentId)),
|
|
3834
|
+
experimentStats: async (v, c) => await c.experiments.getExperimentStats(str(v.experimentId))
|
|
3835
|
+
},
|
|
3836
|
+
// ── Settings ────────────────────────────────────────────────
|
|
3837
|
+
settings: {
|
|
3838
|
+
allSettings: async (v, c) => await c.settings.getSettings({
|
|
3839
|
+
category: str(v.category),
|
|
3840
|
+
key: str(v.key)
|
|
3841
|
+
}),
|
|
3842
|
+
setting: async (v, c) => {
|
|
3843
|
+
const settings = await c.settings.getSettings({ key: str(v.key) });
|
|
3844
|
+
return settings[0] ?? null;
|
|
3845
|
+
},
|
|
3846
|
+
setSetting: async (v, c) => {
|
|
3847
|
+
const input = v.input;
|
|
3848
|
+
if (!input) throw new Error("Input required");
|
|
3849
|
+
return await c.settings.updateSetting({
|
|
3850
|
+
key: str(input.key),
|
|
3851
|
+
value: input.value ?? input
|
|
3852
|
+
});
|
|
3859
3853
|
}
|
|
3860
|
-
}
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3854
|
+
},
|
|
3855
|
+
// ── API Keys ────────────────────────────────────────────────
|
|
3856
|
+
"api-keys": {
|
|
3857
|
+
listApiKeys: async (v, c) => wrapList(await c.identity.listApiKeys({ limit: num(v.limit, 50) })),
|
|
3858
|
+
createApiKey: async (v, c) => {
|
|
3859
|
+
const input = v.input;
|
|
3860
|
+
if (!input) throw new Error("Input required");
|
|
3861
|
+
return await c.identity.createApiKey({
|
|
3862
|
+
name: str(input.name),
|
|
3863
|
+
keyType: input.keyType,
|
|
3864
|
+
rateLimitPerHour: input.rateLimitPerHour
|
|
3865
|
+
});
|
|
3866
|
+
},
|
|
3867
|
+
rotateApiKey: async (v, c) => await c.identity.rotateApiKey(str(v.id)),
|
|
3868
|
+
revokeApiKey: async (v, c) => await c.identity.revokeApiKey(str(v.id))
|
|
3869
|
+
},
|
|
3870
|
+
// ── Auth Providers ──────────────────────────────────────────
|
|
3871
|
+
"auth-providers": {
|
|
3872
|
+
customerAuthProviders: async (v, c) => {
|
|
3873
|
+
const resp = await c.identity.listAuthProviders({
|
|
3874
|
+
limit: num(v.limit, 50)
|
|
3875
|
+
});
|
|
3876
|
+
return { items: resp.items, total: resp.total };
|
|
3877
|
+
},
|
|
3878
|
+
customerAuthProvider: async (v, c) => await c.identity.getAuthProvider(str(v.id)),
|
|
3879
|
+
createCustomerAuthProvider: async (v, c) => {
|
|
3880
|
+
const input = v.input;
|
|
3881
|
+
if (!input) throw new Error("Input required");
|
|
3882
|
+
return await c.identity.createAuthProvider({
|
|
3883
|
+
key: str(input.key),
|
|
3884
|
+
name: str(input.name),
|
|
3885
|
+
type: str(input.type),
|
|
3886
|
+
config: input.config,
|
|
3887
|
+
enabled: input.enabled,
|
|
3888
|
+
isDefault: input.isDefault,
|
|
3889
|
+
priority: input.priority
|
|
3890
|
+
});
|
|
3891
|
+
},
|
|
3892
|
+
updateCustomerAuthProvider: async (v, c) => {
|
|
3893
|
+
const input = v.input;
|
|
3894
|
+
if (!input) throw new Error("Input required");
|
|
3895
|
+
return await c.identity.updateAuthProvider({
|
|
3896
|
+
id: str(input.id ?? v.id),
|
|
3897
|
+
name: str(input.name),
|
|
3898
|
+
config: input.config,
|
|
3899
|
+
enabled: input.enabled,
|
|
3900
|
+
isDefault: input.isDefault,
|
|
3901
|
+
priority: input.priority
|
|
3902
|
+
});
|
|
3903
|
+
},
|
|
3904
|
+
deleteCustomerAuthProvider: async (v, c) => await c.identity.deleteAuthProvider(str(v.id))
|
|
3905
|
+
},
|
|
3906
|
+
// ── Configs ─────────────────────────────────────────────────
|
|
3907
|
+
configs: {
|
|
3908
|
+
configs: async (v, c) => {
|
|
3909
|
+
const resp = await c.configs.listConfigs({ limit: num(v.limit, 50) });
|
|
3910
|
+
return { items: resp.configs ?? [], total: resp.total ?? 0 };
|
|
3911
|
+
},
|
|
3912
|
+
config: async (v, c) => await c.configs.getConfig(str(v.id)),
|
|
3913
|
+
configByKey: async (v, c) => await c.configs.getConfigByKey(str(v.key)),
|
|
3914
|
+
registerConfig: async (v, c) => {
|
|
3915
|
+
const input = v.input;
|
|
3916
|
+
if (!input) throw new Error("Input required");
|
|
3917
|
+
return await c.configs.createConfig({
|
|
3918
|
+
key: str(input.key),
|
|
3919
|
+
configType: str(input.configType),
|
|
3920
|
+
name: str(input.name),
|
|
3921
|
+
description: str(input.description),
|
|
3922
|
+
config: input.config,
|
|
3923
|
+
enabled: input.enabled,
|
|
3924
|
+
direction: str(input.direction)
|
|
3925
|
+
});
|
|
3926
|
+
},
|
|
3927
|
+
applyConfig: async (v, c) => {
|
|
3928
|
+
const input = v.input;
|
|
3929
|
+
if (!input) throw new Error("Input required");
|
|
3930
|
+
return await c.configs.applyConfig(
|
|
3931
|
+
str(input.key ?? input.configKey),
|
|
3932
|
+
input
|
|
3933
|
+
);
|
|
3934
|
+
},
|
|
3935
|
+
unregisterConfig: async (v, c) => await c.configs.deleteConfig(str(v.id)),
|
|
3936
|
+
triggerConfigSync: async (v, _c) => {
|
|
3937
|
+
console.log(
|
|
3938
|
+
chalk9.yellow(
|
|
3939
|
+
`Config sync trigger for ${str(v.configId)} is not yet available via ConnectRPC.`
|
|
3940
|
+
)
|
|
3941
|
+
);
|
|
3942
|
+
return { success: false };
|
|
3880
3943
|
}
|
|
3881
|
-
}
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
const
|
|
3892
|
-
|
|
3893
|
-
}
|
|
3894
|
-
|
|
3895
|
-
|
|
3944
|
+
},
|
|
3945
|
+
// ── Customers ───────────────────────────────────────────────
|
|
3946
|
+
customers: {
|
|
3947
|
+
customers: async (v, c) => wrapList(
|
|
3948
|
+
await c.identity.listCustomers({
|
|
3949
|
+
limit: num(v.limit, 50),
|
|
3950
|
+
search: str(v.search)
|
|
3951
|
+
})
|
|
3952
|
+
),
|
|
3953
|
+
customer: async (v, c) => {
|
|
3954
|
+
const result = await c.identity.getCustomer(str(v.id));
|
|
3955
|
+
return result.customer;
|
|
3956
|
+
},
|
|
3957
|
+
createCustomer: async (v, c) => {
|
|
3958
|
+
const input = v.input;
|
|
3959
|
+
if (!input) throw new Error("Input required");
|
|
3960
|
+
const result = await c.identity.createCustomer({
|
|
3961
|
+
email: str(input.email),
|
|
3962
|
+
password: str(input.password)
|
|
3963
|
+
});
|
|
3964
|
+
return result.customer;
|
|
3965
|
+
},
|
|
3966
|
+
updateCustomer: async (v, c) => {
|
|
3967
|
+
const input = v.input;
|
|
3968
|
+
if (!input) throw new Error("Input required");
|
|
3969
|
+
const result = await c.identity.updateCustomer({
|
|
3970
|
+
id: str(input.id ?? v.id),
|
|
3971
|
+
email: str(input.email),
|
|
3972
|
+
status: input.status
|
|
3973
|
+
});
|
|
3974
|
+
return result.customer;
|
|
3975
|
+
},
|
|
3976
|
+
deleteCustomer: async (v, c) => await c.identity.deleteCustomer(str(v.id)),
|
|
3977
|
+
suspendCustomer: async (v, c) => {
|
|
3978
|
+
const result = await c.identity.suspendCustomer(str(v.id));
|
|
3979
|
+
return result.customer;
|
|
3896
3980
|
}
|
|
3897
3981
|
}
|
|
3898
|
-
const varPart = varDecls ? `(${varDecls})` : "";
|
|
3899
|
-
const argPart = fieldArgs ? `(${fieldArgs})` : "";
|
|
3900
|
-
return `${opType} ${opName}${varPart} { ${entry.operation}${argPart} ${selectionSet} }`;
|
|
3901
|
-
}
|
|
3902
|
-
function getInputFields(operationName, operationType, inputArgName) {
|
|
3903
|
-
const field = getField(operationName, operationType);
|
|
3904
|
-
if (!field) return [];
|
|
3905
|
-
const argName = inputArgName ?? "input";
|
|
3906
|
-
const arg = field.args.find((a) => a.name === argName);
|
|
3907
|
-
if (!arg) return [];
|
|
3908
|
-
const namedType = unwrapType(arg.type);
|
|
3909
|
-
if (!isInputObjectType(namedType)) return [];
|
|
3910
|
-
const fields = namedType.getFields();
|
|
3911
|
-
return Object.entries(fields).map(([name, f]) => ({
|
|
3912
|
-
name,
|
|
3913
|
-
type: typeToString(f.type),
|
|
3914
|
-
required: isNonNullType(f.type)
|
|
3915
|
-
}));
|
|
3916
|
-
}
|
|
3917
|
-
function getCompletions(partial, commandNames) {
|
|
3918
|
-
return commandNames.filter(
|
|
3919
|
-
(name) => name.toLowerCase().startsWith(partial.toLowerCase())
|
|
3920
|
-
);
|
|
3921
|
-
}
|
|
3922
|
-
return {
|
|
3923
|
-
buildQuery,
|
|
3924
|
-
getOperationArgs,
|
|
3925
|
-
coerceArgs,
|
|
3926
|
-
getInputFields,
|
|
3927
|
-
getCompletions
|
|
3928
3982
|
};
|
|
3929
3983
|
}
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
var __filename = fileURLToPath(import.meta.url);
|
|
3933
|
-
var __dirname = dirname4(__filename);
|
|
3934
|
-
function loadSchemaSDL() {
|
|
3935
|
-
const bundledPath = resolve5(__dirname, "schema.graphql");
|
|
3936
|
-
try {
|
|
3937
|
-
return readFileSync(bundledPath, "utf-8");
|
|
3938
|
-
} catch {
|
|
3939
|
-
const monorepoPath = resolve5(
|
|
3940
|
-
__dirname,
|
|
3941
|
-
"../../../graphql-core/schema.graphql"
|
|
3942
|
-
);
|
|
3943
|
-
try {
|
|
3944
|
-
return readFileSync(monorepoPath, "utf-8");
|
|
3945
|
-
} catch {
|
|
3946
|
-
throw new Error(
|
|
3947
|
-
"Could not find schema.graphql. Try reinstalling @eide/foir-cli."
|
|
3948
|
-
);
|
|
3949
|
-
}
|
|
3950
|
-
}
|
|
3984
|
+
function str(v) {
|
|
3985
|
+
return v != null ? String(v) : void 0;
|
|
3951
3986
|
}
|
|
3952
|
-
function
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
return { data: val, total: obj.total };
|
|
3960
|
-
}
|
|
3961
|
-
}
|
|
3962
|
-
}
|
|
3963
|
-
return { data: raw };
|
|
3987
|
+
function num(v, fallback) {
|
|
3988
|
+
if (v == null) return fallback;
|
|
3989
|
+
const n = Number(v);
|
|
3990
|
+
return Number.isNaN(n) ? fallback : n;
|
|
3991
|
+
}
|
|
3992
|
+
function wrapList(result) {
|
|
3993
|
+
return result;
|
|
3964
3994
|
}
|
|
3965
3995
|
function toCliColumns(columns) {
|
|
3966
3996
|
if (!columns) return void 0;
|
|
@@ -3979,8 +4009,8 @@ function toCliColumns(columns) {
|
|
|
3979
4009
|
break;
|
|
3980
4010
|
case "truncate":
|
|
3981
4011
|
cliCol.format = (v) => {
|
|
3982
|
-
const
|
|
3983
|
-
return
|
|
4012
|
+
const s = String(v ?? "");
|
|
4013
|
+
return s.length > 40 ? s.slice(0, 39) + "\u2026" : s;
|
|
3984
4014
|
};
|
|
3985
4015
|
break;
|
|
3986
4016
|
case "join":
|
|
@@ -3988,27 +4018,37 @@ function toCliColumns(columns) {
|
|
|
3988
4018
|
break;
|
|
3989
4019
|
case "bytes":
|
|
3990
4020
|
cliCol.format = (v) => {
|
|
3991
|
-
const
|
|
3992
|
-
if (
|
|
3993
|
-
if (
|
|
3994
|
-
return `${(
|
|
4021
|
+
const n = Number(v);
|
|
4022
|
+
if (n < 1024) return `${n} B`;
|
|
4023
|
+
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
|
|
4024
|
+
return `${(n / (1024 * 1024)).toFixed(1)} MB`;
|
|
3995
4025
|
};
|
|
3996
4026
|
break;
|
|
3997
4027
|
}
|
|
3998
4028
|
return cliCol;
|
|
3999
4029
|
});
|
|
4000
4030
|
}
|
|
4001
|
-
function
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4031
|
+
function autoColumns(items) {
|
|
4032
|
+
if (items.length === 0) return [];
|
|
4033
|
+
const first = items[0];
|
|
4034
|
+
return Object.keys(first).filter((k) => k !== "__typename" && k !== "$typeName" && k !== "$unknown").slice(0, 6).map((key) => ({
|
|
4035
|
+
key,
|
|
4036
|
+
header: key,
|
|
4037
|
+
width: 20
|
|
4038
|
+
}));
|
|
4039
|
+
}
|
|
4040
|
+
var DISPATCH = buildDispatchTable();
|
|
4041
|
+
async function executeCommand(entry, variables, client) {
|
|
4042
|
+
const groupHandlers = DISPATCH[entry.group];
|
|
4043
|
+
const handler = groupHandlers?.[entry.operation];
|
|
4044
|
+
if (!handler) {
|
|
4045
|
+
throw new Error(
|
|
4046
|
+
`Command not yet migrated to ConnectRPC: ${entry.group}.${entry.operation}`
|
|
4009
4047
|
);
|
|
4010
|
-
return;
|
|
4011
4048
|
}
|
|
4049
|
+
return handler(variables, client);
|
|
4050
|
+
}
|
|
4051
|
+
function registerDynamicCommands(program2, globalOpts) {
|
|
4012
4052
|
const groups = /* @__PURE__ */ new Map();
|
|
4013
4053
|
for (const cmd of COMMANDS) {
|
|
4014
4054
|
if (!groups.has(cmd.group)) groups.set(cmd.group, []);
|
|
@@ -4017,42 +4057,15 @@ function registerDynamicCommands(program2, globalOpts) {
|
|
|
4017
4057
|
for (const [groupName, entries] of groups) {
|
|
4018
4058
|
const group = program2.command(groupName).description(`Manage ${groupName}`);
|
|
4019
4059
|
for (const entry of entries) {
|
|
4060
|
+
if (!DISPATCH[entry.group]?.[entry.operation]) continue;
|
|
4020
4061
|
let cmd = group.command(entry.name).description(entry.description);
|
|
4021
4062
|
for (const pos of entry.positionalArgs ?? []) {
|
|
4022
4063
|
cmd = cmd.argument(`<${pos.name}>`, pos.description ?? pos.name);
|
|
4023
4064
|
}
|
|
4024
|
-
|
|
4025
|
-
entry.operation,
|
|
4026
|
-
entry.operationType
|
|
4027
|
-
);
|
|
4028
|
-
for (const arg of schemaArgs) {
|
|
4029
|
-
if (entry.positionalArgs?.some((p) => p.graphqlArg === arg.name))
|
|
4030
|
-
continue;
|
|
4031
|
-
if (entry.acceptsInput && arg.name === (entry.inputArgName ?? "input"))
|
|
4032
|
-
continue;
|
|
4033
|
-
const reqStr = arg.required ? " (required)" : "";
|
|
4034
|
-
cmd = cmd.option(`--${arg.name} <value>`, `${arg.name}${reqStr}`);
|
|
4035
|
-
}
|
|
4065
|
+
cmd = cmd.option("--limit <n>", "Max results");
|
|
4036
4066
|
if (entry.acceptsInput) {
|
|
4037
4067
|
cmd = cmd.option("-d, --data <json>", "Data as JSON");
|
|
4038
4068
|
cmd = cmd.option("-f, --file <path>", "Read data from file");
|
|
4039
|
-
const inputFields = engine.getInputFields(
|
|
4040
|
-
entry.operation,
|
|
4041
|
-
entry.operationType,
|
|
4042
|
-
entry.inputArgName
|
|
4043
|
-
);
|
|
4044
|
-
if (inputFields.length > 0) {
|
|
4045
|
-
const required = inputFields.filter((f) => f.required);
|
|
4046
|
-
const optional = inputFields.filter((f) => !f.required);
|
|
4047
|
-
let fieldHelp = "\nInput fields:";
|
|
4048
|
-
if (required.length > 0) {
|
|
4049
|
-
fieldHelp += "\n Required: " + required.map((f) => `${f.name} (${f.type})`).join(", ");
|
|
4050
|
-
}
|
|
4051
|
-
if (optional.length > 0) {
|
|
4052
|
-
fieldHelp += "\n Optional: " + optional.map((f) => `${f.name} (${f.type})`).join(", ");
|
|
4053
|
-
}
|
|
4054
|
-
cmd = cmd.addHelpText("after", fieldHelp);
|
|
4055
|
-
}
|
|
4056
4069
|
}
|
|
4057
4070
|
for (const cf of entry.customFlags ?? []) {
|
|
4058
4071
|
cmd = cmd.option(cf.flag, cf.description);
|
|
@@ -4063,7 +4076,7 @@ function registerDynamicCommands(program2, globalOpts) {
|
|
|
4063
4076
|
cmd.action(
|
|
4064
4077
|
withErrorHandler(globalOpts, async (...actionArgs) => {
|
|
4065
4078
|
const opts = globalOpts();
|
|
4066
|
-
const client = await
|
|
4079
|
+
const client = await createPlatformClient(opts);
|
|
4067
4080
|
const variables = {};
|
|
4068
4081
|
const positionals = entry.positionalArgs ?? [];
|
|
4069
4082
|
for (let i = 0; i < positionals.length; i++) {
|
|
@@ -4076,27 +4089,27 @@ function registerDynamicCommands(program2, globalOpts) {
|
|
|
4076
4089
|
const flags = actionArgs[positionals.length] ?? {};
|
|
4077
4090
|
const customFlagNames = new Set(
|
|
4078
4091
|
(entry.customFlags ?? []).map(
|
|
4079
|
-
(cf) => cf.flag.replace(/ <.*>$/, "").replace(/^--/, "").replace(/-([a-z])/g, (_,
|
|
4092
|
+
(cf) => cf.flag.replace(/ <.*>$/, "").replace(/^--/, "").replace(/-([a-z])/g, (_, ch) => ch.toUpperCase())
|
|
4080
4093
|
)
|
|
4081
4094
|
);
|
|
4082
|
-
const rawFlags = {};
|
|
4083
4095
|
for (const [key, val] of Object.entries(flags)) {
|
|
4084
|
-
if (key === "data" || key === "file" || key === "confirm")
|
|
4085
|
-
|
|
4086
|
-
|
|
4096
|
+
if (key === "data" || key === "file" || key === "confirm" || key === "limit")
|
|
4097
|
+
continue;
|
|
4098
|
+
if (customFlagNames.has(key)) {
|
|
4099
|
+
variables[key] = val;
|
|
4100
|
+
continue;
|
|
4101
|
+
}
|
|
4102
|
+
variables[key] = val;
|
|
4103
|
+
}
|
|
4104
|
+
if (flags.limit) {
|
|
4105
|
+
variables.limit = flags.limit;
|
|
4087
4106
|
}
|
|
4088
|
-
const coerced = engine.coerceArgs(
|
|
4089
|
-
entry.operation,
|
|
4090
|
-
entry.operationType,
|
|
4091
|
-
rawFlags
|
|
4092
|
-
);
|
|
4093
|
-
Object.assign(variables, coerced);
|
|
4094
4107
|
if (flags.dir && entry.acceptsInput) {
|
|
4095
4108
|
const dirPath = resolve5(String(flags.dir));
|
|
4096
4109
|
const files = readdirSync(dirPath).filter((f) => /\.(json|ts|js|mjs)$/.test(f)).sort();
|
|
4097
4110
|
if (files.length === 0) {
|
|
4098
4111
|
console.error(
|
|
4099
|
-
chalk9.yellow(
|
|
4112
|
+
chalk9.yellow(`No .json/.ts/.js files found in ${dirPath}`)
|
|
4100
4113
|
);
|
|
4101
4114
|
return;
|
|
4102
4115
|
}
|
|
@@ -4110,8 +4123,7 @@ function registerDynamicCommands(program2, globalOpts) {
|
|
|
4110
4123
|
const fileVars = { ...variables, [argName]: fileData };
|
|
4111
4124
|
const label = fileData.key ?? fileData.name ?? file;
|
|
4112
4125
|
try {
|
|
4113
|
-
|
|
4114
|
-
await client.request(q, fileVars);
|
|
4126
|
+
await executeCommand(entry, fileVars, client);
|
|
4115
4127
|
created++;
|
|
4116
4128
|
if (!(opts.json || opts.jsonl || opts.quiet)) {
|
|
4117
4129
|
success(`Created ${label}`);
|
|
@@ -4130,8 +4142,7 @@ function registerDynamicCommands(program2, globalOpts) {
|
|
|
4130
4142
|
[updateEntry.positionalArgs[0].graphqlArg]: fileData.key
|
|
4131
4143
|
} : {}
|
|
4132
4144
|
};
|
|
4133
|
-
|
|
4134
|
-
await client.request(uq, updateVars);
|
|
4145
|
+
await executeCommand(updateEntry, updateVars, client);
|
|
4135
4146
|
updated++;
|
|
4136
4147
|
if (!(opts.json || opts.jsonl || opts.quiet)) {
|
|
4137
4148
|
success(`Updated ${label}`);
|
|
@@ -4165,19 +4176,13 @@ function registerDynamicCommands(program2, globalOpts) {
|
|
|
4165
4176
|
data: flags.data,
|
|
4166
4177
|
file: flags.file
|
|
4167
4178
|
});
|
|
4168
|
-
|
|
4169
|
-
entry.operation,
|
|
4170
|
-
entry.operationType,
|
|
4171
|
-
entry.inputArgName
|
|
4172
|
-
);
|
|
4173
|
-
const fieldNames = new Set(inputFields.map((f) => f.name));
|
|
4174
|
-
if (fieldNames.has("projectId") && !inputData.projectId || fieldNames.has("tenantId") && !inputData.tenantId) {
|
|
4179
|
+
if (!inputData.projectId || !inputData.tenantId) {
|
|
4175
4180
|
const resolved = await resolveProjectContext(opts);
|
|
4176
4181
|
if (resolved) {
|
|
4177
|
-
if (
|
|
4182
|
+
if (!inputData.projectId) {
|
|
4178
4183
|
inputData.projectId = resolved.project.id;
|
|
4179
4184
|
}
|
|
4180
|
-
if (
|
|
4185
|
+
if (!inputData.tenantId) {
|
|
4181
4186
|
inputData.tenantId = resolved.project.tenantId;
|
|
4182
4187
|
}
|
|
4183
4188
|
}
|
|
@@ -4208,12 +4213,7 @@ function registerDynamicCommands(program2, globalOpts) {
|
|
|
4208
4213
|
if (flags.modelKey) {
|
|
4209
4214
|
variables.modelKey = String(flags.modelKey);
|
|
4210
4215
|
}
|
|
4211
|
-
const
|
|
4212
|
-
const result2 = await client.request(
|
|
4213
|
-
queryStr2,
|
|
4214
|
-
variables
|
|
4215
|
-
);
|
|
4216
|
-
const { data: data2 } = extractResult(result2, altEntry.operation);
|
|
4216
|
+
const data2 = await executeCommand(altEntry, variables, client);
|
|
4217
4217
|
formatOutput(data2, opts);
|
|
4218
4218
|
return;
|
|
4219
4219
|
}
|
|
@@ -4230,13 +4230,10 @@ function registerDynamicCommands(program2, globalOpts) {
|
|
|
4230
4230
|
if (entry.group === "records" && entry.name === "publish" && variables.versionId) {
|
|
4231
4231
|
const versionIdValue = String(variables.versionId);
|
|
4232
4232
|
if (flags.modelKey) {
|
|
4233
|
-
const
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
naturalKey: versionIdValue
|
|
4238
|
-
});
|
|
4239
|
-
const record = lookupResult.recordByKey;
|
|
4233
|
+
const record = await client.records.getRecordByKey(
|
|
4234
|
+
String(flags.modelKey),
|
|
4235
|
+
versionIdValue
|
|
4236
|
+
);
|
|
4240
4237
|
if (!record?.currentVersionId) {
|
|
4241
4238
|
throw new Error(
|
|
4242
4239
|
`No current version found for record "${versionIdValue}"`
|
|
@@ -4249,23 +4246,18 @@ function registerDynamicCommands(program2, globalOpts) {
|
|
|
4249
4246
|
);
|
|
4250
4247
|
} else {
|
|
4251
4248
|
try {
|
|
4252
|
-
const
|
|
4253
|
-
|
|
4254
|
-
id: versionIdValue
|
|
4255
|
-
});
|
|
4256
|
-
const record = lookupResult.record;
|
|
4257
|
-
if (record?.recordType === "record" && record?.currentVersionId) {
|
|
4249
|
+
const record = await client.records.getRecord(versionIdValue);
|
|
4250
|
+
if (record?.recordType === RecordType.RECORD && record?.currentVersionId) {
|
|
4258
4251
|
variables.versionId = record.currentVersionId;
|
|
4259
4252
|
}
|
|
4260
4253
|
} catch {
|
|
4261
4254
|
}
|
|
4262
4255
|
}
|
|
4263
4256
|
}
|
|
4264
|
-
|
|
4265
|
-
let result;
|
|
4257
|
+
let data;
|
|
4266
4258
|
let usedUpdate = false;
|
|
4267
4259
|
try {
|
|
4268
|
-
|
|
4260
|
+
data = await executeCommand(entry, variables, client);
|
|
4269
4261
|
} catch (createErr) {
|
|
4270
4262
|
if (flags.upsert && entry.name === "create") {
|
|
4271
4263
|
const updateEntry = COMMANDS.find(
|
|
@@ -4281,8 +4273,7 @@ function registerDynamicCommands(program2, globalOpts) {
|
|
|
4281
4273
|
[updateEntry.positionalArgs[0].graphqlArg]: inputData.key
|
|
4282
4274
|
} : {}
|
|
4283
4275
|
};
|
|
4284
|
-
|
|
4285
|
-
result = await client.request(uq, updateVars);
|
|
4276
|
+
data = await executeCommand(updateEntry, updateVars, client);
|
|
4286
4277
|
usedUpdate = true;
|
|
4287
4278
|
} else {
|
|
4288
4279
|
throw createErr;
|
|
@@ -4294,8 +4285,14 @@ function registerDynamicCommands(program2, globalOpts) {
|
|
|
4294
4285
|
const activeEntry = usedUpdate ? COMMANDS.find(
|
|
4295
4286
|
(c) => c.group === entry.group && c.name === "update"
|
|
4296
4287
|
) ?? entry : entry;
|
|
4297
|
-
|
|
4298
|
-
|
|
4288
|
+
let displayData = data;
|
|
4289
|
+
let total;
|
|
4290
|
+
if (data && typeof data === "object" && "items" in data) {
|
|
4291
|
+
const listResult = data;
|
|
4292
|
+
displayData = listResult.items;
|
|
4293
|
+
total = listResult.total;
|
|
4294
|
+
}
|
|
4295
|
+
const responseData = displayData && typeof displayData === "object" && !Array.isArray(displayData) ? displayData : void 0;
|
|
4299
4296
|
const displayEntry = usedUpdate ? activeEntry : entry;
|
|
4300
4297
|
if (displayEntry.scalarResult) {
|
|
4301
4298
|
if (!(opts.json || opts.jsonl || opts.quiet)) {
|
|
@@ -4303,16 +4300,16 @@ function registerDynamicCommands(program2, globalOpts) {
|
|
|
4303
4300
|
success(displayEntry.successMessage, responseData);
|
|
4304
4301
|
}
|
|
4305
4302
|
} else {
|
|
4306
|
-
formatOutput(
|
|
4303
|
+
formatOutput(displayData, opts);
|
|
4307
4304
|
}
|
|
4308
|
-
} else if (Array.isArray(
|
|
4305
|
+
} else if (Array.isArray(displayData)) {
|
|
4309
4306
|
const cliColumns = toCliColumns(displayEntry.columns);
|
|
4310
|
-
formatList(
|
|
4311
|
-
columns: cliColumns ?? autoColumns(
|
|
4307
|
+
formatList(displayData, opts, {
|
|
4308
|
+
columns: cliColumns ?? autoColumns(displayData),
|
|
4312
4309
|
total
|
|
4313
4310
|
});
|
|
4314
4311
|
} else {
|
|
4315
|
-
formatOutput(
|
|
4312
|
+
formatOutput(displayData, opts);
|
|
4316
4313
|
if (displayEntry.successMessage && !(opts.json || opts.jsonl || opts.quiet)) {
|
|
4317
4314
|
success(displayEntry.successMessage, responseData);
|
|
4318
4315
|
}
|
|
@@ -4322,15 +4319,14 @@ function registerDynamicCommands(program2, globalOpts) {
|
|
|
4322
4319
|
const record = responseData.record;
|
|
4323
4320
|
const versionId = version2?.id ?? record?.currentVersionId;
|
|
4324
4321
|
if (versionId) {
|
|
4325
|
-
|
|
4326
|
-
await client.request(publishQuery, { versionId });
|
|
4322
|
+
await client.records.publishVersion(versionId);
|
|
4327
4323
|
if (!(opts.json || opts.jsonl || opts.quiet)) {
|
|
4328
4324
|
success("Published version {id}", { id: versionId });
|
|
4329
4325
|
}
|
|
4330
4326
|
} else if (!(opts.json || opts.jsonl || opts.quiet)) {
|
|
4331
4327
|
console.error(
|
|
4332
4328
|
chalk9.yellow(
|
|
4333
|
-
"
|
|
4329
|
+
"Could not auto-publish: no version found in response"
|
|
4334
4330
|
)
|
|
4335
4331
|
);
|
|
4336
4332
|
}
|
|
@@ -4340,20 +4336,11 @@ function registerDynamicCommands(program2, globalOpts) {
|
|
|
4340
4336
|
}
|
|
4341
4337
|
}
|
|
4342
4338
|
}
|
|
4343
|
-
function autoColumns(items) {
|
|
4344
|
-
if (items.length === 0) return [];
|
|
4345
|
-
const first = items[0];
|
|
4346
|
-
return Object.keys(first).filter((k) => k !== "__typename").slice(0, 6).map((key) => ({
|
|
4347
|
-
key,
|
|
4348
|
-
header: key,
|
|
4349
|
-
width: 20
|
|
4350
|
-
}));
|
|
4351
|
-
}
|
|
4352
4339
|
|
|
4353
4340
|
// src/cli.ts
|
|
4354
|
-
var
|
|
4355
|
-
var
|
|
4356
|
-
config({ path: resolve6(
|
|
4341
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
4342
|
+
var __dirname = dirname4(__filename);
|
|
4343
|
+
config({ path: resolve6(__dirname, "../.env.local") });
|
|
4357
4344
|
var require2 = createRequire(import.meta.url);
|
|
4358
4345
|
var { version } = require2("../package.json");
|
|
4359
4346
|
var program = new Command();
|