@eide/foir-cli 0.1.47 → 0.3.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 +1118 -856
- package/dist/lib/config-helpers.d.ts +96 -208
- package/package.json +14 -17
- package/dist/schema.graphql +0 -6212
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,154 @@ 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 as createRpcClient } 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 "@eide/foir-connect-clients/services";
|
|
463
|
+
import { createIdentityMethods } from "@eide/foir-connect-clients/identity";
|
|
464
|
+
import { createModelsMethods } from "@eide/foir-connect-clients/models";
|
|
465
|
+
import { createRecordsMethods } from "@eide/foir-connect-clients/records";
|
|
466
|
+
import { createConfigsMethods } from "@eide/foir-connect-clients/configs";
|
|
467
|
+
import { createSegmentsMethods } from "@eide/foir-connect-clients/segments";
|
|
468
|
+
import { createExperimentsMethods } from "@eide/foir-connect-clients/experiments";
|
|
469
|
+
import { createSettingsMethods } from "@eide/foir-connect-clients/settings";
|
|
470
|
+
import { createStorageMethods } from "@eide/foir-connect-clients/storage";
|
|
471
|
+
import { GraphQLClient } from "graphql-request";
|
|
472
|
+
async function createPlatformClient(options) {
|
|
473
|
+
const apiUrl = getApiUrl(options);
|
|
474
|
+
const headers = {};
|
|
475
|
+
const envApiKey = process.env.FOIR_API_KEY;
|
|
476
|
+
if (envApiKey) {
|
|
477
|
+
headers["x-api-key"] = envApiKey;
|
|
478
|
+
} else {
|
|
479
|
+
const credentials = await getCredentials();
|
|
480
|
+
if (!credentials) {
|
|
481
|
+
throw new Error(
|
|
482
|
+
"Not authenticated. Run `foir login` or set FOIR_API_KEY."
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
if (isTokenExpired(credentials)) {
|
|
486
|
+
throw new Error("Session expired. Run `foir login` to re-authenticate.");
|
|
487
|
+
}
|
|
488
|
+
headers["Authorization"] = `Bearer ${credentials.accessToken}`;
|
|
516
489
|
}
|
|
517
|
-
const
|
|
518
|
-
if (
|
|
519
|
-
|
|
490
|
+
const resolved = await resolveProjectContext(options);
|
|
491
|
+
if (resolved) {
|
|
492
|
+
headers["x-tenant-id"] = resolved.project.tenantId;
|
|
493
|
+
headers["x-project-id"] = resolved.project.id;
|
|
520
494
|
}
|
|
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;
|
|
495
|
+
const authInterceptor = (next) => async (req) => {
|
|
496
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
497
|
+
req.header.set(key, value);
|
|
498
|
+
}
|
|
499
|
+
return next(req);
|
|
500
|
+
};
|
|
501
|
+
const transport = createConnectTransport({
|
|
502
|
+
baseUrl: apiUrl.replace(/\/$/, ""),
|
|
503
|
+
httpVersion: "1.1",
|
|
504
|
+
interceptors: [authInterceptor]
|
|
505
|
+
});
|
|
506
|
+
return {
|
|
507
|
+
identity: createIdentityMethods(createRpcClient(IdentityService, transport)),
|
|
508
|
+
models: createModelsMethods(createRpcClient(ModelsService, transport)),
|
|
509
|
+
records: createRecordsMethods(createRpcClient(RecordsService, transport)),
|
|
510
|
+
configs: createConfigsMethods(createRpcClient(ConfigsService, transport)),
|
|
511
|
+
segments: createSegmentsMethods(createRpcClient(SegmentsService, transport)),
|
|
512
|
+
experiments: createExperimentsMethods(
|
|
513
|
+
createRpcClient(ExperimentsService, transport)
|
|
514
|
+
),
|
|
515
|
+
settings: createSettingsMethods(createRpcClient(SettingsService, transport)),
|
|
516
|
+
storage: createStorageMethods(createRpcClient(StorageService, transport))
|
|
517
|
+
};
|
|
557
518
|
}
|
|
558
|
-
|
|
559
|
-
const
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
519
|
+
function createPlatformClientWithHeaders(apiUrl, headers) {
|
|
520
|
+
const authInterceptor = (next) => async (req) => {
|
|
521
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
522
|
+
req.header.set(key, value);
|
|
523
|
+
}
|
|
524
|
+
return next(req);
|
|
525
|
+
};
|
|
526
|
+
const transport = createConnectTransport({
|
|
527
|
+
baseUrl: apiUrl.replace(/\/$/, ""),
|
|
528
|
+
httpVersion: "1.1",
|
|
529
|
+
interceptors: [authInterceptor]
|
|
530
|
+
});
|
|
531
|
+
return {
|
|
532
|
+
identity: createIdentityMethods(createRpcClient(IdentityService, transport)),
|
|
533
|
+
models: createModelsMethods(createRpcClient(ModelsService, transport)),
|
|
534
|
+
records: createRecordsMethods(createRpcClient(RecordsService, transport)),
|
|
535
|
+
configs: createConfigsMethods(createRpcClient(ConfigsService, transport)),
|
|
536
|
+
segments: createSegmentsMethods(createRpcClient(SegmentsService, transport)),
|
|
537
|
+
experiments: createExperimentsMethods(
|
|
538
|
+
createRpcClient(ExperimentsService, transport)
|
|
539
|
+
),
|
|
540
|
+
settings: createSettingsMethods(createRpcClient(SettingsService, transport)),
|
|
541
|
+
storage: createStorageMethods(createRpcClient(StorageService, transport))
|
|
542
|
+
};
|
|
567
543
|
}
|
|
568
|
-
async function
|
|
569
|
-
const
|
|
570
|
-
const
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
544
|
+
async function getStorageAuth(options) {
|
|
545
|
+
const apiUrl = getApiUrl(options);
|
|
546
|
+
const baseHeaders = {
|
|
547
|
+
"Content-Type": "application/json"
|
|
548
|
+
};
|
|
549
|
+
const envApiKey = process.env.FOIR_API_KEY;
|
|
550
|
+
if (envApiKey) {
|
|
551
|
+
baseHeaders["x-api-key"] = envApiKey;
|
|
552
|
+
} else {
|
|
553
|
+
const credentials = await getCredentials();
|
|
554
|
+
if (!credentials) {
|
|
555
|
+
throw new Error(
|
|
556
|
+
"Not authenticated. Run `foir login` or set FOIR_API_KEY."
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
if (isTokenExpired(credentials)) {
|
|
560
|
+
throw new Error("Session expired. Run `foir login` to re-authenticate.");
|
|
561
|
+
}
|
|
562
|
+
baseHeaders["Authorization"] = `Bearer ${credentials.accessToken}`;
|
|
583
563
|
}
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
564
|
+
const resolved = await resolveProjectContext(options);
|
|
565
|
+
if (resolved) {
|
|
566
|
+
baseHeaders["x-tenant-id"] = resolved.project.tenantId;
|
|
567
|
+
baseHeaders["x-project-id"] = resolved.project.id;
|
|
568
|
+
}
|
|
569
|
+
let cachedToken = null;
|
|
570
|
+
const getToken = async () => {
|
|
571
|
+
if (cachedToken && Date.now() < cachedToken.expiresAt - 3e4) {
|
|
572
|
+
return cachedToken.token;
|
|
573
|
+
}
|
|
574
|
+
const tokenUrl = `${apiUrl.replace(/\/$/, "")}/api/auth/token`;
|
|
575
|
+
const res = await fetch(tokenUrl, {
|
|
576
|
+
method: "POST",
|
|
577
|
+
headers: baseHeaders,
|
|
578
|
+
body: JSON.stringify({ purpose: "storage" })
|
|
579
|
+
});
|
|
580
|
+
if (!res.ok) {
|
|
581
|
+
throw new Error(
|
|
582
|
+
`Failed to get storage token (${res.status}): ${await res.text()}`
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
const data = await res.json();
|
|
586
|
+
cachedToken = {
|
|
587
|
+
token: data.token,
|
|
588
|
+
expiresAt: new Date(data.expiresAt).getTime()
|
|
589
|
+
};
|
|
590
|
+
return cachedToken.token;
|
|
591
|
+
};
|
|
592
|
+
return { getToken };
|
|
587
593
|
}
|
|
594
|
+
|
|
595
|
+
// src/commands/select-project.ts
|
|
596
|
+
var CLI_API_KEY_NAME = "Foir CLI";
|
|
588
597
|
function registerSelectProjectCommand(program2, globalOpts) {
|
|
589
598
|
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
599
|
withErrorHandler(
|
|
@@ -598,11 +607,24 @@ function registerSelectProjectCommand(program2, globalOpts) {
|
|
|
598
607
|
throw new Error("Not authenticated");
|
|
599
608
|
}
|
|
600
609
|
console.log("Fetching your projects...\n");
|
|
601
|
-
const
|
|
602
|
-
|
|
603
|
-
|
|
610
|
+
const client = createPlatformClientWithHeaders(apiUrl, {
|
|
611
|
+
Authorization: `Bearer ${credentials.accessToken}`
|
|
612
|
+
});
|
|
613
|
+
const sessionContext = await client.identity.getSessionContext();
|
|
614
|
+
if (!sessionContext) {
|
|
615
|
+
throw new Error("Could not fetch session context");
|
|
616
|
+
}
|
|
617
|
+
const tenants = (sessionContext.availableTenants ?? []).map(
|
|
618
|
+
(t) => ({
|
|
619
|
+
id: t.id,
|
|
620
|
+
name: t.name
|
|
621
|
+
})
|
|
604
622
|
);
|
|
605
|
-
const
|
|
623
|
+
const projects = (sessionContext.availableProjects ?? []).map((p) => ({
|
|
624
|
+
id: p.id,
|
|
625
|
+
name: p.name,
|
|
626
|
+
tenantId: p.tenantId
|
|
627
|
+
}));
|
|
606
628
|
if (projects.length === 0) {
|
|
607
629
|
console.log("No projects found. Create one in the platform first.");
|
|
608
630
|
throw new Error("No projects available");
|
|
@@ -651,12 +673,12 @@ function registerSelectProjectCommand(program2, globalOpts) {
|
|
|
651
673
|
selectedProject = projects.find((p) => p.id === projectId);
|
|
652
674
|
}
|
|
653
675
|
console.log("\nProvisioning API key for CLI access...");
|
|
654
|
-
const
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
selectedProject.id
|
|
658
|
-
|
|
659
|
-
);
|
|
676
|
+
const projectClient = createPlatformClientWithHeaders(apiUrl, {
|
|
677
|
+
Authorization: `Bearer ${credentials.accessToken}`,
|
|
678
|
+
"x-tenant-id": selectedProject.tenantId,
|
|
679
|
+
"x-project-id": selectedProject.id
|
|
680
|
+
});
|
|
681
|
+
const { apiKey, apiKeyId } = await provisionApiKey(projectClient);
|
|
660
682
|
await writeProjectContext(
|
|
661
683
|
{
|
|
662
684
|
id: selectedProject.id,
|
|
@@ -681,6 +703,26 @@ function registerSelectProjectCommand(program2, globalOpts) {
|
|
|
681
703
|
)
|
|
682
704
|
);
|
|
683
705
|
}
|
|
706
|
+
async function provisionApiKey(client) {
|
|
707
|
+
const { items: apiKeys } = await client.identity.listApiKeys({ limit: 100 });
|
|
708
|
+
const existing = apiKeys.find(
|
|
709
|
+
(k) => k.name === CLI_API_KEY_NAME && k.isActive
|
|
710
|
+
);
|
|
711
|
+
if (existing) {
|
|
712
|
+
console.log(" Rotating existing CLI API key...");
|
|
713
|
+
const result2 = await client.identity.rotateApiKey(existing.id);
|
|
714
|
+
if (!result2.apiKey?.rawKey) {
|
|
715
|
+
throw new Error("Failed to rotate API key \u2014 no raw key returned");
|
|
716
|
+
}
|
|
717
|
+
return { apiKey: result2.apiKey.rawKey, apiKeyId: result2.apiKey.id };
|
|
718
|
+
}
|
|
719
|
+
console.log(" Creating CLI API key...");
|
|
720
|
+
const result = await client.identity.createApiKey({ name: CLI_API_KEY_NAME });
|
|
721
|
+
if (!result.apiKey?.rawKey) {
|
|
722
|
+
throw new Error("Failed to create API key \u2014 no raw key returned");
|
|
723
|
+
}
|
|
724
|
+
return { apiKey: result.apiKey.rawKey, apiKeyId: result.apiKey.id };
|
|
725
|
+
}
|
|
684
726
|
|
|
685
727
|
// src/lib/output.ts
|
|
686
728
|
import chalk2 from "chalk";
|
|
@@ -734,11 +776,11 @@ function formatList(items, options, config2) {
|
|
|
734
776
|
${items.length} of ${config2.total} shown`));
|
|
735
777
|
}
|
|
736
778
|
}
|
|
737
|
-
function pad(
|
|
738
|
-
if (
|
|
739
|
-
return
|
|
779
|
+
function pad(str2, width) {
|
|
780
|
+
if (str2.length > width) {
|
|
781
|
+
return str2.slice(0, width - 1) + "\u2026";
|
|
740
782
|
}
|
|
741
|
-
return
|
|
783
|
+
return str2.padEnd(width);
|
|
742
784
|
}
|
|
743
785
|
function timeAgo(dateStr) {
|
|
744
786
|
if (!dateStr) return "\u2014";
|
|
@@ -831,99 +873,303 @@ function registerWhoamiCommand(program2, globalOpts) {
|
|
|
831
873
|
import { promises as fs2 } from "fs";
|
|
832
874
|
import { basename } from "path";
|
|
833
875
|
import chalk3 from "chalk";
|
|
876
|
+
import { createStorageClient } from "@eide/foir-connect-clients/storage";
|
|
834
877
|
|
|
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.");
|
|
878
|
+
// src/lib/input.ts
|
|
879
|
+
import inquirer2 from "inquirer";
|
|
880
|
+
|
|
881
|
+
// src/lib/config-loader.ts
|
|
882
|
+
import { readFile } from "fs/promises";
|
|
883
|
+
import { pathToFileURL } from "url";
|
|
884
|
+
import { resolve } from "path";
|
|
885
|
+
async function loadConfig(filePath) {
|
|
886
|
+
const absPath = resolve(filePath);
|
|
887
|
+
if (filePath.endsWith(".ts")) {
|
|
888
|
+
const configModule = await import(pathToFileURL(absPath).href);
|
|
889
|
+
return configModule.default;
|
|
851
890
|
}
|
|
852
|
-
if (
|
|
853
|
-
|
|
891
|
+
if (filePath.endsWith(".js") || filePath.endsWith(".mjs")) {
|
|
892
|
+
const configModule = await import(pathToFileURL(absPath).href);
|
|
893
|
+
return configModule.default;
|
|
854
894
|
}
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
headers["x-tenant-id"] = resolved.project.tenantId;
|
|
859
|
-
headers["x-project-id"] = resolved.project.id;
|
|
895
|
+
if (filePath.endsWith(".json")) {
|
|
896
|
+
const content = await readFile(absPath, "utf-8");
|
|
897
|
+
return JSON.parse(content);
|
|
860
898
|
}
|
|
861
|
-
|
|
899
|
+
throw new Error(
|
|
900
|
+
`Unsupported file extension for "${filePath}". Supported: .ts, .js, .mjs, .json`
|
|
901
|
+
);
|
|
862
902
|
}
|
|
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.");
|
|
903
|
+
|
|
904
|
+
// src/lib/input.ts
|
|
905
|
+
async function parseInputData(opts) {
|
|
906
|
+
if (opts.data) {
|
|
907
|
+
return JSON.parse(opts.data);
|
|
874
908
|
}
|
|
875
|
-
if (
|
|
876
|
-
|
|
909
|
+
if (opts.file) {
|
|
910
|
+
return await loadConfig(opts.file);
|
|
877
911
|
}
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
912
|
+
if (!process.stdin.isTTY) {
|
|
913
|
+
const chunks = [];
|
|
914
|
+
for await (const chunk of process.stdin) {
|
|
915
|
+
chunks.push(chunk);
|
|
916
|
+
}
|
|
917
|
+
const stdinContent = Buffer.concat(chunks).toString("utf-8").trim();
|
|
918
|
+
if (stdinContent) {
|
|
919
|
+
return JSON.parse(stdinContent);
|
|
920
|
+
}
|
|
883
921
|
}
|
|
884
|
-
|
|
922
|
+
throw new Error(
|
|
923
|
+
"No input data provided. Use --data, --file, or pipe via stdin."
|
|
924
|
+
);
|
|
925
|
+
}
|
|
926
|
+
function isUUID(value) {
|
|
927
|
+
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(
|
|
928
|
+
value
|
|
929
|
+
) || /^c[a-z0-9]{24,}$/.test(value);
|
|
930
|
+
}
|
|
931
|
+
async function confirmAction(message, opts) {
|
|
932
|
+
if (opts?.confirm) return true;
|
|
933
|
+
const { confirmed } = await inquirer2.prompt([
|
|
934
|
+
{
|
|
935
|
+
type: "confirm",
|
|
936
|
+
name: "confirmed",
|
|
937
|
+
message,
|
|
938
|
+
default: false
|
|
939
|
+
}
|
|
940
|
+
]);
|
|
941
|
+
return confirmed;
|
|
885
942
|
}
|
|
886
943
|
|
|
887
944
|
// src/commands/media.ts
|
|
945
|
+
var MIME_TYPES = {
|
|
946
|
+
".jpg": "image/jpeg",
|
|
947
|
+
".jpeg": "image/jpeg",
|
|
948
|
+
".png": "image/png",
|
|
949
|
+
".gif": "image/gif",
|
|
950
|
+
".webp": "image/webp",
|
|
951
|
+
".avif": "image/avif",
|
|
952
|
+
".svg": "image/svg+xml",
|
|
953
|
+
".mp4": "video/mp4",
|
|
954
|
+
".webm": "video/webm",
|
|
955
|
+
".mov": "video/quicktime",
|
|
956
|
+
".mp3": "audio/mpeg",
|
|
957
|
+
".wav": "audio/wav",
|
|
958
|
+
".pdf": "application/pdf",
|
|
959
|
+
".json": "application/json",
|
|
960
|
+
".csv": "text/csv",
|
|
961
|
+
".txt": "text/plain"
|
|
962
|
+
};
|
|
963
|
+
function guessMimeType(filename) {
|
|
964
|
+
const ext = filename.slice(filename.lastIndexOf(".")).toLowerCase();
|
|
965
|
+
return MIME_TYPES[ext] ?? "application/octet-stream";
|
|
966
|
+
}
|
|
967
|
+
function getStorageUrl() {
|
|
968
|
+
return process.env.FOIR_STORAGE_URL ?? "https://storage.foir.dev";
|
|
969
|
+
}
|
|
970
|
+
function createClient(getToken) {
|
|
971
|
+
return createStorageClient({
|
|
972
|
+
baseUrl: getStorageUrl(),
|
|
973
|
+
getAuthToken: getToken
|
|
974
|
+
});
|
|
975
|
+
}
|
|
888
976
|
function registerMediaCommands(program2, globalOpts) {
|
|
889
977
|
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
|
-
|
|
978
|
+
media.command("upload <filepath>").description("Upload a file").option("--folder <folder>", "Target folder").action(
|
|
979
|
+
withErrorHandler(
|
|
980
|
+
globalOpts,
|
|
981
|
+
async (filepath, flags) => {
|
|
982
|
+
const opts = globalOpts();
|
|
983
|
+
const { getToken } = await getStorageAuth(opts);
|
|
984
|
+
const storage = createClient(getToken);
|
|
985
|
+
const fileBuffer = await fs2.readFile(filepath);
|
|
986
|
+
const filename = basename(filepath);
|
|
987
|
+
const mimeType = guessMimeType(filename);
|
|
988
|
+
const upload = await storage.createFileUpload({
|
|
989
|
+
filename,
|
|
990
|
+
mimeType,
|
|
991
|
+
size: fileBuffer.byteLength,
|
|
992
|
+
folder: flags.folder
|
|
993
|
+
});
|
|
994
|
+
const uploadResp = await fetch(upload.uploadUrl, {
|
|
995
|
+
method: "PUT",
|
|
996
|
+
headers: { "Content-Type": mimeType },
|
|
997
|
+
body: fileBuffer
|
|
998
|
+
});
|
|
999
|
+
if (!uploadResp.ok) {
|
|
1000
|
+
throw new Error(
|
|
1001
|
+
`Upload to storage failed (${uploadResp.status}): ${await uploadResp.text()}`
|
|
1002
|
+
);
|
|
1003
|
+
}
|
|
1004
|
+
const file = await storage.confirmFileUpload(upload.uploadId);
|
|
1005
|
+
if (opts.json || opts.jsonl) {
|
|
1006
|
+
formatOutput(file, opts);
|
|
1007
|
+
} else {
|
|
1008
|
+
success(`Uploaded ${filename}`);
|
|
1009
|
+
if (file?.url) {
|
|
1010
|
+
console.log(chalk3.bold(` URL: ${file.url}`));
|
|
1011
|
+
}
|
|
1012
|
+
if (file?.storageKey) {
|
|
1013
|
+
console.log(chalk3.gray(` Key: ${file.storageKey}`));
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
907
1016
|
}
|
|
908
|
-
|
|
1017
|
+
)
|
|
1018
|
+
);
|
|
1019
|
+
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(
|
|
1020
|
+
withErrorHandler(
|
|
1021
|
+
globalOpts,
|
|
1022
|
+
async (flags) => {
|
|
1023
|
+
const opts = globalOpts();
|
|
1024
|
+
const { getToken } = await getStorageAuth(opts);
|
|
1025
|
+
const storage = createClient(getToken);
|
|
1026
|
+
const result = await storage.listFiles({
|
|
1027
|
+
folder: flags.folder,
|
|
1028
|
+
mimeType: flags["mime-type"] ?? flags.mimeType,
|
|
1029
|
+
search: flags.search,
|
|
1030
|
+
includeDeleted: !!flags.includeDeleted,
|
|
1031
|
+
limit: Number(flags.limit) || 50,
|
|
1032
|
+
offset: Number(flags.offset) || 0
|
|
1033
|
+
});
|
|
1034
|
+
formatList(result.items, opts, {
|
|
1035
|
+
columns: [
|
|
1036
|
+
{ key: "id", header: "ID", width: 28 },
|
|
1037
|
+
{ key: "filename", header: "Filename", width: 24 },
|
|
1038
|
+
{ key: "mimeType", header: "Type", width: 16 },
|
|
1039
|
+
{
|
|
1040
|
+
key: "size",
|
|
1041
|
+
header: "Size",
|
|
1042
|
+
width: 10,
|
|
1043
|
+
format: (v) => {
|
|
1044
|
+
const num2 = Number(v);
|
|
1045
|
+
if (num2 < 1024) return `${num2} B`;
|
|
1046
|
+
if (num2 < 1024 * 1024) return `${(num2 / 1024).toFixed(1)} KB`;
|
|
1047
|
+
return `${(num2 / (1024 * 1024)).toFixed(1)} MB`;
|
|
1048
|
+
}
|
|
1049
|
+
},
|
|
1050
|
+
{ key: "folder", header: "Folder", width: 16 },
|
|
1051
|
+
{
|
|
1052
|
+
key: "createdAt",
|
|
1053
|
+
header: "Created",
|
|
1054
|
+
width: 12,
|
|
1055
|
+
format: (v) => timeAgo(v)
|
|
1056
|
+
}
|
|
1057
|
+
],
|
|
1058
|
+
total: result.total
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
)
|
|
1062
|
+
);
|
|
1063
|
+
media.command("get <id>").description("Get file details").action(
|
|
1064
|
+
withErrorHandler(globalOpts, async (id) => {
|
|
1065
|
+
const opts = globalOpts();
|
|
1066
|
+
const { getToken } = await getStorageAuth(opts);
|
|
1067
|
+
const storage = createClient(getToken);
|
|
1068
|
+
const file = await storage.getFile(id);
|
|
1069
|
+
formatOutput(file, opts);
|
|
1070
|
+
})
|
|
1071
|
+
);
|
|
1072
|
+
media.command("usage").description("Get storage usage").action(
|
|
1073
|
+
withErrorHandler(globalOpts, async () => {
|
|
1074
|
+
const opts = globalOpts();
|
|
1075
|
+
const { getToken } = await getStorageAuth(opts);
|
|
1076
|
+
const storage = createClient(getToken);
|
|
1077
|
+
const usage = await storage.getStorageUsage();
|
|
909
1078
|
if (opts.json || opts.jsonl) {
|
|
910
|
-
formatOutput(
|
|
1079
|
+
formatOutput(usage, opts);
|
|
911
1080
|
} else {
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
1081
|
+
const mb = (Number(usage?.totalBytes ?? 0) / (1024 * 1024)).toFixed(
|
|
1082
|
+
1
|
|
1083
|
+
);
|
|
1084
|
+
console.log(chalk3.bold(`Files: ${usage?.totalFiles ?? 0}`));
|
|
1085
|
+
console.log(chalk3.bold(`Storage: ${mb} MB`));
|
|
1086
|
+
}
|
|
1087
|
+
})
|
|
1088
|
+
);
|
|
1089
|
+
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(
|
|
1090
|
+
withErrorHandler(
|
|
1091
|
+
globalOpts,
|
|
1092
|
+
async (id, flags) => {
|
|
1093
|
+
const opts = globalOpts();
|
|
1094
|
+
const { getToken } = await getStorageAuth(opts);
|
|
1095
|
+
const storage = createClient(getToken);
|
|
1096
|
+
const file = await storage.updateFile({
|
|
1097
|
+
id,
|
|
1098
|
+
filename: flags.filename,
|
|
1099
|
+
folder: flags.folder,
|
|
1100
|
+
tags: flags.tags?.split(",").map((t) => t.trim())
|
|
1101
|
+
});
|
|
1102
|
+
if (opts.json || opts.jsonl) {
|
|
1103
|
+
formatOutput(file, opts);
|
|
1104
|
+
} else {
|
|
1105
|
+
success("Updated file");
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
)
|
|
1109
|
+
);
|
|
1110
|
+
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(
|
|
1111
|
+
withErrorHandler(
|
|
1112
|
+
globalOpts,
|
|
1113
|
+
async (id, flags) => {
|
|
1114
|
+
const opts = globalOpts();
|
|
1115
|
+
const { getToken } = await getStorageAuth(opts);
|
|
1116
|
+
const storage = createClient(getToken);
|
|
1117
|
+
const file = await storage.updateFileMetadata({
|
|
1118
|
+
id,
|
|
1119
|
+
altText: flags.altText ?? flags["alt-text"],
|
|
1120
|
+
caption: flags.caption,
|
|
1121
|
+
description: flags.description
|
|
1122
|
+
});
|
|
1123
|
+
if (opts.json || opts.jsonl) {
|
|
1124
|
+
formatOutput(file, opts);
|
|
1125
|
+
} else {
|
|
1126
|
+
success("Updated file metadata");
|
|
915
1127
|
}
|
|
916
|
-
|
|
917
|
-
|
|
1128
|
+
}
|
|
1129
|
+
)
|
|
1130
|
+
);
|
|
1131
|
+
media.command("delete <id>").description("Delete a file").option("--confirm", "Skip confirmation prompt").option("--permanent", "Permanently delete (cannot be restored)").action(
|
|
1132
|
+
withErrorHandler(
|
|
1133
|
+
globalOpts,
|
|
1134
|
+
async (id, flags) => {
|
|
1135
|
+
const opts = globalOpts();
|
|
1136
|
+
const confirmed = await confirmAction("Delete this file?", {
|
|
1137
|
+
confirm: !!flags.confirm
|
|
1138
|
+
});
|
|
1139
|
+
if (!confirmed) {
|
|
1140
|
+
console.log("Aborted.");
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
1143
|
+
const { getToken } = await getStorageAuth(opts);
|
|
1144
|
+
const storage = createClient(getToken);
|
|
1145
|
+
if (flags.permanent) {
|
|
1146
|
+
await storage.permanentlyDeleteFile(id);
|
|
1147
|
+
success("Permanently deleted file");
|
|
1148
|
+
} else {
|
|
1149
|
+
await storage.deleteFile(id);
|
|
1150
|
+
success("Deleted file");
|
|
918
1151
|
}
|
|
919
1152
|
}
|
|
1153
|
+
)
|
|
1154
|
+
);
|
|
1155
|
+
media.command("restore <id>").description("Restore a deleted file").action(
|
|
1156
|
+
withErrorHandler(globalOpts, async (id) => {
|
|
1157
|
+
const opts = globalOpts();
|
|
1158
|
+
const { getToken } = await getStorageAuth(opts);
|
|
1159
|
+
const storage = createClient(getToken);
|
|
1160
|
+
const file = await storage.restoreFile(id);
|
|
1161
|
+
if (opts.json || opts.jsonl) {
|
|
1162
|
+
formatOutput(file, opts);
|
|
1163
|
+
} else {
|
|
1164
|
+
success("Restored file");
|
|
1165
|
+
}
|
|
920
1166
|
})
|
|
921
1167
|
);
|
|
922
1168
|
}
|
|
923
1169
|
|
|
924
1170
|
// src/commands/create-config.ts
|
|
925
1171
|
import chalk4 from "chalk";
|
|
926
|
-
import
|
|
1172
|
+
import inquirer3 from "inquirer";
|
|
927
1173
|
|
|
928
1174
|
// src/scaffold/scaffold.ts
|
|
929
1175
|
import * as fs4 from "fs";
|
|
@@ -1347,7 +1593,7 @@ function getApiTsconfig() {
|
|
|
1347
1593
|
return JSON.stringify(config2, null, 2) + "\n";
|
|
1348
1594
|
}
|
|
1349
1595
|
function getApiEnvExample(apiUrl) {
|
|
1350
|
-
const baseUrl = apiUrl.replace(
|
|
1596
|
+
const baseUrl = apiUrl.replace(/\/$/, "");
|
|
1351
1597
|
return `# Platform API
|
|
1352
1598
|
PLATFORM_BASE_URL=${baseUrl}
|
|
1353
1599
|
PLATFORM_API_KEY=sk_your_api_key_here
|
|
@@ -1425,11 +1671,7 @@ function isValidConfigType(value) {
|
|
|
1425
1671
|
return CONFIG_TYPES.includes(value);
|
|
1426
1672
|
}
|
|
1427
1673
|
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(
|
|
1674
|
+
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
1675
|
withErrorHandler(
|
|
1434
1676
|
globalOpts,
|
|
1435
1677
|
async (name, cmdOpts) => {
|
|
@@ -1439,7 +1681,7 @@ function registerCreateConfigCommand(program2, globalOpts) {
|
|
|
1439
1681
|
console.log();
|
|
1440
1682
|
let configName = name;
|
|
1441
1683
|
if (!configName) {
|
|
1442
|
-
const { inputName } = await
|
|
1684
|
+
const { inputName } = await inquirer3.prompt([
|
|
1443
1685
|
{
|
|
1444
1686
|
type: "input",
|
|
1445
1687
|
name: "inputName",
|
|
@@ -1453,7 +1695,7 @@ function registerCreateConfigCommand(program2, globalOpts) {
|
|
|
1453
1695
|
if (cmdOpts?.type && isValidConfigType(cmdOpts.type)) {
|
|
1454
1696
|
configType = cmdOpts.type;
|
|
1455
1697
|
} else {
|
|
1456
|
-
const { selectedType } = await
|
|
1698
|
+
const { selectedType } = await inquirer3.prompt([
|
|
1457
1699
|
{
|
|
1458
1700
|
type: "list",
|
|
1459
1701
|
name: "selectedType",
|
|
@@ -1464,7 +1706,7 @@ function registerCreateConfigCommand(program2, globalOpts) {
|
|
|
1464
1706
|
]);
|
|
1465
1707
|
configType = selectedType;
|
|
1466
1708
|
}
|
|
1467
|
-
const apiUrl = cmdOpts?.apiUrl ?? "http://localhost:
|
|
1709
|
+
const apiUrl = cmdOpts?.apiUrl ?? "http://localhost:4011";
|
|
1468
1710
|
console.log();
|
|
1469
1711
|
console.log(
|
|
1470
1712
|
` Scaffolding ${chalk4.cyan(`"${configName}"`)} (${configType})...`
|
|
@@ -1476,69 +1718,53 @@ function registerCreateConfigCommand(program2, globalOpts) {
|
|
|
1476
1718
|
);
|
|
1477
1719
|
}
|
|
1478
1720
|
|
|
1479
|
-
// src/graphql/generated.ts
|
|
1480
|
-
var GlobalSearchDocument = { "kind": "Document", "definitions": [{ "kind": "OperationDefinition", "operation": "query", "name": { "kind": "Name", "value": "GlobalSearch" }, "variableDefinitions": [{ "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "query" } }, "type": { "kind": "NonNullType", "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "String" } } } }, { "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "limit" } }, "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "Int" } } }, { "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "modelKeys" } }, "type": { "kind": "ListType", "type": { "kind": "NonNullType", "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "String" } } } } }, { "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "includeMedia" } }, "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "Boolean" } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "globalSearch" }, "arguments": [{ "kind": "Argument", "name": { "kind": "Name", "value": "query" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "query" } } }, { "kind": "Argument", "name": { "kind": "Name", "value": "limit" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "limit" } } }, { "kind": "Argument", "name": { "kind": "Name", "value": "modelKeys" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "modelKeys" } } }, { "kind": "Argument", "name": { "kind": "Name", "value": "includeMedia" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "includeMedia" } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "records" }, "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }, { "kind": "Field", "name": { "kind": "Name", "value": "modelKey" } }, { "kind": "Field", "name": { "kind": "Name", "value": "title" } }, { "kind": "Field", "name": { "kind": "Name", "value": "naturalKey" } }, { "kind": "Field", "name": { "kind": "Name", "value": "subtitle" } }, { "kind": "Field", "name": { "kind": "Name", "value": "updatedAt" } }] } }, { "kind": "Field", "name": { "kind": "Name", "value": "media" }, "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }, { "kind": "Field", "name": { "kind": "Name", "value": "fileName" } }, { "kind": "Field", "name": { "kind": "Name", "value": "altText" } }, { "kind": "Field", "name": { "kind": "Name", "value": "fileUrl" } }] } }] } }] } }] };
|
|
1481
|
-
|
|
1482
1721
|
// src/commands/search.ts
|
|
1483
1722
|
function registerSearchCommands(program2, globalOpts) {
|
|
1484
|
-
program2.command("search <query>").description("Search across all records
|
|
1723
|
+
program2.command("search <query>").description("Search across all records").option(
|
|
1485
1724
|
"--models <keys>",
|
|
1486
1725
|
"Filter to specific model keys (comma-separated)"
|
|
1487
|
-
).option("--limit <n>", "Max results", "20").
|
|
1726
|
+
).option("--limit <n>", "Max results", "20").action(
|
|
1488
1727
|
withErrorHandler(
|
|
1489
1728
|
globalOpts,
|
|
1490
1729
|
async (query, cmdOpts) => {
|
|
1491
1730
|
const opts = globalOpts();
|
|
1492
|
-
const client = await
|
|
1731
|
+
const client = await createPlatformClient(opts);
|
|
1493
1732
|
const modelKeys = typeof cmdOpts.models === "string" ? cmdOpts.models.split(",").map((k) => k.trim()) : void 0;
|
|
1494
|
-
const
|
|
1733
|
+
const result = await client.records.globalSearch({
|
|
1495
1734
|
query,
|
|
1496
1735
|
limit: parseInt(String(cmdOpts.limit ?? "20"), 10),
|
|
1497
|
-
modelKeys
|
|
1498
|
-
includeMedia: cmdOpts.media !== false
|
|
1736
|
+
modelKeys
|
|
1499
1737
|
});
|
|
1500
1738
|
if (opts.json || opts.jsonl) {
|
|
1501
|
-
formatOutput(
|
|
1739
|
+
formatOutput(result, opts);
|
|
1502
1740
|
return;
|
|
1503
1741
|
}
|
|
1504
|
-
if (
|
|
1742
|
+
if (result.items.length > 0) {
|
|
1505
1743
|
console.log(`
|
|
1506
|
-
Records (${
|
|
1744
|
+
Records (${result.items.length}):`);
|
|
1507
1745
|
formatList(
|
|
1508
|
-
|
|
1746
|
+
result.items.map((item) => ({
|
|
1747
|
+
id: item.id,
|
|
1748
|
+
modelKey: item.modelKey,
|
|
1749
|
+
naturalKey: item.naturalKey ?? "",
|
|
1750
|
+
score: item.score
|
|
1751
|
+
})),
|
|
1509
1752
|
opts,
|
|
1510
1753
|
{
|
|
1511
1754
|
columns: [
|
|
1512
1755
|
{ key: "id", header: "ID", width: 28 },
|
|
1513
1756
|
{ key: "modelKey", header: "Model", width: 18 },
|
|
1514
|
-
{ key: "title", header: "Title", width: 28 },
|
|
1515
1757
|
{ key: "naturalKey", header: "Key", width: 20 },
|
|
1516
1758
|
{
|
|
1517
|
-
key: "
|
|
1518
|
-
header: "
|
|
1519
|
-
width:
|
|
1520
|
-
format: (v) =>
|
|
1759
|
+
key: "score",
|
|
1760
|
+
header: "Score",
|
|
1761
|
+
width: 8,
|
|
1762
|
+
format: (v) => Number(v).toFixed(2)
|
|
1521
1763
|
}
|
|
1522
1764
|
]
|
|
1523
1765
|
}
|
|
1524
1766
|
);
|
|
1525
|
-
}
|
|
1526
|
-
if (data.globalSearch.media.length > 0) {
|
|
1527
|
-
console.log(`
|
|
1528
|
-
Media (${data.globalSearch.media.length}):`);
|
|
1529
|
-
formatList(
|
|
1530
|
-
data.globalSearch.media,
|
|
1531
|
-
opts,
|
|
1532
|
-
{
|
|
1533
|
-
columns: [
|
|
1534
|
-
{ key: "id", header: "ID", width: 28 },
|
|
1535
|
-
{ key: "fileName", header: "File", width: 30 },
|
|
1536
|
-
{ key: "altText", header: "Alt", width: 24 }
|
|
1537
|
-
]
|
|
1538
|
-
}
|
|
1539
|
-
);
|
|
1540
|
-
}
|
|
1541
|
-
if (data.globalSearch.records.length === 0 && data.globalSearch.media.length === 0) {
|
|
1767
|
+
} else {
|
|
1542
1768
|
console.log("No results found.");
|
|
1543
1769
|
}
|
|
1544
1770
|
}
|
|
@@ -1549,9 +1775,9 @@ Media (${data.globalSearch.media.length}):`);
|
|
|
1549
1775
|
// src/commands/init.ts
|
|
1550
1776
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
1551
1777
|
import { writeFile } from "fs/promises";
|
|
1552
|
-
import { resolve as
|
|
1778
|
+
import { resolve as resolve3, join as join4 } from "path";
|
|
1553
1779
|
import chalk5 from "chalk";
|
|
1554
|
-
import
|
|
1780
|
+
import inquirer4 from "inquirer";
|
|
1555
1781
|
var FIELD_DEFAULTS = {
|
|
1556
1782
|
text: "",
|
|
1557
1783
|
richtext: "",
|
|
@@ -1613,9 +1839,9 @@ function generateRecordSeed(model) {
|
|
|
1613
1839
|
)) {
|
|
1614
1840
|
continue;
|
|
1615
1841
|
}
|
|
1616
|
-
if (field.type === "list" && field.
|
|
1842
|
+
if (field.type === "list" && field.itemType) {
|
|
1617
1843
|
data[field.key] = [
|
|
1618
|
-
defaultValueForField({ key: field.key, type: field.
|
|
1844
|
+
defaultValueForField({ key: field.key, type: field.itemType })
|
|
1619
1845
|
];
|
|
1620
1846
|
} else {
|
|
1621
1847
|
data[field.key] = defaultValueForField(field);
|
|
@@ -1640,7 +1866,7 @@ function registerInitCommands(program2, globalOpts) {
|
|
|
1640
1866
|
async (key, opts) => {
|
|
1641
1867
|
const globalFlags = globalOpts();
|
|
1642
1868
|
const template = generateModelTemplate(key);
|
|
1643
|
-
const outDir =
|
|
1869
|
+
const outDir = resolve3(opts.output);
|
|
1644
1870
|
if (!existsSync3(outDir)) {
|
|
1645
1871
|
mkdirSync2(outDir, { recursive: true });
|
|
1646
1872
|
}
|
|
@@ -1671,10 +1897,19 @@ Edit the file, then run:
|
|
|
1671
1897
|
globalOpts,
|
|
1672
1898
|
async (opts) => {
|
|
1673
1899
|
const globalFlags = globalOpts();
|
|
1674
|
-
const client = await
|
|
1675
|
-
const
|
|
1676
|
-
const
|
|
1677
|
-
|
|
1900
|
+
const client = await createPlatformClient(globalFlags);
|
|
1901
|
+
const result = await client.models.listModels({ limit: 100 });
|
|
1902
|
+
const models = result.items.map((m) => ({
|
|
1903
|
+
key: m.key,
|
|
1904
|
+
name: m.name,
|
|
1905
|
+
fields: (m.fields ?? []).map((f) => ({
|
|
1906
|
+
key: f.key,
|
|
1907
|
+
type: f.type,
|
|
1908
|
+
label: f.label,
|
|
1909
|
+
required: f.required,
|
|
1910
|
+
itemType: f.itemType
|
|
1911
|
+
}))
|
|
1912
|
+
}));
|
|
1678
1913
|
if (models.length === 0) {
|
|
1679
1914
|
console.log(
|
|
1680
1915
|
chalk5.yellow(
|
|
@@ -1699,7 +1934,7 @@ Edit the file, then run:
|
|
|
1699
1934
|
selectedModels.push(found);
|
|
1700
1935
|
}
|
|
1701
1936
|
} else {
|
|
1702
|
-
const { selected } = await
|
|
1937
|
+
const { selected } = await inquirer4.prompt([
|
|
1703
1938
|
{
|
|
1704
1939
|
type: "checkbox",
|
|
1705
1940
|
name: "selected",
|
|
@@ -1718,7 +1953,7 @@ Edit the file, then run:
|
|
|
1718
1953
|
console.log("No models selected.");
|
|
1719
1954
|
return;
|
|
1720
1955
|
}
|
|
1721
|
-
const outDir =
|
|
1956
|
+
const outDir = resolve3(opts.output);
|
|
1722
1957
|
if (!existsSync3(outDir)) {
|
|
1723
1958
|
mkdirSync2(outDir, { recursive: true });
|
|
1724
1959
|
}
|
|
@@ -1754,64 +1989,12 @@ Edit the files, then run:
|
|
|
1754
1989
|
import chalk6 from "chalk";
|
|
1755
1990
|
import { existsSync as existsSync4 } from "fs";
|
|
1756
1991
|
import { resolve as resolve4 } from "path";
|
|
1757
|
-
|
|
1758
|
-
// src/lib/config-loader.ts
|
|
1759
|
-
import { readFile } from "fs/promises";
|
|
1760
|
-
import { pathToFileURL } from "url";
|
|
1761
|
-
import { resolve as resolve3 } from "path";
|
|
1762
|
-
async function loadConfig(filePath) {
|
|
1763
|
-
const absPath = resolve3(filePath);
|
|
1764
|
-
if (filePath.endsWith(".ts")) {
|
|
1765
|
-
const configModule = await import(pathToFileURL(absPath).href);
|
|
1766
|
-
return configModule.default;
|
|
1767
|
-
}
|
|
1768
|
-
if (filePath.endsWith(".js") || filePath.endsWith(".mjs")) {
|
|
1769
|
-
const configModule = await import(pathToFileURL(absPath).href);
|
|
1770
|
-
return configModule.default;
|
|
1771
|
-
}
|
|
1772
|
-
if (filePath.endsWith(".json")) {
|
|
1773
|
-
const content = await readFile(absPath, "utf-8");
|
|
1774
|
-
return JSON.parse(content);
|
|
1775
|
-
}
|
|
1776
|
-
throw new Error(
|
|
1777
|
-
`Unsupported file extension for "${filePath}". Supported: .ts, .js, .mjs, .json`
|
|
1778
|
-
);
|
|
1779
|
-
}
|
|
1780
|
-
|
|
1781
|
-
// src/commands/push.ts
|
|
1782
1992
|
var CONFIG_FILE_NAMES = [
|
|
1783
1993
|
"foir.config.ts",
|
|
1784
1994
|
"foir.config.js",
|
|
1785
1995
|
"foir.config.mjs",
|
|
1786
1996
|
"foir.config.json"
|
|
1787
1997
|
];
|
|
1788
|
-
var APPLY_CONFIG_MUTATION = (
|
|
1789
|
-
/* GraphQL */
|
|
1790
|
-
`
|
|
1791
|
-
mutation ApplyConfig($input: ApplyConfigInput!) {
|
|
1792
|
-
applyConfig(input: $input) {
|
|
1793
|
-
configId
|
|
1794
|
-
configKey
|
|
1795
|
-
credentials {
|
|
1796
|
-
platformApiKey
|
|
1797
|
-
platformEditorKey
|
|
1798
|
-
webhookSecret
|
|
1799
|
-
}
|
|
1800
|
-
modelsCreated
|
|
1801
|
-
modelsUpdated
|
|
1802
|
-
operationsCreated
|
|
1803
|
-
operationsUpdated
|
|
1804
|
-
segmentsCreated
|
|
1805
|
-
segmentsUpdated
|
|
1806
|
-
schedulesCreated
|
|
1807
|
-
schedulesUpdated
|
|
1808
|
-
hooksCreated
|
|
1809
|
-
hooksUpdated
|
|
1810
|
-
isUpdate
|
|
1811
|
-
}
|
|
1812
|
-
}
|
|
1813
|
-
`
|
|
1814
|
-
);
|
|
1815
1998
|
function discoverConfigFile() {
|
|
1816
1999
|
for (const name of CONFIG_FILE_NAMES) {
|
|
1817
2000
|
const path3 = resolve4(process.cwd(), name);
|
|
@@ -1843,62 +2026,25 @@ function registerPushCommand(program2, globalOpts) {
|
|
|
1843
2026
|
if (opts.force) {
|
|
1844
2027
|
config2.force = true;
|
|
1845
2028
|
}
|
|
1846
|
-
const client = await
|
|
2029
|
+
const client = await createPlatformClient(globalOpts());
|
|
1847
2030
|
console.log(
|
|
1848
2031
|
chalk6.dim(`Pushing config "${config2.key}" to platform...`)
|
|
1849
2032
|
);
|
|
1850
|
-
const
|
|
1851
|
-
|
|
1852
|
-
|
|
2033
|
+
const result = await client.configs.applyConfig(
|
|
2034
|
+
config2.key,
|
|
2035
|
+
config2
|
|
1853
2036
|
);
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
} else {
|
|
1859
|
-
console.log(chalk6.green("Config applied successfully."));
|
|
2037
|
+
if (!result) {
|
|
2038
|
+
throw new Error(
|
|
2039
|
+
"Failed to apply config \u2014 no response from server."
|
|
2040
|
+
);
|
|
1860
2041
|
}
|
|
1861
2042
|
console.log();
|
|
1862
|
-
console.log(
|
|
1863
|
-
console.log(
|
|
2043
|
+
console.log(chalk6.green("Config applied successfully."));
|
|
2044
|
+
console.log();
|
|
2045
|
+
console.log(` Config ID: ${chalk6.cyan(result.id)}`);
|
|
2046
|
+
console.log(` Config Key: ${chalk6.cyan(result.key)}`);
|
|
1864
2047
|
console.log();
|
|
1865
|
-
const stats = [
|
|
1866
|
-
["Models", result.modelsCreated, result.modelsUpdated],
|
|
1867
|
-
["Operations", result.operationsCreated, result.operationsUpdated],
|
|
1868
|
-
["Segments", result.segmentsCreated, result.segmentsUpdated],
|
|
1869
|
-
["Schedules", result.schedulesCreated, result.schedulesUpdated],
|
|
1870
|
-
["Hooks", result.hooksCreated, result.hooksUpdated]
|
|
1871
|
-
].filter(([, c, u]) => c > 0 || u > 0);
|
|
1872
|
-
if (stats.length > 0) {
|
|
1873
|
-
for (const [label, created, updated] of stats) {
|
|
1874
|
-
const parts = [];
|
|
1875
|
-
if (created > 0)
|
|
1876
|
-
parts.push(chalk6.green(`${created} created`));
|
|
1877
|
-
if (updated > 0)
|
|
1878
|
-
parts.push(chalk6.yellow(`${updated} updated`));
|
|
1879
|
-
console.log(` ${label}: ${parts.join(", ")}`);
|
|
1880
|
-
}
|
|
1881
|
-
console.log();
|
|
1882
|
-
}
|
|
1883
|
-
if (result.credentials) {
|
|
1884
|
-
console.log(chalk6.bold.yellow("Credentials (save these now):"));
|
|
1885
|
-
console.log();
|
|
1886
|
-
console.log(
|
|
1887
|
-
` PLATFORM_API_KEY: ${chalk6.cyan(result.credentials.platformApiKey)}`
|
|
1888
|
-
);
|
|
1889
|
-
console.log(
|
|
1890
|
-
` PLATFORM_EDITOR_KEY: ${chalk6.cyan(result.credentials.platformEditorKey)}`
|
|
1891
|
-
);
|
|
1892
|
-
console.log(
|
|
1893
|
-
` WEBHOOK_SECRET: ${chalk6.cyan(result.credentials.webhookSecret)}`
|
|
1894
|
-
);
|
|
1895
|
-
console.log();
|
|
1896
|
-
console.log(
|
|
1897
|
-
chalk6.dim(
|
|
1898
|
-
"These credentials are only shown once. Store them securely."
|
|
1899
|
-
)
|
|
1900
|
-
);
|
|
1901
|
-
}
|
|
1902
2048
|
}
|
|
1903
2049
|
)
|
|
1904
2050
|
);
|
|
@@ -1906,106 +2052,42 @@ function registerPushCommand(program2, globalOpts) {
|
|
|
1906
2052
|
|
|
1907
2053
|
// src/commands/remove.ts
|
|
1908
2054
|
import chalk7 from "chalk";
|
|
1909
|
-
import
|
|
1910
|
-
var GET_CONFIG_QUERY = (
|
|
1911
|
-
/* GraphQL */
|
|
1912
|
-
`
|
|
1913
|
-
query GetConfigByKey($key: String!) {
|
|
1914
|
-
configByKey(key: $key) {
|
|
1915
|
-
id
|
|
1916
|
-
key
|
|
1917
|
-
name
|
|
1918
|
-
configType
|
|
1919
|
-
}
|
|
1920
|
-
}
|
|
1921
|
-
`
|
|
1922
|
-
);
|
|
1923
|
-
var UNREGISTER_MUTATION = (
|
|
1924
|
-
/* GraphQL */
|
|
1925
|
-
`
|
|
1926
|
-
mutation UnregisterConfig($id: ID!) {
|
|
1927
|
-
unregisterConfig(id: $id)
|
|
1928
|
-
}
|
|
1929
|
-
`
|
|
1930
|
-
);
|
|
2055
|
+
import inquirer5 from "inquirer";
|
|
1931
2056
|
function registerRemoveCommand(program2, globalOpts) {
|
|
1932
2057
|
program2.command("remove <key>").description("Remove a config and all its provisioned resources").option("--force", "Skip confirmation prompt", false).action(
|
|
1933
2058
|
withErrorHandler(
|
|
1934
2059
|
globalOpts,
|
|
1935
2060
|
async (key, opts) => {
|
|
1936
|
-
const client = await
|
|
1937
|
-
const
|
|
2061
|
+
const client = await createPlatformClient(globalOpts());
|
|
2062
|
+
const config2 = await client.configs.getConfigByKey(key);
|
|
1938
2063
|
if (!config2) {
|
|
1939
2064
|
throw new Error(`Config not found: ${key}`);
|
|
1940
2065
|
}
|
|
1941
2066
|
if (!opts.force) {
|
|
1942
|
-
const { confirmed } = await
|
|
2067
|
+
const { confirmed } = await inquirer5.prompt([
|
|
1943
2068
|
{
|
|
1944
2069
|
type: "confirm",
|
|
1945
2070
|
name: "confirmed",
|
|
1946
2071
|
message: `Remove config "${config2.name}" (${config2.key})? This will delete all its models, operations, hooks, and schedules.`,
|
|
1947
2072
|
default: false
|
|
1948
|
-
}
|
|
1949
|
-
]);
|
|
1950
|
-
if (!confirmed) {
|
|
1951
|
-
console.log(chalk7.dim("Cancelled."));
|
|
1952
|
-
return;
|
|
1953
|
-
}
|
|
1954
|
-
}
|
|
1955
|
-
await client.
|
|
1956
|
-
console.log(
|
|
1957
|
-
chalk7.green(`Removed config "${config2.name}" (${config2.key}).`)
|
|
1958
|
-
);
|
|
1959
|
-
}
|
|
1960
|
-
)
|
|
1961
|
-
);
|
|
1962
|
-
}
|
|
1963
|
-
|
|
1964
|
-
// src/commands/profiles.ts
|
|
1965
|
-
import chalk8 from "chalk";
|
|
1966
|
-
|
|
1967
|
-
// src/lib/input.ts
|
|
1968
|
-
import inquirer5 from "inquirer";
|
|
1969
|
-
async function parseInputData(opts) {
|
|
1970
|
-
if (opts.data) {
|
|
1971
|
-
return JSON.parse(opts.data);
|
|
1972
|
-
}
|
|
1973
|
-
if (opts.file) {
|
|
1974
|
-
return await loadConfig(opts.file);
|
|
1975
|
-
}
|
|
1976
|
-
if (!process.stdin.isTTY) {
|
|
1977
|
-
const chunks = [];
|
|
1978
|
-
for await (const chunk of process.stdin) {
|
|
1979
|
-
chunks.push(chunk);
|
|
1980
|
-
}
|
|
1981
|
-
const stdinContent = Buffer.concat(chunks).toString("utf-8").trim();
|
|
1982
|
-
if (stdinContent) {
|
|
1983
|
-
return JSON.parse(stdinContent);
|
|
1984
|
-
}
|
|
1985
|
-
}
|
|
1986
|
-
throw new Error(
|
|
1987
|
-
"No input data provided. Use --data, --file, or pipe via stdin."
|
|
1988
|
-
);
|
|
1989
|
-
}
|
|
1990
|
-
function isUUID(value) {
|
|
1991
|
-
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(
|
|
1992
|
-
value
|
|
1993
|
-
) || /^c[a-z0-9]{24,}$/.test(value);
|
|
1994
|
-
}
|
|
1995
|
-
async function confirmAction(message, opts) {
|
|
1996
|
-
if (opts?.confirm) return true;
|
|
1997
|
-
const { confirmed } = await inquirer5.prompt([
|
|
1998
|
-
{
|
|
1999
|
-
type: "confirm",
|
|
2000
|
-
name: "confirmed",
|
|
2001
|
-
message,
|
|
2002
|
-
default: false
|
|
2003
|
-
}
|
|
2004
|
-
]);
|
|
2005
|
-
return confirmed;
|
|
2073
|
+
}
|
|
2074
|
+
]);
|
|
2075
|
+
if (!confirmed) {
|
|
2076
|
+
console.log(chalk7.dim("Cancelled."));
|
|
2077
|
+
return;
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
2080
|
+
await client.configs.deleteConfig(config2.id);
|
|
2081
|
+
console.log(
|
|
2082
|
+
chalk7.green(`Removed config "${config2.name}" (${config2.key}).`)
|
|
2083
|
+
);
|
|
2084
|
+
}
|
|
2085
|
+
)
|
|
2086
|
+
);
|
|
2006
2087
|
}
|
|
2007
2088
|
|
|
2008
2089
|
// src/commands/profiles.ts
|
|
2090
|
+
import chalk8 from "chalk";
|
|
2009
2091
|
function registerProfilesCommand(program2, globalOpts) {
|
|
2010
2092
|
const profiles = program2.command("profiles").description("Manage named project profiles");
|
|
2011
2093
|
profiles.command("list").description("List all saved project profiles").action(
|
|
@@ -2173,12 +2255,11 @@ function registerProfilesCommand(program2, globalOpts) {
|
|
|
2173
2255
|
}
|
|
2174
2256
|
|
|
2175
2257
|
// src/commands/register-commands.ts
|
|
2176
|
-
import {
|
|
2177
|
-
import { resolve as resolve5
|
|
2178
|
-
import { fileURLToPath } from "url";
|
|
2258
|
+
import { readdirSync } from "fs";
|
|
2259
|
+
import { resolve as resolve5 } from "path";
|
|
2179
2260
|
import chalk9 from "chalk";
|
|
2180
2261
|
|
|
2181
|
-
//
|
|
2262
|
+
// src/command-registry/command-map.ts
|
|
2182
2263
|
var COMMANDS = [
|
|
2183
2264
|
// =========================================================================
|
|
2184
2265
|
// MODELS
|
|
@@ -2353,6 +2434,15 @@ var COMMANDS = [
|
|
|
2353
2434
|
scalarResult: true,
|
|
2354
2435
|
successMessage: "Unpublished record"
|
|
2355
2436
|
},
|
|
2437
|
+
{
|
|
2438
|
+
group: "records",
|
|
2439
|
+
name: "cancel-schedule",
|
|
2440
|
+
description: "Cancel a scheduled publish or unpublish",
|
|
2441
|
+
operation: "cancelScheduledRecordPublish",
|
|
2442
|
+
operationType: "mutation",
|
|
2443
|
+
positionalArgs: [{ name: "versionId", graphqlArg: "versionId", description: "Version ID" }],
|
|
2444
|
+
successMessage: "Cancelled scheduled publish for version {versionId}"
|
|
2445
|
+
},
|
|
2356
2446
|
{
|
|
2357
2447
|
group: "records",
|
|
2358
2448
|
name: "duplicate",
|
|
@@ -2409,6 +2499,104 @@ var COMMANDS = [
|
|
|
2409
2499
|
successMessage: "Created variant"
|
|
2410
2500
|
},
|
|
2411
2501
|
// =========================================================================
|
|
2502
|
+
// ROLLOUTS
|
|
2503
|
+
// =========================================================================
|
|
2504
|
+
{
|
|
2505
|
+
group: "rollouts",
|
|
2506
|
+
name: "list",
|
|
2507
|
+
description: "List rollouts",
|
|
2508
|
+
operation: "listPublishBatches",
|
|
2509
|
+
operationType: "query",
|
|
2510
|
+
columns: [
|
|
2511
|
+
{ key: "id", header: "ID", width: 28 },
|
|
2512
|
+
{ key: "name", header: "Name", width: 24 },
|
|
2513
|
+
{ key: "status", header: "Status", width: 16 },
|
|
2514
|
+
{ key: "itemCount", header: "Items", width: 6 },
|
|
2515
|
+
{ key: "completedCount", header: "Done", width: 6 },
|
|
2516
|
+
{ key: "scheduledAt", header: "Scheduled", width: 12, format: "timeAgo" },
|
|
2517
|
+
{ key: "createdAt", header: "Created", width: 12, format: "timeAgo" }
|
|
2518
|
+
]
|
|
2519
|
+
},
|
|
2520
|
+
{
|
|
2521
|
+
group: "rollouts",
|
|
2522
|
+
name: "get",
|
|
2523
|
+
description: "Get a rollout",
|
|
2524
|
+
operation: "getPublishBatch",
|
|
2525
|
+
operationType: "query",
|
|
2526
|
+
positionalArgs: [{ name: "id", graphqlArg: "id", description: "Rollout ID" }]
|
|
2527
|
+
},
|
|
2528
|
+
{
|
|
2529
|
+
group: "rollouts",
|
|
2530
|
+
name: "create",
|
|
2531
|
+
description: "Create a rollout",
|
|
2532
|
+
operation: "createPublishBatch",
|
|
2533
|
+
operationType: "mutation",
|
|
2534
|
+
acceptsInput: true,
|
|
2535
|
+
successMessage: "Created rollout {name}"
|
|
2536
|
+
},
|
|
2537
|
+
{
|
|
2538
|
+
group: "rollouts",
|
|
2539
|
+
name: "update",
|
|
2540
|
+
description: "Update a rollout",
|
|
2541
|
+
operation: "updatePublishBatch",
|
|
2542
|
+
operationType: "mutation",
|
|
2543
|
+
positionalArgs: [{ name: "id", graphqlArg: "id", description: "Rollout ID" }],
|
|
2544
|
+
acceptsInput: true,
|
|
2545
|
+
successMessage: "Updated rollout"
|
|
2546
|
+
},
|
|
2547
|
+
{
|
|
2548
|
+
group: "rollouts",
|
|
2549
|
+
name: "cancel",
|
|
2550
|
+
description: "Cancel a rollout",
|
|
2551
|
+
operation: "cancelPublishBatch",
|
|
2552
|
+
operationType: "mutation",
|
|
2553
|
+
positionalArgs: [{ name: "id", graphqlArg: "id", description: "Rollout ID" }],
|
|
2554
|
+
requiresConfirmation: true,
|
|
2555
|
+
scalarResult: true,
|
|
2556
|
+
successMessage: "Cancelled rollout"
|
|
2557
|
+
},
|
|
2558
|
+
{
|
|
2559
|
+
group: "rollouts",
|
|
2560
|
+
name: "rollback",
|
|
2561
|
+
description: "Rollback a completed rollout",
|
|
2562
|
+
operation: "rollbackPublishBatch",
|
|
2563
|
+
operationType: "mutation",
|
|
2564
|
+
positionalArgs: [{ name: "id", graphqlArg: "id", description: "Rollout ID" }],
|
|
2565
|
+
requiresConfirmation: true,
|
|
2566
|
+
scalarResult: true,
|
|
2567
|
+
successMessage: "Rolled back rollout"
|
|
2568
|
+
},
|
|
2569
|
+
{
|
|
2570
|
+
group: "rollouts",
|
|
2571
|
+
name: "retry",
|
|
2572
|
+
description: "Retry failed items in a rollout",
|
|
2573
|
+
operation: "retryFailedBatchItems",
|
|
2574
|
+
operationType: "mutation",
|
|
2575
|
+
positionalArgs: [{ name: "id", graphqlArg: "id", description: "Rollout ID" }],
|
|
2576
|
+
scalarResult: true,
|
|
2577
|
+
successMessage: "Retrying failed items in rollout"
|
|
2578
|
+
},
|
|
2579
|
+
{
|
|
2580
|
+
group: "rollouts",
|
|
2581
|
+
name: "add-items",
|
|
2582
|
+
description: "Add version IDs to a rollout",
|
|
2583
|
+
operation: "addItemsToPublishBatch",
|
|
2584
|
+
operationType: "mutation",
|
|
2585
|
+
positionalArgs: [{ name: "id", graphqlArg: "id", description: "Rollout ID" }],
|
|
2586
|
+
acceptsInput: true,
|
|
2587
|
+
successMessage: "Added items to rollout"
|
|
2588
|
+
},
|
|
2589
|
+
{
|
|
2590
|
+
group: "rollouts",
|
|
2591
|
+
name: "remove-items",
|
|
2592
|
+
description: "Remove version IDs from a rollout",
|
|
2593
|
+
operation: "removeItemsFromPublishBatch",
|
|
2594
|
+
operationType: "mutation",
|
|
2595
|
+
positionalArgs: [{ name: "id", graphqlArg: "id", description: "Rollout ID" }],
|
|
2596
|
+
acceptsInput: true,
|
|
2597
|
+
successMessage: "Removed items from rollout"
|
|
2598
|
+
},
|
|
2599
|
+
// =========================================================================
|
|
2412
2600
|
// LOCALES
|
|
2413
2601
|
// =========================================================================
|
|
2414
2602
|
{
|
|
@@ -2953,68 +3141,6 @@ var COMMANDS = [
|
|
|
2953
3141
|
successMessage: "Updated customer profile"
|
|
2954
3142
|
},
|
|
2955
3143
|
// =========================================================================
|
|
2956
|
-
// FILES
|
|
2957
|
-
// =========================================================================
|
|
2958
|
-
{
|
|
2959
|
-
group: "files",
|
|
2960
|
-
name: "list",
|
|
2961
|
-
description: "List files",
|
|
2962
|
-
operation: "files",
|
|
2963
|
-
operationType: "query",
|
|
2964
|
-
columns: [
|
|
2965
|
-
{ key: "id", header: "ID", width: 28 },
|
|
2966
|
-
{ key: "filename", header: "Filename", width: 24 },
|
|
2967
|
-
{ key: "mimeType", header: "Type", width: 16 },
|
|
2968
|
-
{ key: "size", header: "Size", width: 10, format: "bytes" },
|
|
2969
|
-
{ key: "folder", header: "Folder", width: 16 },
|
|
2970
|
-
{ key: "createdAt", header: "Created", width: 12, format: "timeAgo" }
|
|
2971
|
-
]
|
|
2972
|
-
},
|
|
2973
|
-
{
|
|
2974
|
-
group: "files",
|
|
2975
|
-
name: "get",
|
|
2976
|
-
description: "Get a file",
|
|
2977
|
-
operation: "file",
|
|
2978
|
-
operationType: "query",
|
|
2979
|
-
positionalArgs: [{ name: "id", graphqlArg: "id" }]
|
|
2980
|
-
},
|
|
2981
|
-
{
|
|
2982
|
-
group: "files",
|
|
2983
|
-
name: "usage",
|
|
2984
|
-
description: "Get file storage usage",
|
|
2985
|
-
operation: "fileStorageUsage",
|
|
2986
|
-
operationType: "query"
|
|
2987
|
-
},
|
|
2988
|
-
{
|
|
2989
|
-
group: "files",
|
|
2990
|
-
name: "update",
|
|
2991
|
-
description: "Update a file",
|
|
2992
|
-
operation: "updateFile",
|
|
2993
|
-
operationType: "mutation",
|
|
2994
|
-
positionalArgs: [{ name: "id", graphqlArg: "id" }],
|
|
2995
|
-
successMessage: "Updated file"
|
|
2996
|
-
},
|
|
2997
|
-
{
|
|
2998
|
-
group: "files",
|
|
2999
|
-
name: "update-metadata",
|
|
3000
|
-
description: "Update file metadata",
|
|
3001
|
-
operation: "updateFileMetadata",
|
|
3002
|
-
operationType: "mutation",
|
|
3003
|
-
positionalArgs: [{ name: "id", graphqlArg: "id" }],
|
|
3004
|
-
successMessage: "Updated file metadata"
|
|
3005
|
-
},
|
|
3006
|
-
{
|
|
3007
|
-
group: "files",
|
|
3008
|
-
name: "delete",
|
|
3009
|
-
description: "Delete a file",
|
|
3010
|
-
operation: "deleteFile",
|
|
3011
|
-
operationType: "mutation",
|
|
3012
|
-
positionalArgs: [{ name: "id", graphqlArg: "id" }],
|
|
3013
|
-
requiresConfirmation: true,
|
|
3014
|
-
scalarResult: true,
|
|
3015
|
-
successMessage: "Deleted file"
|
|
3016
|
-
},
|
|
3017
|
-
// =========================================================================
|
|
3018
3144
|
// OPERATIONS
|
|
3019
3145
|
// =========================================================================
|
|
3020
3146
|
{
|
|
@@ -3179,6 +3305,7 @@ var COMMANDS = [
|
|
|
3179
3305
|
description: "List hook deliveries",
|
|
3180
3306
|
operation: "hookDeliveries",
|
|
3181
3307
|
operationType: "query",
|
|
3308
|
+
positionalArgs: [{ name: "hookId", graphqlArg: "hookId", description: "Hook ID" }],
|
|
3182
3309
|
columns: [
|
|
3183
3310
|
{ key: "id", header: "ID", width: 28 },
|
|
3184
3311
|
{ key: "event", header: "Event", width: 16 },
|
|
@@ -3582,7 +3709,7 @@ var COMMANDS = [
|
|
|
3582
3709
|
}
|
|
3583
3710
|
];
|
|
3584
3711
|
|
|
3585
|
-
//
|
|
3712
|
+
// src/command-registry/schema-engine.ts
|
|
3586
3713
|
import {
|
|
3587
3714
|
buildSchema,
|
|
3588
3715
|
isObjectType,
|
|
@@ -3592,220 +3719,398 @@ import {
|
|
|
3592
3719
|
isScalarType,
|
|
3593
3720
|
isEnumType
|
|
3594
3721
|
} from "graphql";
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
}
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
}
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3722
|
+
|
|
3723
|
+
// src/commands/register-commands.ts
|
|
3724
|
+
import { RecordType } from "@eide/foir-connect-clients/records";
|
|
3725
|
+
function buildDispatchTable() {
|
|
3726
|
+
return {
|
|
3727
|
+
// ── Models ──────────────────────────────────────────────────
|
|
3728
|
+
models: {
|
|
3729
|
+
models: async (_v, c) => wrapList(
|
|
3730
|
+
await c.models.listModels({
|
|
3731
|
+
limit: num(_v.limit, 50),
|
|
3732
|
+
search: str(_v.search),
|
|
3733
|
+
category: str(_v.category),
|
|
3734
|
+
offset: num(_v.offset, 0)
|
|
3735
|
+
})
|
|
3736
|
+
),
|
|
3737
|
+
modelByKey: async (v, c) => await c.models.getModelByKey(str(v.key)),
|
|
3738
|
+
model: async (v, c) => await c.models.getModel(str(v.id)),
|
|
3739
|
+
createModel: async (v, c) => {
|
|
3740
|
+
const input = v.input;
|
|
3741
|
+
if (!input) throw new Error("Input required (--data or --file)");
|
|
3742
|
+
return await c.models.createModel({
|
|
3743
|
+
key: str(input.key),
|
|
3744
|
+
name: str(input.name),
|
|
3745
|
+
fields: input.fields ?? [],
|
|
3746
|
+
config: input.config,
|
|
3747
|
+
configId: str(input.configId)
|
|
3748
|
+
});
|
|
3749
|
+
},
|
|
3750
|
+
updateModel: async (v, c) => {
|
|
3751
|
+
const input = v.input;
|
|
3752
|
+
if (!input) throw new Error("Input required (--data or --file)");
|
|
3753
|
+
return await c.models.updateModel({
|
|
3754
|
+
id: str(input.id ?? v.id),
|
|
3755
|
+
name: str(input.name),
|
|
3756
|
+
fields: input.fields,
|
|
3757
|
+
config: input.config,
|
|
3758
|
+
changeDescription: str(input.changeDescription)
|
|
3759
|
+
});
|
|
3760
|
+
},
|
|
3761
|
+
deleteModel: async (v, c) => await c.models.deleteModel(str(v.id)),
|
|
3762
|
+
modelVersions: async (v, c) => wrapList(
|
|
3763
|
+
await c.models.listModelVersions(str(v.modelId), {
|
|
3764
|
+
limit: num(v.limit, 50)
|
|
3765
|
+
})
|
|
3766
|
+
)
|
|
3767
|
+
},
|
|
3768
|
+
// ── Records ─────────────────────────────────────────────────
|
|
3769
|
+
records: {
|
|
3770
|
+
records: async (v, c) => wrapList(
|
|
3771
|
+
await c.records.listRecords({
|
|
3772
|
+
modelKey: str(v.modelKey),
|
|
3773
|
+
limit: num(v.limit, 50),
|
|
3774
|
+
offset: num(v.offset, 0),
|
|
3775
|
+
search: str(v.search)
|
|
3776
|
+
})
|
|
3777
|
+
),
|
|
3778
|
+
record: async (v, c) => await c.records.getRecord(str(v.id)),
|
|
3779
|
+
recordByKey: async (v, c) => await c.records.getRecordByKey(str(v.modelKey), str(v.naturalKey)),
|
|
3780
|
+
createRecord: async (v, c) => {
|
|
3781
|
+
const input = v.input;
|
|
3782
|
+
if (!input) throw new Error("Input required (--data or --file)");
|
|
3783
|
+
return await c.records.createRecord({
|
|
3784
|
+
modelKey: str(input.modelKey),
|
|
3785
|
+
naturalKey: str(input.naturalKey),
|
|
3786
|
+
data: input.data,
|
|
3787
|
+
customerId: str(input.customerId),
|
|
3788
|
+
changeDescription: str(input.changeDescription)
|
|
3789
|
+
});
|
|
3790
|
+
},
|
|
3791
|
+
updateRecord: async (v, c) => {
|
|
3792
|
+
const input = v.input;
|
|
3793
|
+
if (!input) throw new Error("Input required (--data or --file)");
|
|
3794
|
+
return await c.records.updateRecord({
|
|
3795
|
+
id: str(input.id ?? v.id),
|
|
3796
|
+
data: input.data ?? input,
|
|
3797
|
+
naturalKey: str(input.naturalKey)
|
|
3798
|
+
});
|
|
3799
|
+
},
|
|
3800
|
+
deleteRecord: async (v, c) => await c.records.deleteRecord(str(v.id)),
|
|
3801
|
+
publishVersion: async (v, c) => await c.records.publishVersion(str(v.versionId)),
|
|
3802
|
+
unpublishRecord: async (v, c) => await c.records.unpublishRecord(str(v.id)),
|
|
3803
|
+
duplicateRecord: async (v, c) => {
|
|
3804
|
+
const input = v.input;
|
|
3805
|
+
return await c.records.duplicateRecord(
|
|
3806
|
+
str(input?.id ?? v.id),
|
|
3807
|
+
str(input?.newNaturalKey)
|
|
3808
|
+
);
|
|
3809
|
+
},
|
|
3810
|
+
recordVersions: async (v, c) => wrapList(
|
|
3811
|
+
await c.records.listRecordVersions(str(v.parentId), {
|
|
3812
|
+
limit: num(v.limit, 50)
|
|
3813
|
+
})
|
|
3814
|
+
),
|
|
3815
|
+
recordVariants: async (v, c) => wrapList(
|
|
3816
|
+
await c.records.listRecordVariants(str(v.recordId), {
|
|
3817
|
+
limit: num(v.limit, 50)
|
|
3818
|
+
})
|
|
3819
|
+
),
|
|
3820
|
+
createVersion: async (v, c) => {
|
|
3821
|
+
const input = v.input;
|
|
3822
|
+
if (!input) throw new Error("Input required (--data or --file)");
|
|
3823
|
+
return await c.records.createVersion(
|
|
3824
|
+
str(input.parentId),
|
|
3825
|
+
input.data,
|
|
3826
|
+
str(input.changeDescription)
|
|
3827
|
+
);
|
|
3828
|
+
},
|
|
3829
|
+
createVariant: async (v, c) => {
|
|
3830
|
+
const input = v.input;
|
|
3831
|
+
if (!input) throw new Error("Input required (--data or --file)");
|
|
3832
|
+
return await c.records.createVariant(
|
|
3833
|
+
str(input.recordId),
|
|
3834
|
+
str(input.variantKey),
|
|
3835
|
+
input.data
|
|
3836
|
+
);
|
|
3683
3837
|
}
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3838
|
+
},
|
|
3839
|
+
// ── Locales ─────────────────────────────────────────────────
|
|
3840
|
+
locales: {
|
|
3841
|
+
locales: async (v, c) => {
|
|
3842
|
+
const resp = await c.settings.listLocales({ limit: num(v.limit, 50) });
|
|
3843
|
+
return { items: resp.locales ?? [], total: resp.total ?? 0 };
|
|
3844
|
+
},
|
|
3845
|
+
locale: async (v, c) => await c.settings.getLocale(str(v.id)),
|
|
3846
|
+
createLocale: async (v, c) => {
|
|
3847
|
+
const input = v.input;
|
|
3848
|
+
if (!input) throw new Error("Input required");
|
|
3849
|
+
return await c.settings.createLocale({
|
|
3850
|
+
locale: str(input.locale),
|
|
3851
|
+
displayName: str(input.displayName),
|
|
3852
|
+
nativeName: str(input.nativeName),
|
|
3853
|
+
isDefault: input.isDefault,
|
|
3854
|
+
isRtl: input.isRtl,
|
|
3855
|
+
fallbackLocale: str(input.fallbackLocale)
|
|
3856
|
+
});
|
|
3857
|
+
},
|
|
3858
|
+
updateLocale: async (v, c) => {
|
|
3859
|
+
const input = v.input;
|
|
3860
|
+
if (!input) throw new Error("Input required");
|
|
3861
|
+
return await c.settings.updateLocale({
|
|
3862
|
+
id: str(input.id ?? v.id),
|
|
3863
|
+
displayName: str(input.displayName),
|
|
3864
|
+
nativeName: str(input.nativeName),
|
|
3865
|
+
isDefault: input.isDefault,
|
|
3866
|
+
isActive: input.isActive,
|
|
3867
|
+
isRtl: input.isRtl,
|
|
3868
|
+
fallbackLocale: str(input.fallbackLocale)
|
|
3869
|
+
});
|
|
3870
|
+
},
|
|
3871
|
+
deleteLocale: async (v, c) => await c.settings.deleteLocale(str(v.id))
|
|
3872
|
+
},
|
|
3873
|
+
// ── Segments ────────────────────────────────────────────────
|
|
3874
|
+
segments: {
|
|
3875
|
+
segments: async (v, c) => {
|
|
3876
|
+
const resp = await c.segments.listSegments({ limit: num(v.limit, 50) });
|
|
3877
|
+
return { items: resp.segments ?? [], total: resp.total ?? 0 };
|
|
3878
|
+
},
|
|
3879
|
+
segment: async (v, c) => await c.segments.getSegment(str(v.id)),
|
|
3880
|
+
segmentByKey: async (v, c) => await c.segments.getSegmentByKey(str(v.key)),
|
|
3881
|
+
createSegment: async (v, c) => {
|
|
3882
|
+
const input = v.input;
|
|
3883
|
+
if (!input) throw new Error("Input required");
|
|
3884
|
+
return await c.segments.createSegment({
|
|
3885
|
+
key: str(input.key),
|
|
3886
|
+
name: str(input.name),
|
|
3887
|
+
description: str(input.description),
|
|
3888
|
+
rules: input.rules,
|
|
3889
|
+
evaluationMode: str(input.evaluationMode),
|
|
3890
|
+
isActive: input.isActive
|
|
3891
|
+
});
|
|
3892
|
+
},
|
|
3893
|
+
updateSegment: async (v, c) => {
|
|
3894
|
+
const input = v.input;
|
|
3895
|
+
if (!input) throw new Error("Input required");
|
|
3896
|
+
return await c.segments.updateSegment({
|
|
3897
|
+
id: str(input.id ?? v.id),
|
|
3898
|
+
name: str(input.name),
|
|
3899
|
+
description: str(input.description),
|
|
3900
|
+
rules: input.rules,
|
|
3901
|
+
evaluationMode: str(input.evaluationMode),
|
|
3902
|
+
isActive: input.isActive
|
|
3903
|
+
});
|
|
3904
|
+
},
|
|
3905
|
+
deleteSegment: async (v, c) => await c.segments.deleteSegment(str(v.id)),
|
|
3906
|
+
previewSegmentRules: async (v, c) => {
|
|
3907
|
+
const input = v.rules;
|
|
3908
|
+
return await c.segments.previewSegmentRules(input);
|
|
3909
|
+
},
|
|
3910
|
+
testSegmentEvaluation: async (v, c) => await c.segments.testSegmentEvaluation(
|
|
3911
|
+
str(v.segmentId),
|
|
3912
|
+
str(v.customerId)
|
|
3913
|
+
)
|
|
3914
|
+
},
|
|
3915
|
+
// ── Experiments ─────────────────────────────────────────────
|
|
3916
|
+
experiments: {
|
|
3917
|
+
experiments: async (v, c) => {
|
|
3918
|
+
const resp = await c.experiments.listExperiments({
|
|
3919
|
+
limit: num(v.limit, 50)
|
|
3920
|
+
});
|
|
3921
|
+
return { items: resp.experiments ?? [], total: resp.total ?? 0 };
|
|
3922
|
+
},
|
|
3923
|
+
experiment: async (v, c) => await c.experiments.getExperiment(str(v.id)),
|
|
3924
|
+
experimentByKey: async (v, c) => await c.experiments.getExperimentByKey(str(v.key)),
|
|
3925
|
+
createExperiment: async (v, c) => {
|
|
3926
|
+
const input = v.input;
|
|
3927
|
+
if (!input) throw new Error("Input required");
|
|
3928
|
+
return await c.experiments.createExperiment({
|
|
3929
|
+
key: str(input.key),
|
|
3930
|
+
name: str(input.name),
|
|
3931
|
+
description: str(input.description),
|
|
3932
|
+
targeting: input.targeting,
|
|
3933
|
+
controlPercent: input.controlPercent,
|
|
3934
|
+
variants: input.variants
|
|
3935
|
+
});
|
|
3936
|
+
},
|
|
3937
|
+
updateExperiment: async (v, c) => {
|
|
3938
|
+
const input = v.input;
|
|
3939
|
+
if (!input) throw new Error("Input required");
|
|
3940
|
+
return await c.experiments.updateExperiment({
|
|
3941
|
+
id: str(input.id ?? v.id),
|
|
3942
|
+
name: str(input.name),
|
|
3943
|
+
description: str(input.description),
|
|
3944
|
+
targeting: input.targeting,
|
|
3945
|
+
controlPercent: input.controlPercent,
|
|
3946
|
+
variants: input.variants
|
|
3947
|
+
});
|
|
3948
|
+
},
|
|
3949
|
+
deleteExperiment: async (v, c) => await c.experiments.deleteExperiment(str(v.id)),
|
|
3950
|
+
startExperiment: async (v, c) => await c.experiments.startExperiment(str(v.experimentId)),
|
|
3951
|
+
pauseExperiment: async (v, c) => await c.experiments.pauseExperiment(str(v.experimentId)),
|
|
3952
|
+
resumeExperiment: async (v, c) => await c.experiments.resumeExperiment(str(v.experimentId)),
|
|
3953
|
+
endExperiment: async (v, c) => await c.experiments.endExperiment(str(v.experimentId)),
|
|
3954
|
+
experimentStats: async (v, c) => await c.experiments.getExperimentStats(str(v.experimentId))
|
|
3955
|
+
},
|
|
3956
|
+
// ── Settings ────────────────────────────────────────────────
|
|
3957
|
+
settings: {
|
|
3958
|
+
allSettings: async (v, c) => await c.settings.getSettings({
|
|
3959
|
+
category: str(v.category),
|
|
3960
|
+
key: str(v.key)
|
|
3961
|
+
}),
|
|
3962
|
+
setting: async (v, c) => {
|
|
3963
|
+
const settings = await c.settings.getSettings({ key: str(v.key) });
|
|
3964
|
+
return settings[0] ?? null;
|
|
3965
|
+
},
|
|
3966
|
+
setSetting: async (v, c) => {
|
|
3967
|
+
const input = v.input;
|
|
3968
|
+
if (!input) throw new Error("Input required");
|
|
3969
|
+
return await c.settings.updateSetting({
|
|
3970
|
+
key: str(input.key),
|
|
3971
|
+
value: input.value ?? input
|
|
3972
|
+
});
|
|
3704
3973
|
}
|
|
3705
|
-
}
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3974
|
+
},
|
|
3975
|
+
// ── API Keys ────────────────────────────────────────────────
|
|
3976
|
+
"api-keys": {
|
|
3977
|
+
listApiKeys: async (v, c) => wrapList(await c.identity.listApiKeys({ limit: num(v.limit, 50) })),
|
|
3978
|
+
createApiKey: async (v, c) => {
|
|
3979
|
+
const input = v.input;
|
|
3980
|
+
if (!input) throw new Error("Input required");
|
|
3981
|
+
return await c.identity.createApiKey({
|
|
3982
|
+
name: str(input.name),
|
|
3983
|
+
keyType: input.keyType,
|
|
3984
|
+
rateLimitPerHour: input.rateLimitPerHour
|
|
3985
|
+
});
|
|
3986
|
+
},
|
|
3987
|
+
rotateApiKey: async (v, c) => await c.identity.rotateApiKey(str(v.id)),
|
|
3988
|
+
revokeApiKey: async (v, c) => await c.identity.revokeApiKey(str(v.id))
|
|
3989
|
+
},
|
|
3990
|
+
// ── Auth Providers ──────────────────────────────────────────
|
|
3991
|
+
"auth-providers": {
|
|
3992
|
+
customerAuthProviders: async (v, c) => {
|
|
3993
|
+
const resp = await c.identity.listAuthProviders({
|
|
3994
|
+
limit: num(v.limit, 50)
|
|
3995
|
+
});
|
|
3996
|
+
return { items: resp.items, total: resp.total };
|
|
3997
|
+
},
|
|
3998
|
+
customerAuthProvider: async (v, c) => await c.identity.getAuthProvider(str(v.id)),
|
|
3999
|
+
createCustomerAuthProvider: async (v, c) => {
|
|
4000
|
+
const input = v.input;
|
|
4001
|
+
if (!input) throw new Error("Input required");
|
|
4002
|
+
return await c.identity.createAuthProvider({
|
|
4003
|
+
key: str(input.key),
|
|
4004
|
+
name: str(input.name),
|
|
4005
|
+
type: str(input.type),
|
|
4006
|
+
config: input.config,
|
|
4007
|
+
enabled: input.enabled,
|
|
4008
|
+
isDefault: input.isDefault,
|
|
4009
|
+
priority: input.priority
|
|
4010
|
+
});
|
|
4011
|
+
},
|
|
4012
|
+
updateCustomerAuthProvider: async (v, c) => {
|
|
4013
|
+
const input = v.input;
|
|
4014
|
+
if (!input) throw new Error("Input required");
|
|
4015
|
+
return await c.identity.updateAuthProvider({
|
|
4016
|
+
id: str(input.id ?? v.id),
|
|
4017
|
+
name: str(input.name),
|
|
4018
|
+
config: input.config,
|
|
4019
|
+
enabled: input.enabled,
|
|
4020
|
+
isDefault: input.isDefault,
|
|
4021
|
+
priority: input.priority
|
|
4022
|
+
});
|
|
4023
|
+
},
|
|
4024
|
+
deleteCustomerAuthProvider: async (v, c) => await c.identity.deleteAuthProvider(str(v.id))
|
|
4025
|
+
},
|
|
4026
|
+
// ── Configs ─────────────────────────────────────────────────
|
|
4027
|
+
configs: {
|
|
4028
|
+
configs: async (v, c) => {
|
|
4029
|
+
const resp = await c.configs.listConfigs({ limit: num(v.limit, 50) });
|
|
4030
|
+
return { items: resp.configs ?? [], total: resp.total ?? 0 };
|
|
4031
|
+
},
|
|
4032
|
+
config: async (v, c) => await c.configs.getConfig(str(v.id)),
|
|
4033
|
+
configByKey: async (v, c) => await c.configs.getConfigByKey(str(v.key)),
|
|
4034
|
+
registerConfig: async (v, c) => {
|
|
4035
|
+
const input = v.input;
|
|
4036
|
+
if (!input) throw new Error("Input required");
|
|
4037
|
+
return await c.configs.createConfig({
|
|
4038
|
+
key: str(input.key),
|
|
4039
|
+
configType: str(input.configType),
|
|
4040
|
+
name: str(input.name),
|
|
4041
|
+
description: str(input.description),
|
|
4042
|
+
config: input.config,
|
|
4043
|
+
enabled: input.enabled,
|
|
4044
|
+
direction: str(input.direction)
|
|
4045
|
+
});
|
|
4046
|
+
},
|
|
4047
|
+
applyConfig: async (v, c) => {
|
|
4048
|
+
const input = v.input;
|
|
4049
|
+
if (!input) throw new Error("Input required");
|
|
4050
|
+
return await c.configs.applyConfig(
|
|
4051
|
+
str(input.key ?? input.configKey),
|
|
4052
|
+
input
|
|
4053
|
+
);
|
|
4054
|
+
},
|
|
4055
|
+
unregisterConfig: async (v, c) => await c.configs.deleteConfig(str(v.id)),
|
|
4056
|
+
triggerConfigSync: async (v, _c) => {
|
|
4057
|
+
console.log(
|
|
4058
|
+
chalk9.yellow(
|
|
4059
|
+
`Config sync trigger for ${str(v.configId)} is not yet available via ConnectRPC.`
|
|
4060
|
+
)
|
|
4061
|
+
);
|
|
4062
|
+
return { success: false };
|
|
3725
4063
|
}
|
|
3726
|
-
}
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
const
|
|
3737
|
-
|
|
3738
|
-
}
|
|
3739
|
-
|
|
3740
|
-
|
|
4064
|
+
},
|
|
4065
|
+
// ── Customers ───────────────────────────────────────────────
|
|
4066
|
+
customers: {
|
|
4067
|
+
customers: async (v, c) => wrapList(
|
|
4068
|
+
await c.identity.listCustomers({
|
|
4069
|
+
limit: num(v.limit, 50),
|
|
4070
|
+
search: str(v.search)
|
|
4071
|
+
})
|
|
4072
|
+
),
|
|
4073
|
+
customer: async (v, c) => {
|
|
4074
|
+
const result = await c.identity.getCustomer(str(v.id));
|
|
4075
|
+
return result.customer;
|
|
4076
|
+
},
|
|
4077
|
+
createCustomer: async (v, c) => {
|
|
4078
|
+
const input = v.input;
|
|
4079
|
+
if (!input) throw new Error("Input required");
|
|
4080
|
+
const result = await c.identity.createCustomer({
|
|
4081
|
+
email: str(input.email),
|
|
4082
|
+
password: str(input.password)
|
|
4083
|
+
});
|
|
4084
|
+
return result.customer;
|
|
4085
|
+
},
|
|
4086
|
+
updateCustomer: async (v, c) => {
|
|
4087
|
+
const input = v.input;
|
|
4088
|
+
if (!input) throw new Error("Input required");
|
|
4089
|
+
const result = await c.identity.updateCustomer({
|
|
4090
|
+
id: str(input.id ?? v.id),
|
|
4091
|
+
email: str(input.email),
|
|
4092
|
+
status: input.status
|
|
4093
|
+
});
|
|
4094
|
+
return result.customer;
|
|
4095
|
+
},
|
|
4096
|
+
deleteCustomer: async (v, c) => await c.identity.deleteCustomer(str(v.id)),
|
|
4097
|
+
suspendCustomer: async (v, c) => {
|
|
4098
|
+
const result = await c.identity.suspendCustomer(str(v.id));
|
|
4099
|
+
return result.customer;
|
|
3741
4100
|
}
|
|
3742
4101
|
}
|
|
3743
|
-
const varPart = varDecls ? `(${varDecls})` : "";
|
|
3744
|
-
const argPart = fieldArgs ? `(${fieldArgs})` : "";
|
|
3745
|
-
return `${opType} ${opName}${varPart} { ${entry.operation}${argPart} ${selectionSet} }`;
|
|
3746
|
-
}
|
|
3747
|
-
function getInputFields(operationName, operationType, inputArgName) {
|
|
3748
|
-
const field = getField(operationName, operationType);
|
|
3749
|
-
if (!field) return [];
|
|
3750
|
-
const argName = inputArgName ?? "input";
|
|
3751
|
-
const arg = field.args.find((a) => a.name === argName);
|
|
3752
|
-
if (!arg) return [];
|
|
3753
|
-
const namedType = unwrapType(arg.type);
|
|
3754
|
-
if (!isInputObjectType(namedType)) return [];
|
|
3755
|
-
const fields = namedType.getFields();
|
|
3756
|
-
return Object.entries(fields).map(([name, f]) => ({
|
|
3757
|
-
name,
|
|
3758
|
-
type: typeToString(f.type),
|
|
3759
|
-
required: isNonNullType(f.type)
|
|
3760
|
-
}));
|
|
3761
|
-
}
|
|
3762
|
-
function getCompletions(partial, commandNames) {
|
|
3763
|
-
return commandNames.filter(
|
|
3764
|
-
(name) => name.toLowerCase().startsWith(partial.toLowerCase())
|
|
3765
|
-
);
|
|
3766
|
-
}
|
|
3767
|
-
return {
|
|
3768
|
-
buildQuery,
|
|
3769
|
-
getOperationArgs,
|
|
3770
|
-
coerceArgs,
|
|
3771
|
-
getInputFields,
|
|
3772
|
-
getCompletions
|
|
3773
4102
|
};
|
|
3774
4103
|
}
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
var __filename = fileURLToPath(import.meta.url);
|
|
3778
|
-
var __dirname = dirname4(__filename);
|
|
3779
|
-
function loadSchemaSDL() {
|
|
3780
|
-
const bundledPath = resolve5(__dirname, "schema.graphql");
|
|
3781
|
-
try {
|
|
3782
|
-
return readFileSync(bundledPath, "utf-8");
|
|
3783
|
-
} catch {
|
|
3784
|
-
const monorepoPath = resolve5(
|
|
3785
|
-
__dirname,
|
|
3786
|
-
"../../../graphql-core/schema.graphql"
|
|
3787
|
-
);
|
|
3788
|
-
try {
|
|
3789
|
-
return readFileSync(monorepoPath, "utf-8");
|
|
3790
|
-
} catch {
|
|
3791
|
-
throw new Error(
|
|
3792
|
-
"Could not find schema.graphql. Try reinstalling @eide/foir-cli."
|
|
3793
|
-
);
|
|
3794
|
-
}
|
|
3795
|
-
}
|
|
4104
|
+
function str(v) {
|
|
4105
|
+
return v != null ? String(v) : void 0;
|
|
3796
4106
|
}
|
|
3797
|
-
function
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
return { data: val, total: obj.total };
|
|
3805
|
-
}
|
|
3806
|
-
}
|
|
3807
|
-
}
|
|
3808
|
-
return { data: raw };
|
|
4107
|
+
function num(v, fallback) {
|
|
4108
|
+
if (v == null) return fallback;
|
|
4109
|
+
const n = Number(v);
|
|
4110
|
+
return Number.isNaN(n) ? fallback : n;
|
|
4111
|
+
}
|
|
4112
|
+
function wrapList(result) {
|
|
4113
|
+
return result;
|
|
3809
4114
|
}
|
|
3810
4115
|
function toCliColumns(columns) {
|
|
3811
4116
|
if (!columns) return void 0;
|
|
@@ -3824,8 +4129,8 @@ function toCliColumns(columns) {
|
|
|
3824
4129
|
break;
|
|
3825
4130
|
case "truncate":
|
|
3826
4131
|
cliCol.format = (v) => {
|
|
3827
|
-
const
|
|
3828
|
-
return
|
|
4132
|
+
const s = String(v ?? "");
|
|
4133
|
+
return s.length > 40 ? s.slice(0, 39) + "\u2026" : s;
|
|
3829
4134
|
};
|
|
3830
4135
|
break;
|
|
3831
4136
|
case "join":
|
|
@@ -3833,27 +4138,37 @@ function toCliColumns(columns) {
|
|
|
3833
4138
|
break;
|
|
3834
4139
|
case "bytes":
|
|
3835
4140
|
cliCol.format = (v) => {
|
|
3836
|
-
const
|
|
3837
|
-
if (
|
|
3838
|
-
if (
|
|
3839
|
-
return `${(
|
|
4141
|
+
const n = Number(v);
|
|
4142
|
+
if (n < 1024) return `${n} B`;
|
|
4143
|
+
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
|
|
4144
|
+
return `${(n / (1024 * 1024)).toFixed(1)} MB`;
|
|
3840
4145
|
};
|
|
3841
4146
|
break;
|
|
3842
4147
|
}
|
|
3843
4148
|
return cliCol;
|
|
3844
4149
|
});
|
|
3845
4150
|
}
|
|
3846
|
-
function
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
4151
|
+
function autoColumns(items) {
|
|
4152
|
+
if (items.length === 0) return [];
|
|
4153
|
+
const first = items[0];
|
|
4154
|
+
return Object.keys(first).filter((k) => k !== "__typename" && k !== "$typeName" && k !== "$unknown").slice(0, 6).map((key) => ({
|
|
4155
|
+
key,
|
|
4156
|
+
header: key,
|
|
4157
|
+
width: 20
|
|
4158
|
+
}));
|
|
4159
|
+
}
|
|
4160
|
+
var DISPATCH = buildDispatchTable();
|
|
4161
|
+
async function executeCommand(entry, variables, client) {
|
|
4162
|
+
const groupHandlers = DISPATCH[entry.group];
|
|
4163
|
+
const handler = groupHandlers?.[entry.operation];
|
|
4164
|
+
if (!handler) {
|
|
4165
|
+
throw new Error(
|
|
4166
|
+
`Command not yet migrated to ConnectRPC: ${entry.group}.${entry.operation}`
|
|
3854
4167
|
);
|
|
3855
|
-
return;
|
|
3856
4168
|
}
|
|
4169
|
+
return handler(variables, client);
|
|
4170
|
+
}
|
|
4171
|
+
function registerDynamicCommands(program2, globalOpts) {
|
|
3857
4172
|
const groups = /* @__PURE__ */ new Map();
|
|
3858
4173
|
for (const cmd of COMMANDS) {
|
|
3859
4174
|
if (!groups.has(cmd.group)) groups.set(cmd.group, []);
|
|
@@ -3862,42 +4177,15 @@ function registerDynamicCommands(program2, globalOpts) {
|
|
|
3862
4177
|
for (const [groupName, entries] of groups) {
|
|
3863
4178
|
const group = program2.command(groupName).description(`Manage ${groupName}`);
|
|
3864
4179
|
for (const entry of entries) {
|
|
4180
|
+
if (!DISPATCH[entry.group]?.[entry.operation]) continue;
|
|
3865
4181
|
let cmd = group.command(entry.name).description(entry.description);
|
|
3866
4182
|
for (const pos of entry.positionalArgs ?? []) {
|
|
3867
4183
|
cmd = cmd.argument(`<${pos.name}>`, pos.description ?? pos.name);
|
|
3868
4184
|
}
|
|
3869
|
-
|
|
3870
|
-
entry.operation,
|
|
3871
|
-
entry.operationType
|
|
3872
|
-
);
|
|
3873
|
-
for (const arg of schemaArgs) {
|
|
3874
|
-
if (entry.positionalArgs?.some((p) => p.graphqlArg === arg.name))
|
|
3875
|
-
continue;
|
|
3876
|
-
if (entry.acceptsInput && arg.name === (entry.inputArgName ?? "input"))
|
|
3877
|
-
continue;
|
|
3878
|
-
const reqStr = arg.required ? " (required)" : "";
|
|
3879
|
-
cmd = cmd.option(`--${arg.name} <value>`, `${arg.name}${reqStr}`);
|
|
3880
|
-
}
|
|
4185
|
+
cmd = cmd.option("--limit <n>", "Max results");
|
|
3881
4186
|
if (entry.acceptsInput) {
|
|
3882
4187
|
cmd = cmd.option("-d, --data <json>", "Data as JSON");
|
|
3883
4188
|
cmd = cmd.option("-f, --file <path>", "Read data from file");
|
|
3884
|
-
const inputFields = engine.getInputFields(
|
|
3885
|
-
entry.operation,
|
|
3886
|
-
entry.operationType,
|
|
3887
|
-
entry.inputArgName
|
|
3888
|
-
);
|
|
3889
|
-
if (inputFields.length > 0) {
|
|
3890
|
-
const required = inputFields.filter((f) => f.required);
|
|
3891
|
-
const optional = inputFields.filter((f) => !f.required);
|
|
3892
|
-
let fieldHelp = "\nInput fields:";
|
|
3893
|
-
if (required.length > 0) {
|
|
3894
|
-
fieldHelp += "\n Required: " + required.map((f) => `${f.name} (${f.type})`).join(", ");
|
|
3895
|
-
}
|
|
3896
|
-
if (optional.length > 0) {
|
|
3897
|
-
fieldHelp += "\n Optional: " + optional.map((f) => `${f.name} (${f.type})`).join(", ");
|
|
3898
|
-
}
|
|
3899
|
-
cmd = cmd.addHelpText("after", fieldHelp);
|
|
3900
|
-
}
|
|
3901
4189
|
}
|
|
3902
4190
|
for (const cf of entry.customFlags ?? []) {
|
|
3903
4191
|
cmd = cmd.option(cf.flag, cf.description);
|
|
@@ -3908,7 +4196,7 @@ function registerDynamicCommands(program2, globalOpts) {
|
|
|
3908
4196
|
cmd.action(
|
|
3909
4197
|
withErrorHandler(globalOpts, async (...actionArgs) => {
|
|
3910
4198
|
const opts = globalOpts();
|
|
3911
|
-
const client = await
|
|
4199
|
+
const client = await createPlatformClient(opts);
|
|
3912
4200
|
const variables = {};
|
|
3913
4201
|
const positionals = entry.positionalArgs ?? [];
|
|
3914
4202
|
for (let i = 0; i < positionals.length; i++) {
|
|
@@ -3921,27 +4209,27 @@ function registerDynamicCommands(program2, globalOpts) {
|
|
|
3921
4209
|
const flags = actionArgs[positionals.length] ?? {};
|
|
3922
4210
|
const customFlagNames = new Set(
|
|
3923
4211
|
(entry.customFlags ?? []).map(
|
|
3924
|
-
(cf) => cf.flag.replace(/ <.*>$/, "").replace(/^--/, "").replace(/-([a-z])/g, (_,
|
|
4212
|
+
(cf) => cf.flag.replace(/ <.*>$/, "").replace(/^--/, "").replace(/-([a-z])/g, (_, ch) => ch.toUpperCase())
|
|
3925
4213
|
)
|
|
3926
4214
|
);
|
|
3927
|
-
const rawFlags = {};
|
|
3928
4215
|
for (const [key, val] of Object.entries(flags)) {
|
|
3929
|
-
if (key === "data" || key === "file" || key === "confirm")
|
|
3930
|
-
|
|
3931
|
-
|
|
4216
|
+
if (key === "data" || key === "file" || key === "confirm" || key === "limit")
|
|
4217
|
+
continue;
|
|
4218
|
+
if (customFlagNames.has(key)) {
|
|
4219
|
+
variables[key] = val;
|
|
4220
|
+
continue;
|
|
4221
|
+
}
|
|
4222
|
+
variables[key] = val;
|
|
4223
|
+
}
|
|
4224
|
+
if (flags.limit) {
|
|
4225
|
+
variables.limit = flags.limit;
|
|
3932
4226
|
}
|
|
3933
|
-
const coerced = engine.coerceArgs(
|
|
3934
|
-
entry.operation,
|
|
3935
|
-
entry.operationType,
|
|
3936
|
-
rawFlags
|
|
3937
|
-
);
|
|
3938
|
-
Object.assign(variables, coerced);
|
|
3939
4227
|
if (flags.dir && entry.acceptsInput) {
|
|
3940
4228
|
const dirPath = resolve5(String(flags.dir));
|
|
3941
4229
|
const files = readdirSync(dirPath).filter((f) => /\.(json|ts|js|mjs)$/.test(f)).sort();
|
|
3942
4230
|
if (files.length === 0) {
|
|
3943
4231
|
console.error(
|
|
3944
|
-
chalk9.yellow(
|
|
4232
|
+
chalk9.yellow(`No .json/.ts/.js files found in ${dirPath}`)
|
|
3945
4233
|
);
|
|
3946
4234
|
return;
|
|
3947
4235
|
}
|
|
@@ -3955,8 +4243,7 @@ function registerDynamicCommands(program2, globalOpts) {
|
|
|
3955
4243
|
const fileVars = { ...variables, [argName]: fileData };
|
|
3956
4244
|
const label = fileData.key ?? fileData.name ?? file;
|
|
3957
4245
|
try {
|
|
3958
|
-
|
|
3959
|
-
await client.request(q, fileVars);
|
|
4246
|
+
await executeCommand(entry, fileVars, client);
|
|
3960
4247
|
created++;
|
|
3961
4248
|
if (!(opts.json || opts.jsonl || opts.quiet)) {
|
|
3962
4249
|
success(`Created ${label}`);
|
|
@@ -3975,8 +4262,7 @@ function registerDynamicCommands(program2, globalOpts) {
|
|
|
3975
4262
|
[updateEntry.positionalArgs[0].graphqlArg]: fileData.key
|
|
3976
4263
|
} : {}
|
|
3977
4264
|
};
|
|
3978
|
-
|
|
3979
|
-
await client.request(uq, updateVars);
|
|
4265
|
+
await executeCommand(updateEntry, updateVars, client);
|
|
3980
4266
|
updated++;
|
|
3981
4267
|
if (!(opts.json || opts.jsonl || opts.quiet)) {
|
|
3982
4268
|
success(`Updated ${label}`);
|
|
@@ -4010,19 +4296,13 @@ function registerDynamicCommands(program2, globalOpts) {
|
|
|
4010
4296
|
data: flags.data,
|
|
4011
4297
|
file: flags.file
|
|
4012
4298
|
});
|
|
4013
|
-
|
|
4014
|
-
entry.operation,
|
|
4015
|
-
entry.operationType,
|
|
4016
|
-
entry.inputArgName
|
|
4017
|
-
);
|
|
4018
|
-
const fieldNames = new Set(inputFields.map((f) => f.name));
|
|
4019
|
-
if (fieldNames.has("projectId") && !inputData.projectId || fieldNames.has("tenantId") && !inputData.tenantId) {
|
|
4299
|
+
if (!inputData.projectId || !inputData.tenantId) {
|
|
4020
4300
|
const resolved = await resolveProjectContext(opts);
|
|
4021
4301
|
if (resolved) {
|
|
4022
|
-
if (
|
|
4302
|
+
if (!inputData.projectId) {
|
|
4023
4303
|
inputData.projectId = resolved.project.id;
|
|
4024
4304
|
}
|
|
4025
|
-
if (
|
|
4305
|
+
if (!inputData.tenantId) {
|
|
4026
4306
|
inputData.tenantId = resolved.project.tenantId;
|
|
4027
4307
|
}
|
|
4028
4308
|
}
|
|
@@ -4053,12 +4333,7 @@ function registerDynamicCommands(program2, globalOpts) {
|
|
|
4053
4333
|
if (flags.modelKey) {
|
|
4054
4334
|
variables.modelKey = String(flags.modelKey);
|
|
4055
4335
|
}
|
|
4056
|
-
const
|
|
4057
|
-
const result2 = await client.request(
|
|
4058
|
-
queryStr2,
|
|
4059
|
-
variables
|
|
4060
|
-
);
|
|
4061
|
-
const { data: data2 } = extractResult(result2, altEntry.operation);
|
|
4336
|
+
const data2 = await executeCommand(altEntry, variables, client);
|
|
4062
4337
|
formatOutput(data2, opts);
|
|
4063
4338
|
return;
|
|
4064
4339
|
}
|
|
@@ -4075,13 +4350,10 @@ function registerDynamicCommands(program2, globalOpts) {
|
|
|
4075
4350
|
if (entry.group === "records" && entry.name === "publish" && variables.versionId) {
|
|
4076
4351
|
const versionIdValue = String(variables.versionId);
|
|
4077
4352
|
if (flags.modelKey) {
|
|
4078
|
-
const
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
naturalKey: versionIdValue
|
|
4083
|
-
});
|
|
4084
|
-
const record = lookupResult.recordByKey;
|
|
4353
|
+
const record = await client.records.getRecordByKey(
|
|
4354
|
+
String(flags.modelKey),
|
|
4355
|
+
versionIdValue
|
|
4356
|
+
);
|
|
4085
4357
|
if (!record?.currentVersionId) {
|
|
4086
4358
|
throw new Error(
|
|
4087
4359
|
`No current version found for record "${versionIdValue}"`
|
|
@@ -4094,23 +4366,18 @@ function registerDynamicCommands(program2, globalOpts) {
|
|
|
4094
4366
|
);
|
|
4095
4367
|
} else {
|
|
4096
4368
|
try {
|
|
4097
|
-
const
|
|
4098
|
-
|
|
4099
|
-
id: versionIdValue
|
|
4100
|
-
});
|
|
4101
|
-
const record = lookupResult.record;
|
|
4102
|
-
if (record?.recordType === "record" && record?.currentVersionId) {
|
|
4369
|
+
const record = await client.records.getRecord(versionIdValue);
|
|
4370
|
+
if (record?.recordType === RecordType.RECORD && record?.currentVersionId) {
|
|
4103
4371
|
variables.versionId = record.currentVersionId;
|
|
4104
4372
|
}
|
|
4105
4373
|
} catch {
|
|
4106
4374
|
}
|
|
4107
4375
|
}
|
|
4108
4376
|
}
|
|
4109
|
-
|
|
4110
|
-
let result;
|
|
4377
|
+
let data;
|
|
4111
4378
|
let usedUpdate = false;
|
|
4112
4379
|
try {
|
|
4113
|
-
|
|
4380
|
+
data = await executeCommand(entry, variables, client);
|
|
4114
4381
|
} catch (createErr) {
|
|
4115
4382
|
if (flags.upsert && entry.name === "create") {
|
|
4116
4383
|
const updateEntry = COMMANDS.find(
|
|
@@ -4126,8 +4393,7 @@ function registerDynamicCommands(program2, globalOpts) {
|
|
|
4126
4393
|
[updateEntry.positionalArgs[0].graphqlArg]: inputData.key
|
|
4127
4394
|
} : {}
|
|
4128
4395
|
};
|
|
4129
|
-
|
|
4130
|
-
result = await client.request(uq, updateVars);
|
|
4396
|
+
data = await executeCommand(updateEntry, updateVars, client);
|
|
4131
4397
|
usedUpdate = true;
|
|
4132
4398
|
} else {
|
|
4133
4399
|
throw createErr;
|
|
@@ -4139,8 +4405,14 @@ function registerDynamicCommands(program2, globalOpts) {
|
|
|
4139
4405
|
const activeEntry = usedUpdate ? COMMANDS.find(
|
|
4140
4406
|
(c) => c.group === entry.group && c.name === "update"
|
|
4141
4407
|
) ?? entry : entry;
|
|
4142
|
-
|
|
4143
|
-
|
|
4408
|
+
let displayData = data;
|
|
4409
|
+
let total;
|
|
4410
|
+
if (data && typeof data === "object" && "items" in data) {
|
|
4411
|
+
const listResult = data;
|
|
4412
|
+
displayData = listResult.items;
|
|
4413
|
+
total = listResult.total;
|
|
4414
|
+
}
|
|
4415
|
+
const responseData = displayData && typeof displayData === "object" && !Array.isArray(displayData) ? displayData : void 0;
|
|
4144
4416
|
const displayEntry = usedUpdate ? activeEntry : entry;
|
|
4145
4417
|
if (displayEntry.scalarResult) {
|
|
4146
4418
|
if (!(opts.json || opts.jsonl || opts.quiet)) {
|
|
@@ -4148,16 +4420,16 @@ function registerDynamicCommands(program2, globalOpts) {
|
|
|
4148
4420
|
success(displayEntry.successMessage, responseData);
|
|
4149
4421
|
}
|
|
4150
4422
|
} else {
|
|
4151
|
-
formatOutput(
|
|
4423
|
+
formatOutput(displayData, opts);
|
|
4152
4424
|
}
|
|
4153
|
-
} else if (Array.isArray(
|
|
4425
|
+
} else if (Array.isArray(displayData)) {
|
|
4154
4426
|
const cliColumns = toCliColumns(displayEntry.columns);
|
|
4155
|
-
formatList(
|
|
4156
|
-
columns: cliColumns ?? autoColumns(
|
|
4427
|
+
formatList(displayData, opts, {
|
|
4428
|
+
columns: cliColumns ?? autoColumns(displayData),
|
|
4157
4429
|
total
|
|
4158
4430
|
});
|
|
4159
4431
|
} else {
|
|
4160
|
-
formatOutput(
|
|
4432
|
+
formatOutput(displayData, opts);
|
|
4161
4433
|
if (displayEntry.successMessage && !(opts.json || opts.jsonl || opts.quiet)) {
|
|
4162
4434
|
success(displayEntry.successMessage, responseData);
|
|
4163
4435
|
}
|
|
@@ -4167,15 +4439,14 @@ function registerDynamicCommands(program2, globalOpts) {
|
|
|
4167
4439
|
const record = responseData.record;
|
|
4168
4440
|
const versionId = version2?.id ?? record?.currentVersionId;
|
|
4169
4441
|
if (versionId) {
|
|
4170
|
-
|
|
4171
|
-
await client.request(publishQuery, { versionId });
|
|
4442
|
+
await client.records.publishVersion(versionId);
|
|
4172
4443
|
if (!(opts.json || opts.jsonl || opts.quiet)) {
|
|
4173
4444
|
success("Published version {id}", { id: versionId });
|
|
4174
4445
|
}
|
|
4175
4446
|
} else if (!(opts.json || opts.jsonl || opts.quiet)) {
|
|
4176
4447
|
console.error(
|
|
4177
4448
|
chalk9.yellow(
|
|
4178
|
-
"
|
|
4449
|
+
"Could not auto-publish: no version found in response"
|
|
4179
4450
|
)
|
|
4180
4451
|
);
|
|
4181
4452
|
}
|
|
@@ -4185,20 +4456,11 @@ function registerDynamicCommands(program2, globalOpts) {
|
|
|
4185
4456
|
}
|
|
4186
4457
|
}
|
|
4187
4458
|
}
|
|
4188
|
-
function autoColumns(items) {
|
|
4189
|
-
if (items.length === 0) return [];
|
|
4190
|
-
const first = items[0];
|
|
4191
|
-
return Object.keys(first).filter((k) => k !== "__typename").slice(0, 6).map((key) => ({
|
|
4192
|
-
key,
|
|
4193
|
-
header: key,
|
|
4194
|
-
width: 20
|
|
4195
|
-
}));
|
|
4196
|
-
}
|
|
4197
4459
|
|
|
4198
4460
|
// src/cli.ts
|
|
4199
|
-
var
|
|
4200
|
-
var
|
|
4201
|
-
config({ path: resolve6(
|
|
4461
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
4462
|
+
var __dirname = dirname4(__filename);
|
|
4463
|
+
config({ path: resolve6(__dirname, "../.env.local") });
|
|
4202
4464
|
var require2 = createRequire(import.meta.url);
|
|
4203
4465
|
var { version } = require2("../package.json");
|
|
4204
4466
|
var program = new Command();
|