@eide/foir-cli 0.1.38 → 0.1.40
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 +877 -37
- package/dist/schema.graphql +51 -0
- package/package.json +4 -4
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { config } from "dotenv";
|
|
5
|
-
import { resolve as
|
|
5
|
+
import { resolve as resolve7, dirname as dirname6 } from "path";
|
|
6
6
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7
7
|
import { createRequire } from "module";
|
|
8
8
|
import { Command } from "commander";
|
|
@@ -127,6 +127,22 @@ function getGraphQLEndpoint(apiUrl) {
|
|
|
127
127
|
|
|
128
128
|
// src/lib/errors.ts
|
|
129
129
|
import chalk from "chalk";
|
|
130
|
+
function extractErrorMessage(error) {
|
|
131
|
+
if (!error || typeof error !== "object")
|
|
132
|
+
return String(error ?? "Unknown error");
|
|
133
|
+
const err = error;
|
|
134
|
+
const gqlErrors = err.response?.errors;
|
|
135
|
+
if (gqlErrors && gqlErrors.length > 0) {
|
|
136
|
+
return gqlErrors[0].message;
|
|
137
|
+
}
|
|
138
|
+
if (err.message && err.message !== "undefined") {
|
|
139
|
+
return err.message;
|
|
140
|
+
}
|
|
141
|
+
if (error instanceof Error && error.message) {
|
|
142
|
+
return error.message;
|
|
143
|
+
}
|
|
144
|
+
return "An unknown error occurred. Use --json for details.";
|
|
145
|
+
}
|
|
130
146
|
function withErrorHandler(optsFn, fn) {
|
|
131
147
|
return async (...args) => {
|
|
132
148
|
try {
|
|
@@ -145,12 +161,18 @@ function withErrorHandler(optsFn, fn) {
|
|
|
145
161
|
error: {
|
|
146
162
|
message: first.message,
|
|
147
163
|
code: code ?? "GRAPHQL_ERROR",
|
|
148
|
-
...validationErrors ? { validationErrors } : {}
|
|
164
|
+
...validationErrors ? { validationErrors } : {},
|
|
165
|
+
...gqlErrors.length > 1 ? {
|
|
166
|
+
additionalErrors: gqlErrors.slice(1).map((e) => e.message)
|
|
167
|
+
} : {}
|
|
149
168
|
}
|
|
150
169
|
})
|
|
151
170
|
);
|
|
152
171
|
} else {
|
|
153
172
|
console.error(chalk.red("Error:"), first.message);
|
|
173
|
+
for (const extra of gqlErrors.slice(1)) {
|
|
174
|
+
console.error(chalk.red(" \u2022"), extra.message);
|
|
175
|
+
}
|
|
154
176
|
if (validationErrors && Object.keys(validationErrors).length > 0) {
|
|
155
177
|
console.error("");
|
|
156
178
|
console.error(chalk.yellow("Field errors:"));
|
|
@@ -162,12 +184,12 @@ function withErrorHandler(optsFn, fn) {
|
|
|
162
184
|
}
|
|
163
185
|
if (code === "UNAUTHENTICATED") {
|
|
164
186
|
console.error(
|
|
165
|
-
chalk.gray("
|
|
187
|
+
chalk.gray("\nHint: Run `foir login` to authenticate.")
|
|
166
188
|
);
|
|
167
189
|
} else if (code === "FORBIDDEN") {
|
|
168
190
|
console.error(
|
|
169
191
|
chalk.gray(
|
|
170
|
-
"
|
|
192
|
+
"\nHint: You may not have permission. Check your API key scopes."
|
|
171
193
|
)
|
|
172
194
|
);
|
|
173
195
|
}
|
|
@@ -190,7 +212,7 @@ function withErrorHandler(optsFn, fn) {
|
|
|
190
212
|
}
|
|
191
213
|
process.exit(1);
|
|
192
214
|
}
|
|
193
|
-
const message =
|
|
215
|
+
const message = extractErrorMessage(error);
|
|
194
216
|
if (opts?.json || opts?.jsonl) {
|
|
195
217
|
console.error(JSON.stringify({ error: { message } }));
|
|
196
218
|
} else {
|
|
@@ -204,13 +226,13 @@ function withErrorHandler(optsFn, fn) {
|
|
|
204
226
|
// src/commands/login.ts
|
|
205
227
|
async function findAvailablePort(start, end) {
|
|
206
228
|
for (let port = start; port <= end; port++) {
|
|
207
|
-
const available = await new Promise((
|
|
229
|
+
const available = await new Promise((resolve8) => {
|
|
208
230
|
const server = http.createServer();
|
|
209
231
|
server.listen(port, () => {
|
|
210
232
|
server.close();
|
|
211
|
-
|
|
233
|
+
resolve8(true);
|
|
212
234
|
});
|
|
213
|
-
server.on("error", () =>
|
|
235
|
+
server.on("error", () => resolve8(false));
|
|
214
236
|
});
|
|
215
237
|
if (available) return port;
|
|
216
238
|
}
|
|
@@ -248,7 +270,7 @@ async function loginAction(globalOpts) {
|
|
|
248
270
|
const state = crypto.randomBytes(16).toString("hex");
|
|
249
271
|
const port = await findAvailablePort(9876, 9900);
|
|
250
272
|
const redirectUri = `http://localhost:${port}/callback`;
|
|
251
|
-
const authCode = await new Promise((
|
|
273
|
+
const authCode = await new Promise((resolve8, reject) => {
|
|
252
274
|
const server = http.createServer((req, res) => {
|
|
253
275
|
const url = new URL(req.url, `http://localhost:${port}`);
|
|
254
276
|
if (url.pathname === "/callback") {
|
|
@@ -281,7 +303,7 @@ async function loginAction(globalOpts) {
|
|
|
281
303
|
`<html><head><meta http-equiv="refresh" content="2;url=${mainUrl}"></head><body style="font-family:system-ui;text-align:center;padding:50px"><h1>Authentication successful!</h1><p>You can close this window.</p></body></html>`
|
|
282
304
|
);
|
|
283
305
|
server.close();
|
|
284
|
-
|
|
306
|
+
resolve8(code);
|
|
285
307
|
}
|
|
286
308
|
});
|
|
287
309
|
server.listen(port);
|
|
@@ -604,8 +626,15 @@ function timeAgo(dateStr) {
|
|
|
604
626
|
if (days < 30) return `${days}d ago`;
|
|
605
627
|
return date.toLocaleDateString();
|
|
606
628
|
}
|
|
607
|
-
function success(message) {
|
|
608
|
-
|
|
629
|
+
function success(message, data) {
|
|
630
|
+
let interpolated = message;
|
|
631
|
+
if (data) {
|
|
632
|
+
interpolated = message.replace(
|
|
633
|
+
/\{(\w+)\}/g,
|
|
634
|
+
(_, field) => String(data[field] ?? `{${field}}`)
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
console.log(chalk2.green(`\u2713 ${interpolated}`));
|
|
609
638
|
}
|
|
610
639
|
|
|
611
640
|
// src/commands/whoami.ts
|
|
@@ -853,9 +882,361 @@ async function loadPullConfig(flags) {
|
|
|
853
882
|
}
|
|
854
883
|
|
|
855
884
|
// src/graphql/generated.ts
|
|
856
|
-
var GetCustomerProfileSchemaDocument = {
|
|
857
|
-
|
|
858
|
-
|
|
885
|
+
var GetCustomerProfileSchemaDocument = {
|
|
886
|
+
kind: "Document",
|
|
887
|
+
definitions: [
|
|
888
|
+
{
|
|
889
|
+
kind: "OperationDefinition",
|
|
890
|
+
operation: "query",
|
|
891
|
+
name: { kind: "Name", value: "GetCustomerProfileSchema" },
|
|
892
|
+
selectionSet: {
|
|
893
|
+
kind: "SelectionSet",
|
|
894
|
+
selections: [
|
|
895
|
+
{
|
|
896
|
+
kind: "Field",
|
|
897
|
+
name: { kind: "Name", value: "customerProfileSchema" },
|
|
898
|
+
selectionSet: {
|
|
899
|
+
kind: "SelectionSet",
|
|
900
|
+
selections: [
|
|
901
|
+
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
|
902
|
+
{
|
|
903
|
+
kind: "Field",
|
|
904
|
+
name: { kind: "Name", value: "fields" },
|
|
905
|
+
selectionSet: {
|
|
906
|
+
kind: "SelectionSet",
|
|
907
|
+
selections: [
|
|
908
|
+
{ kind: "Field", name: { kind: "Name", value: "key" } },
|
|
909
|
+
{ kind: "Field", name: { kind: "Name", value: "type" } },
|
|
910
|
+
{ kind: "Field", name: { kind: "Name", value: "label" } },
|
|
911
|
+
{
|
|
912
|
+
kind: "Field",
|
|
913
|
+
name: { kind: "Name", value: "required" }
|
|
914
|
+
},
|
|
915
|
+
{
|
|
916
|
+
kind: "Field",
|
|
917
|
+
name: { kind: "Name", value: "helpText" }
|
|
918
|
+
},
|
|
919
|
+
{
|
|
920
|
+
kind: "Field",
|
|
921
|
+
name: { kind: "Name", value: "defaultValue" }
|
|
922
|
+
},
|
|
923
|
+
{
|
|
924
|
+
kind: "Field",
|
|
925
|
+
name: { kind: "Name", value: "config" }
|
|
926
|
+
},
|
|
927
|
+
{
|
|
928
|
+
kind: "Field",
|
|
929
|
+
name: { kind: "Name", value: "validation" },
|
|
930
|
+
selectionSet: {
|
|
931
|
+
kind: "SelectionSet",
|
|
932
|
+
selections: [
|
|
933
|
+
{
|
|
934
|
+
kind: "Field",
|
|
935
|
+
name: { kind: "Name", value: "rule" }
|
|
936
|
+
},
|
|
937
|
+
{
|
|
938
|
+
kind: "Field",
|
|
939
|
+
name: { kind: "Name", value: "value" }
|
|
940
|
+
},
|
|
941
|
+
{
|
|
942
|
+
kind: "Field",
|
|
943
|
+
name: { kind: "Name", value: "message" }
|
|
944
|
+
}
|
|
945
|
+
]
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
]
|
|
949
|
+
}
|
|
950
|
+
},
|
|
951
|
+
{
|
|
952
|
+
kind: "Field",
|
|
953
|
+
name: { kind: "Name", value: "publicFields" }
|
|
954
|
+
},
|
|
955
|
+
{ kind: "Field", name: { kind: "Name", value: "version" } },
|
|
956
|
+
{ kind: "Field", name: { kind: "Name", value: "createdAt" } },
|
|
957
|
+
{ kind: "Field", name: { kind: "Name", value: "updatedAt" } }
|
|
958
|
+
]
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
]
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
]
|
|
965
|
+
};
|
|
966
|
+
var ModelsForCodegenDocument = {
|
|
967
|
+
kind: "Document",
|
|
968
|
+
definitions: [
|
|
969
|
+
{
|
|
970
|
+
kind: "OperationDefinition",
|
|
971
|
+
operation: "query",
|
|
972
|
+
name: { kind: "Name", value: "ModelsForCodegen" },
|
|
973
|
+
variableDefinitions: [
|
|
974
|
+
{
|
|
975
|
+
kind: "VariableDefinition",
|
|
976
|
+
variable: {
|
|
977
|
+
kind: "Variable",
|
|
978
|
+
name: { kind: "Name", value: "search" }
|
|
979
|
+
},
|
|
980
|
+
type: { kind: "NamedType", name: { kind: "Name", value: "String" } }
|
|
981
|
+
},
|
|
982
|
+
{
|
|
983
|
+
kind: "VariableDefinition",
|
|
984
|
+
variable: {
|
|
985
|
+
kind: "Variable",
|
|
986
|
+
name: { kind: "Name", value: "limit" }
|
|
987
|
+
},
|
|
988
|
+
type: { kind: "NamedType", name: { kind: "Name", value: "Int" } }
|
|
989
|
+
},
|
|
990
|
+
{
|
|
991
|
+
kind: "VariableDefinition",
|
|
992
|
+
variable: {
|
|
993
|
+
kind: "Variable",
|
|
994
|
+
name: { kind: "Name", value: "offset" }
|
|
995
|
+
},
|
|
996
|
+
type: { kind: "NamedType", name: { kind: "Name", value: "Int" } }
|
|
997
|
+
}
|
|
998
|
+
],
|
|
999
|
+
selectionSet: {
|
|
1000
|
+
kind: "SelectionSet",
|
|
1001
|
+
selections: [
|
|
1002
|
+
{
|
|
1003
|
+
kind: "Field",
|
|
1004
|
+
name: { kind: "Name", value: "models" },
|
|
1005
|
+
arguments: [
|
|
1006
|
+
{
|
|
1007
|
+
kind: "Argument",
|
|
1008
|
+
name: { kind: "Name", value: "search" },
|
|
1009
|
+
value: {
|
|
1010
|
+
kind: "Variable",
|
|
1011
|
+
name: { kind: "Name", value: "search" }
|
|
1012
|
+
}
|
|
1013
|
+
},
|
|
1014
|
+
{
|
|
1015
|
+
kind: "Argument",
|
|
1016
|
+
name: { kind: "Name", value: "limit" },
|
|
1017
|
+
value: {
|
|
1018
|
+
kind: "Variable",
|
|
1019
|
+
name: { kind: "Name", value: "limit" }
|
|
1020
|
+
}
|
|
1021
|
+
},
|
|
1022
|
+
{
|
|
1023
|
+
kind: "Argument",
|
|
1024
|
+
name: { kind: "Name", value: "offset" },
|
|
1025
|
+
value: {
|
|
1026
|
+
kind: "Variable",
|
|
1027
|
+
name: { kind: "Name", value: "offset" }
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
],
|
|
1031
|
+
selectionSet: {
|
|
1032
|
+
kind: "SelectionSet",
|
|
1033
|
+
selections: [
|
|
1034
|
+
{
|
|
1035
|
+
kind: "Field",
|
|
1036
|
+
name: { kind: "Name", value: "items" },
|
|
1037
|
+
selectionSet: {
|
|
1038
|
+
kind: "SelectionSet",
|
|
1039
|
+
selections: [
|
|
1040
|
+
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
|
1041
|
+
{ kind: "Field", name: { kind: "Name", value: "key" } },
|
|
1042
|
+
{ kind: "Field", name: { kind: "Name", value: "name" } },
|
|
1043
|
+
{
|
|
1044
|
+
kind: "Field",
|
|
1045
|
+
name: { kind: "Name", value: "pluralName" }
|
|
1046
|
+
},
|
|
1047
|
+
{
|
|
1048
|
+
kind: "Field",
|
|
1049
|
+
name: { kind: "Name", value: "description" }
|
|
1050
|
+
},
|
|
1051
|
+
{
|
|
1052
|
+
kind: "Field",
|
|
1053
|
+
name: { kind: "Name", value: "category" }
|
|
1054
|
+
},
|
|
1055
|
+
{
|
|
1056
|
+
kind: "Field",
|
|
1057
|
+
name: { kind: "Name", value: "fields" }
|
|
1058
|
+
},
|
|
1059
|
+
{
|
|
1060
|
+
kind: "Field",
|
|
1061
|
+
name: { kind: "Name", value: "config" }
|
|
1062
|
+
},
|
|
1063
|
+
{ kind: "Field", name: { kind: "Name", value: "hooks" } },
|
|
1064
|
+
{
|
|
1065
|
+
kind: "Field",
|
|
1066
|
+
name: { kind: "Name", value: "createdAt" }
|
|
1067
|
+
},
|
|
1068
|
+
{
|
|
1069
|
+
kind: "Field",
|
|
1070
|
+
name: { kind: "Name", value: "updatedAt" }
|
|
1071
|
+
}
|
|
1072
|
+
]
|
|
1073
|
+
}
|
|
1074
|
+
},
|
|
1075
|
+
{ kind: "Field", name: { kind: "Name", value: "total" } }
|
|
1076
|
+
]
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
]
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
]
|
|
1083
|
+
};
|
|
1084
|
+
var GlobalSearchDocument = {
|
|
1085
|
+
kind: "Document",
|
|
1086
|
+
definitions: [
|
|
1087
|
+
{
|
|
1088
|
+
kind: "OperationDefinition",
|
|
1089
|
+
operation: "query",
|
|
1090
|
+
name: { kind: "Name", value: "GlobalSearch" },
|
|
1091
|
+
variableDefinitions: [
|
|
1092
|
+
{
|
|
1093
|
+
kind: "VariableDefinition",
|
|
1094
|
+
variable: {
|
|
1095
|
+
kind: "Variable",
|
|
1096
|
+
name: { kind: "Name", value: "query" }
|
|
1097
|
+
},
|
|
1098
|
+
type: {
|
|
1099
|
+
kind: "NonNullType",
|
|
1100
|
+
type: {
|
|
1101
|
+
kind: "NamedType",
|
|
1102
|
+
name: { kind: "Name", value: "String" }
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
},
|
|
1106
|
+
{
|
|
1107
|
+
kind: "VariableDefinition",
|
|
1108
|
+
variable: {
|
|
1109
|
+
kind: "Variable",
|
|
1110
|
+
name: { kind: "Name", value: "limit" }
|
|
1111
|
+
},
|
|
1112
|
+
type: { kind: "NamedType", name: { kind: "Name", value: "Int" } }
|
|
1113
|
+
},
|
|
1114
|
+
{
|
|
1115
|
+
kind: "VariableDefinition",
|
|
1116
|
+
variable: {
|
|
1117
|
+
kind: "Variable",
|
|
1118
|
+
name: { kind: "Name", value: "modelKeys" }
|
|
1119
|
+
},
|
|
1120
|
+
type: {
|
|
1121
|
+
kind: "ListType",
|
|
1122
|
+
type: {
|
|
1123
|
+
kind: "NonNullType",
|
|
1124
|
+
type: {
|
|
1125
|
+
kind: "NamedType",
|
|
1126
|
+
name: { kind: "Name", value: "String" }
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
},
|
|
1131
|
+
{
|
|
1132
|
+
kind: "VariableDefinition",
|
|
1133
|
+
variable: {
|
|
1134
|
+
kind: "Variable",
|
|
1135
|
+
name: { kind: "Name", value: "includeMedia" }
|
|
1136
|
+
},
|
|
1137
|
+
type: { kind: "NamedType", name: { kind: "Name", value: "Boolean" } }
|
|
1138
|
+
}
|
|
1139
|
+
],
|
|
1140
|
+
selectionSet: {
|
|
1141
|
+
kind: "SelectionSet",
|
|
1142
|
+
selections: [
|
|
1143
|
+
{
|
|
1144
|
+
kind: "Field",
|
|
1145
|
+
name: { kind: "Name", value: "globalSearch" },
|
|
1146
|
+
arguments: [
|
|
1147
|
+
{
|
|
1148
|
+
kind: "Argument",
|
|
1149
|
+
name: { kind: "Name", value: "query" },
|
|
1150
|
+
value: {
|
|
1151
|
+
kind: "Variable",
|
|
1152
|
+
name: { kind: "Name", value: "query" }
|
|
1153
|
+
}
|
|
1154
|
+
},
|
|
1155
|
+
{
|
|
1156
|
+
kind: "Argument",
|
|
1157
|
+
name: { kind: "Name", value: "limit" },
|
|
1158
|
+
value: {
|
|
1159
|
+
kind: "Variable",
|
|
1160
|
+
name: { kind: "Name", value: "limit" }
|
|
1161
|
+
}
|
|
1162
|
+
},
|
|
1163
|
+
{
|
|
1164
|
+
kind: "Argument",
|
|
1165
|
+
name: { kind: "Name", value: "modelKeys" },
|
|
1166
|
+
value: {
|
|
1167
|
+
kind: "Variable",
|
|
1168
|
+
name: { kind: "Name", value: "modelKeys" }
|
|
1169
|
+
}
|
|
1170
|
+
},
|
|
1171
|
+
{
|
|
1172
|
+
kind: "Argument",
|
|
1173
|
+
name: { kind: "Name", value: "includeMedia" },
|
|
1174
|
+
value: {
|
|
1175
|
+
kind: "Variable",
|
|
1176
|
+
name: { kind: "Name", value: "includeMedia" }
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
],
|
|
1180
|
+
selectionSet: {
|
|
1181
|
+
kind: "SelectionSet",
|
|
1182
|
+
selections: [
|
|
1183
|
+
{
|
|
1184
|
+
kind: "Field",
|
|
1185
|
+
name: { kind: "Name", value: "records" },
|
|
1186
|
+
selectionSet: {
|
|
1187
|
+
kind: "SelectionSet",
|
|
1188
|
+
selections: [
|
|
1189
|
+
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
|
1190
|
+
{
|
|
1191
|
+
kind: "Field",
|
|
1192
|
+
name: { kind: "Name", value: "modelKey" }
|
|
1193
|
+
},
|
|
1194
|
+
{ kind: "Field", name: { kind: "Name", value: "title" } },
|
|
1195
|
+
{
|
|
1196
|
+
kind: "Field",
|
|
1197
|
+
name: { kind: "Name", value: "naturalKey" }
|
|
1198
|
+
},
|
|
1199
|
+
{
|
|
1200
|
+
kind: "Field",
|
|
1201
|
+
name: { kind: "Name", value: "subtitle" }
|
|
1202
|
+
},
|
|
1203
|
+
{
|
|
1204
|
+
kind: "Field",
|
|
1205
|
+
name: { kind: "Name", value: "updatedAt" }
|
|
1206
|
+
}
|
|
1207
|
+
]
|
|
1208
|
+
}
|
|
1209
|
+
},
|
|
1210
|
+
{
|
|
1211
|
+
kind: "Field",
|
|
1212
|
+
name: { kind: "Name", value: "media" },
|
|
1213
|
+
selectionSet: {
|
|
1214
|
+
kind: "SelectionSet",
|
|
1215
|
+
selections: [
|
|
1216
|
+
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
|
1217
|
+
{
|
|
1218
|
+
kind: "Field",
|
|
1219
|
+
name: { kind: "Name", value: "fileName" }
|
|
1220
|
+
},
|
|
1221
|
+
{
|
|
1222
|
+
kind: "Field",
|
|
1223
|
+
name: { kind: "Name", value: "altText" }
|
|
1224
|
+
},
|
|
1225
|
+
{
|
|
1226
|
+
kind: "Field",
|
|
1227
|
+
name: { kind: "Name", value: "fileUrl" }
|
|
1228
|
+
}
|
|
1229
|
+
]
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
]
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
]
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
]
|
|
1239
|
+
};
|
|
859
1240
|
|
|
860
1241
|
// src/codegen/fetch-models.ts
|
|
861
1242
|
function normalizeConfig(raw) {
|
|
@@ -5265,10 +5646,215 @@ Media (${data.globalSearch.media.length}):`);
|
|
|
5265
5646
|
);
|
|
5266
5647
|
}
|
|
5267
5648
|
|
|
5649
|
+
// src/commands/init.ts
|
|
5650
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
|
|
5651
|
+
import { writeFile as writeFile2 } from "fs/promises";
|
|
5652
|
+
import { resolve as resolve4, join as join4 } from "path";
|
|
5653
|
+
import chalk6 from "chalk";
|
|
5654
|
+
import inquirer3 from "inquirer";
|
|
5655
|
+
var FIELD_DEFAULTS = {
|
|
5656
|
+
text: "",
|
|
5657
|
+
richtext: "",
|
|
5658
|
+
number: 0,
|
|
5659
|
+
boolean: false,
|
|
5660
|
+
date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
5661
|
+
select: "",
|
|
5662
|
+
image: null,
|
|
5663
|
+
video: null,
|
|
5664
|
+
file: null,
|
|
5665
|
+
json: {},
|
|
5666
|
+
link: { url: "", label: "" },
|
|
5667
|
+
relationship: null,
|
|
5668
|
+
list: []
|
|
5669
|
+
};
|
|
5670
|
+
function defaultValueForField(field) {
|
|
5671
|
+
return FIELD_DEFAULTS[field.type] ?? null;
|
|
5672
|
+
}
|
|
5673
|
+
function generateModelTemplate(key) {
|
|
5674
|
+
return {
|
|
5675
|
+
key,
|
|
5676
|
+
name: key.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" "),
|
|
5677
|
+
fields: [
|
|
5678
|
+
{
|
|
5679
|
+
key: "title",
|
|
5680
|
+
type: "text",
|
|
5681
|
+
label: "Title",
|
|
5682
|
+
required: true
|
|
5683
|
+
},
|
|
5684
|
+
{
|
|
5685
|
+
key: "slug",
|
|
5686
|
+
type: "text",
|
|
5687
|
+
label: "Slug",
|
|
5688
|
+
required: true
|
|
5689
|
+
},
|
|
5690
|
+
{
|
|
5691
|
+
key: "description",
|
|
5692
|
+
type: "richtext",
|
|
5693
|
+
label: "Description"
|
|
5694
|
+
},
|
|
5695
|
+
{
|
|
5696
|
+
key: "image",
|
|
5697
|
+
type: "image",
|
|
5698
|
+
label: "Featured Image"
|
|
5699
|
+
}
|
|
5700
|
+
],
|
|
5701
|
+
config: {
|
|
5702
|
+
versioning: true,
|
|
5703
|
+
publishing: true,
|
|
5704
|
+
variants: false
|
|
5705
|
+
}
|
|
5706
|
+
};
|
|
5707
|
+
}
|
|
5708
|
+
function generateRecordSeed(model) {
|
|
5709
|
+
const data = {};
|
|
5710
|
+
for (const field of model.fields) {
|
|
5711
|
+
if (["id", "createdAt", "updatedAt", "createdBy", "updatedBy"].includes(
|
|
5712
|
+
field.key
|
|
5713
|
+
)) {
|
|
5714
|
+
continue;
|
|
5715
|
+
}
|
|
5716
|
+
if (field.type === "list" && field.items) {
|
|
5717
|
+
data[field.key] = [
|
|
5718
|
+
defaultValueForField({ key: field.key, type: field.items.type })
|
|
5719
|
+
];
|
|
5720
|
+
} else {
|
|
5721
|
+
data[field.key] = defaultValueForField(field);
|
|
5722
|
+
}
|
|
5723
|
+
}
|
|
5724
|
+
return {
|
|
5725
|
+
modelKey: model.key,
|
|
5726
|
+
naturalKey: `example-${model.key}`,
|
|
5727
|
+
data
|
|
5728
|
+
};
|
|
5729
|
+
}
|
|
5730
|
+
function formatAsTypeScript(obj) {
|
|
5731
|
+
const json = JSON.stringify(obj, null, 2);
|
|
5732
|
+
return `export default ${json} as const;
|
|
5733
|
+
`;
|
|
5734
|
+
}
|
|
5735
|
+
function registerInitCommands(program2, globalOpts) {
|
|
5736
|
+
const initGroup = program2.command("init").description("Generate starter config and seed files");
|
|
5737
|
+
initGroup.command("model").description("Generate a starter model definition file").argument("<key>", "Model key (e.g. blog-post)").option("-o, --output <dir>", "Output directory", "models").option("--ts", "Generate TypeScript (.ts) instead of JSON").action(
|
|
5738
|
+
withErrorHandler(
|
|
5739
|
+
globalOpts,
|
|
5740
|
+
async (key, opts) => {
|
|
5741
|
+
const globalFlags = globalOpts();
|
|
5742
|
+
const template = generateModelTemplate(key);
|
|
5743
|
+
const outDir = resolve4(opts.output);
|
|
5744
|
+
if (!existsSync4(outDir)) {
|
|
5745
|
+
mkdirSync2(outDir, { recursive: true });
|
|
5746
|
+
}
|
|
5747
|
+
const ext = opts.ts ? "ts" : "json";
|
|
5748
|
+
const filePath = join4(outDir, `${key}.${ext}`);
|
|
5749
|
+
const content = opts.ts ? formatAsTypeScript(template) : JSON.stringify(template, null, 2) + "\n";
|
|
5750
|
+
await writeFile2(filePath, content, "utf-8");
|
|
5751
|
+
if (!(globalFlags.json || globalFlags.jsonl || globalFlags.quiet)) {
|
|
5752
|
+
success(`Created ${filePath}`);
|
|
5753
|
+
console.log(
|
|
5754
|
+
chalk6.gray(
|
|
5755
|
+
`
|
|
5756
|
+
Edit the file, then run:
|
|
5757
|
+
foir models create -f ${filePath}`
|
|
5758
|
+
)
|
|
5759
|
+
);
|
|
5760
|
+
} else {
|
|
5761
|
+
console.log(JSON.stringify({ path: filePath }));
|
|
5762
|
+
}
|
|
5763
|
+
}
|
|
5764
|
+
)
|
|
5765
|
+
);
|
|
5766
|
+
initGroup.command("records").description("Generate seed files for records (interactive model selector)").option("-o, --output <dir>", "Output directory", "seed").option("--ts", "Generate TypeScript (.ts) instead of JSON").option(
|
|
5767
|
+
"--model <keys...>",
|
|
5768
|
+
"Model keys to generate for (skip interactive selector)"
|
|
5769
|
+
).action(
|
|
5770
|
+
withErrorHandler(
|
|
5771
|
+
globalOpts,
|
|
5772
|
+
async (opts) => {
|
|
5773
|
+
const globalFlags = globalOpts();
|
|
5774
|
+
const client = await createClient(globalFlags);
|
|
5775
|
+
const query = `query { models(limit: 100) { items { key name fields } total } }`;
|
|
5776
|
+
const result = await client.request(query);
|
|
5777
|
+
const models = result.models.items;
|
|
5778
|
+
if (models.length === 0) {
|
|
5779
|
+
console.log(
|
|
5780
|
+
chalk6.yellow(
|
|
5781
|
+
"No models found. Create models first with `foir models create`."
|
|
5782
|
+
)
|
|
5783
|
+
);
|
|
5784
|
+
return;
|
|
5785
|
+
}
|
|
5786
|
+
let selectedModels;
|
|
5787
|
+
if (opts.model && opts.model.length > 0) {
|
|
5788
|
+
selectedModels = [];
|
|
5789
|
+
for (const key of opts.model) {
|
|
5790
|
+
const found = models.find((m) => m.key === key);
|
|
5791
|
+
if (!found) {
|
|
5792
|
+
console.error(
|
|
5793
|
+
chalk6.red(`Model "${key}" not found.`),
|
|
5794
|
+
"Available:",
|
|
5795
|
+
models.map((m) => m.key).join(", ")
|
|
5796
|
+
);
|
|
5797
|
+
return;
|
|
5798
|
+
}
|
|
5799
|
+
selectedModels.push(found);
|
|
5800
|
+
}
|
|
5801
|
+
} else {
|
|
5802
|
+
const { selected } = await inquirer3.prompt([
|
|
5803
|
+
{
|
|
5804
|
+
type: "checkbox",
|
|
5805
|
+
name: "selected",
|
|
5806
|
+
message: "Select models to generate seed files for:",
|
|
5807
|
+
choices: models.map((m) => ({
|
|
5808
|
+
name: `${m.name} (${m.key})`,
|
|
5809
|
+
value: m.key,
|
|
5810
|
+
short: m.key
|
|
5811
|
+
})),
|
|
5812
|
+
validate: (answer) => answer.length > 0 ? true : "Select at least one model."
|
|
5813
|
+
}
|
|
5814
|
+
]);
|
|
5815
|
+
selectedModels = selected.map((key) => models.find((m) => m.key === key)).filter(Boolean);
|
|
5816
|
+
}
|
|
5817
|
+
if (selectedModels.length === 0) {
|
|
5818
|
+
console.log("No models selected.");
|
|
5819
|
+
return;
|
|
5820
|
+
}
|
|
5821
|
+
const outDir = resolve4(opts.output);
|
|
5822
|
+
if (!existsSync4(outDir)) {
|
|
5823
|
+
mkdirSync2(outDir, { recursive: true });
|
|
5824
|
+
}
|
|
5825
|
+
const createdFiles = [];
|
|
5826
|
+
for (const model of selectedModels) {
|
|
5827
|
+
const seed = generateRecordSeed(model);
|
|
5828
|
+
const ext = opts.ts ? "ts" : "json";
|
|
5829
|
+
const filePath = join4(outDir, `${model.key}.${ext}`);
|
|
5830
|
+
const content = opts.ts ? formatAsTypeScript(seed) : JSON.stringify(seed, null, 2) + "\n";
|
|
5831
|
+
await writeFile2(filePath, content, "utf-8");
|
|
5832
|
+
createdFiles.push(filePath);
|
|
5833
|
+
if (!(globalFlags.json || globalFlags.jsonl || globalFlags.quiet)) {
|
|
5834
|
+
success(`Created ${filePath}`);
|
|
5835
|
+
}
|
|
5836
|
+
}
|
|
5837
|
+
if (!(globalFlags.json || globalFlags.jsonl || globalFlags.quiet)) {
|
|
5838
|
+
console.log(
|
|
5839
|
+
chalk6.gray(
|
|
5840
|
+
`
|
|
5841
|
+
Edit the files, then run:
|
|
5842
|
+
foir records create --dir ${outDir} --publish`
|
|
5843
|
+
)
|
|
5844
|
+
);
|
|
5845
|
+
} else {
|
|
5846
|
+
console.log(JSON.stringify({ files: createdFiles }));
|
|
5847
|
+
}
|
|
5848
|
+
}
|
|
5849
|
+
)
|
|
5850
|
+
);
|
|
5851
|
+
}
|
|
5852
|
+
|
|
5268
5853
|
// src/commands/register-commands.ts
|
|
5269
|
-
import { readFileSync } from "fs";
|
|
5270
|
-
import { resolve as
|
|
5854
|
+
import { readFileSync, readdirSync } from "fs";
|
|
5855
|
+
import { resolve as resolve6, dirname as dirname5 } from "path";
|
|
5271
5856
|
import { fileURLToPath } from "url";
|
|
5857
|
+
import chalk7 from "chalk";
|
|
5272
5858
|
|
|
5273
5859
|
// ../command-registry/src/command-map.ts
|
|
5274
5860
|
var COMMANDS = [
|
|
@@ -5303,7 +5889,17 @@ var COMMANDS = [
|
|
|
5303
5889
|
operation: "createModel",
|
|
5304
5890
|
operationType: "mutation",
|
|
5305
5891
|
acceptsInput: true,
|
|
5306
|
-
successMessage: "Created model {key}"
|
|
5892
|
+
successMessage: "Created model {key}",
|
|
5893
|
+
customFlags: [
|
|
5894
|
+
{
|
|
5895
|
+
flag: "--dir <path>",
|
|
5896
|
+
description: "Create models from all files in directory"
|
|
5897
|
+
},
|
|
5898
|
+
{
|
|
5899
|
+
flag: "--upsert",
|
|
5900
|
+
description: "Update if model key already exists"
|
|
5901
|
+
}
|
|
5902
|
+
]
|
|
5307
5903
|
},
|
|
5308
5904
|
{
|
|
5309
5905
|
group: "models",
|
|
@@ -5364,7 +5960,13 @@ var COMMANDS = [
|
|
|
5364
5960
|
operation: "record",
|
|
5365
5961
|
operationType: "query",
|
|
5366
5962
|
positionalArgs: [{ name: "id", graphqlArg: "id" }],
|
|
5367
|
-
alternateGet: { operation: "recordByKey", argName: "naturalKey" }
|
|
5963
|
+
alternateGet: { operation: "recordByKey", argName: "naturalKey" },
|
|
5964
|
+
customFlags: [
|
|
5965
|
+
{
|
|
5966
|
+
flag: "--model-key <key>",
|
|
5967
|
+
description: "Model key (required when looking up by natural key)"
|
|
5968
|
+
}
|
|
5969
|
+
]
|
|
5368
5970
|
},
|
|
5369
5971
|
{
|
|
5370
5972
|
group: "records",
|
|
@@ -5373,7 +5975,10 @@ var COMMANDS = [
|
|
|
5373
5975
|
operation: "createRecord",
|
|
5374
5976
|
operationType: "mutation",
|
|
5375
5977
|
acceptsInput: true,
|
|
5376
|
-
successMessage: "Created record"
|
|
5978
|
+
successMessage: "Created record",
|
|
5979
|
+
customFlags: [
|
|
5980
|
+
{ flag: "--publish", description: "Publish the record after creation" }
|
|
5981
|
+
]
|
|
5377
5982
|
},
|
|
5378
5983
|
{
|
|
5379
5984
|
group: "records",
|
|
@@ -5400,9 +6005,21 @@ var COMMANDS = [
|
|
|
5400
6005
|
description: "Publish a record version",
|
|
5401
6006
|
operation: "publishVersion",
|
|
5402
6007
|
operationType: "mutation",
|
|
5403
|
-
positionalArgs: [
|
|
6008
|
+
positionalArgs: [
|
|
6009
|
+
{
|
|
6010
|
+
name: "versionId",
|
|
6011
|
+
graphqlArg: "versionId",
|
|
6012
|
+
description: "Version ID, record ID, or natural key (with --model)"
|
|
6013
|
+
}
|
|
6014
|
+
],
|
|
5404
6015
|
scalarResult: true,
|
|
5405
|
-
successMessage: "Published version"
|
|
6016
|
+
successMessage: "Published version",
|
|
6017
|
+
customFlags: [
|
|
6018
|
+
{
|
|
6019
|
+
flag: "--model-key <key>",
|
|
6020
|
+
description: "Model key (use with natural key instead of version ID)"
|
|
6021
|
+
}
|
|
6022
|
+
]
|
|
5406
6023
|
},
|
|
5407
6024
|
{
|
|
5408
6025
|
group: "records",
|
|
@@ -6647,6 +7264,7 @@ var COMMANDS = [
|
|
|
6647
7264
|
import {
|
|
6648
7265
|
buildSchema,
|
|
6649
7266
|
isObjectType,
|
|
7267
|
+
isInputObjectType,
|
|
6650
7268
|
isListType,
|
|
6651
7269
|
isNonNullType,
|
|
6652
7270
|
isScalarType,
|
|
@@ -6804,6 +7422,21 @@ function createSchemaEngine(sdl) {
|
|
|
6804
7422
|
const argPart = fieldArgs ? `(${fieldArgs})` : "";
|
|
6805
7423
|
return `${opType} ${opName}${varPart} { ${entry.operation}${argPart} ${selectionSet} }`;
|
|
6806
7424
|
}
|
|
7425
|
+
function getInputFields(operationName, operationType, inputArgName) {
|
|
7426
|
+
const field = getField(operationName, operationType);
|
|
7427
|
+
if (!field) return [];
|
|
7428
|
+
const argName = inputArgName ?? "input";
|
|
7429
|
+
const arg = field.args.find((a) => a.name === argName);
|
|
7430
|
+
if (!arg) return [];
|
|
7431
|
+
const namedType = unwrapType(arg.type);
|
|
7432
|
+
if (!isInputObjectType(namedType)) return [];
|
|
7433
|
+
const fields = namedType.getFields();
|
|
7434
|
+
return Object.entries(fields).map(([name, f]) => ({
|
|
7435
|
+
name,
|
|
7436
|
+
type: typeToString(f.type),
|
|
7437
|
+
required: isNonNullType(f.type)
|
|
7438
|
+
}));
|
|
7439
|
+
}
|
|
6807
7440
|
function getCompletions(partial, commandNames) {
|
|
6808
7441
|
return commandNames.filter(
|
|
6809
7442
|
(name) => name.toLowerCase().startsWith(partial.toLowerCase())
|
|
@@ -6813,19 +7446,20 @@ function createSchemaEngine(sdl) {
|
|
|
6813
7446
|
buildQuery,
|
|
6814
7447
|
getOperationArgs,
|
|
6815
7448
|
coerceArgs,
|
|
7449
|
+
getInputFields,
|
|
6816
7450
|
getCompletions
|
|
6817
7451
|
};
|
|
6818
7452
|
}
|
|
6819
7453
|
|
|
6820
7454
|
// src/lib/input.ts
|
|
6821
|
-
import
|
|
7455
|
+
import inquirer4 from "inquirer";
|
|
6822
7456
|
|
|
6823
7457
|
// src/lib/config-loader.ts
|
|
6824
7458
|
import { readFile } from "fs/promises";
|
|
6825
7459
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
6826
|
-
import { resolve as
|
|
7460
|
+
import { resolve as resolve5 } from "path";
|
|
6827
7461
|
async function loadConfig(filePath) {
|
|
6828
|
-
const absPath =
|
|
7462
|
+
const absPath = resolve5(filePath);
|
|
6829
7463
|
if (filePath.endsWith(".ts")) {
|
|
6830
7464
|
const configModule = await import(pathToFileURL2(absPath).href);
|
|
6831
7465
|
return configModule.default;
|
|
@@ -6872,7 +7506,7 @@ function isUUID(value) {
|
|
|
6872
7506
|
}
|
|
6873
7507
|
async function confirmAction(message, opts) {
|
|
6874
7508
|
if (opts?.confirm) return true;
|
|
6875
|
-
const { confirmed } = await
|
|
7509
|
+
const { confirmed } = await inquirer4.prompt([
|
|
6876
7510
|
{
|
|
6877
7511
|
type: "confirm",
|
|
6878
7512
|
name: "confirmed",
|
|
@@ -6887,11 +7521,11 @@ async function confirmAction(message, opts) {
|
|
|
6887
7521
|
var __filename = fileURLToPath(import.meta.url);
|
|
6888
7522
|
var __dirname = dirname5(__filename);
|
|
6889
7523
|
function loadSchemaSDL() {
|
|
6890
|
-
const bundledPath =
|
|
7524
|
+
const bundledPath = resolve6(__dirname, "schema.graphql");
|
|
6891
7525
|
try {
|
|
6892
7526
|
return readFileSync(bundledPath, "utf-8");
|
|
6893
7527
|
} catch {
|
|
6894
|
-
const monorepoPath =
|
|
7528
|
+
const monorepoPath = resolve6(
|
|
6895
7529
|
__dirname,
|
|
6896
7530
|
"../../../graphql-core/schema.graphql"
|
|
6897
7531
|
);
|
|
@@ -6991,6 +7625,26 @@ function registerDynamicCommands(program2, globalOpts) {
|
|
|
6991
7625
|
if (entry.acceptsInput) {
|
|
6992
7626
|
cmd = cmd.option("-d, --data <json>", "Data as JSON");
|
|
6993
7627
|
cmd = cmd.option("-f, --file <path>", "Read data from file");
|
|
7628
|
+
const inputFields = engine.getInputFields(
|
|
7629
|
+
entry.operation,
|
|
7630
|
+
entry.operationType,
|
|
7631
|
+
entry.inputArgName
|
|
7632
|
+
);
|
|
7633
|
+
if (inputFields.length > 0) {
|
|
7634
|
+
const required = inputFields.filter((f) => f.required);
|
|
7635
|
+
const optional = inputFields.filter((f) => !f.required);
|
|
7636
|
+
let fieldHelp = "\nInput fields:";
|
|
7637
|
+
if (required.length > 0) {
|
|
7638
|
+
fieldHelp += "\n Required: " + required.map((f) => `${f.name} (${f.type})`).join(", ");
|
|
7639
|
+
}
|
|
7640
|
+
if (optional.length > 0) {
|
|
7641
|
+
fieldHelp += "\n Optional: " + optional.map((f) => `${f.name} (${f.type})`).join(", ");
|
|
7642
|
+
}
|
|
7643
|
+
cmd = cmd.addHelpText("after", fieldHelp);
|
|
7644
|
+
}
|
|
7645
|
+
}
|
|
7646
|
+
for (const cf of entry.customFlags ?? []) {
|
|
7647
|
+
cmd = cmd.option(cf.flag, cf.description);
|
|
6994
7648
|
}
|
|
6995
7649
|
if (entry.requiresConfirmation) {
|
|
6996
7650
|
cmd = cmd.option("--confirm", "Skip confirmation prompt");
|
|
@@ -7009,9 +7663,15 @@ function registerDynamicCommands(program2, globalOpts) {
|
|
|
7009
7663
|
}
|
|
7010
7664
|
}
|
|
7011
7665
|
const flags = actionArgs[positionals.length] ?? {};
|
|
7666
|
+
const customFlagNames = new Set(
|
|
7667
|
+
(entry.customFlags ?? []).map(
|
|
7668
|
+
(cf) => cf.flag.replace(/ <.*>$/, "").replace(/^--/, "").replace(/-([a-z])/g, (_, c) => c.toUpperCase())
|
|
7669
|
+
)
|
|
7670
|
+
);
|
|
7012
7671
|
const rawFlags = {};
|
|
7013
7672
|
for (const [key, val] of Object.entries(flags)) {
|
|
7014
7673
|
if (key === "data" || key === "file" || key === "confirm") continue;
|
|
7674
|
+
if (customFlagNames.has(key)) continue;
|
|
7015
7675
|
rawFlags[key] = String(val);
|
|
7016
7676
|
}
|
|
7017
7677
|
const coerced = engine.coerceArgs(
|
|
@@ -7020,17 +7680,108 @@ function registerDynamicCommands(program2, globalOpts) {
|
|
|
7020
7680
|
rawFlags
|
|
7021
7681
|
);
|
|
7022
7682
|
Object.assign(variables, coerced);
|
|
7683
|
+
if (flags.dir && entry.acceptsInput) {
|
|
7684
|
+
const dirPath = resolve6(String(flags.dir));
|
|
7685
|
+
const files = readdirSync(dirPath).filter((f) => /\.(json|ts|js|mjs)$/.test(f)).sort();
|
|
7686
|
+
if (files.length === 0) {
|
|
7687
|
+
console.error(
|
|
7688
|
+
chalk7.yellow(`\u26A0 No .json/.ts/.js files found in ${dirPath}`)
|
|
7689
|
+
);
|
|
7690
|
+
return;
|
|
7691
|
+
}
|
|
7692
|
+
let created = 0;
|
|
7693
|
+
let updated = 0;
|
|
7694
|
+
let failed = 0;
|
|
7695
|
+
for (const file of files) {
|
|
7696
|
+
const filePath = resolve6(dirPath, file);
|
|
7697
|
+
const fileData = await parseInputData({ file: filePath });
|
|
7698
|
+
const argName = entry.inputArgName ?? "input";
|
|
7699
|
+
const fileVars = { ...variables, [argName]: fileData };
|
|
7700
|
+
const label = fileData.key ?? fileData.name ?? file;
|
|
7701
|
+
try {
|
|
7702
|
+
const q = engine.buildQuery(entry, fileVars);
|
|
7703
|
+
await client.request(q, fileVars);
|
|
7704
|
+
created++;
|
|
7705
|
+
if (!(opts.json || opts.jsonl || opts.quiet)) {
|
|
7706
|
+
success(`Created ${label}`);
|
|
7707
|
+
}
|
|
7708
|
+
} catch (err) {
|
|
7709
|
+
if (flags.upsert && fileData.key) {
|
|
7710
|
+
try {
|
|
7711
|
+
const updateEntry = COMMANDS.find(
|
|
7712
|
+
(c) => c.group === entry.group && c.name === "update"
|
|
7713
|
+
);
|
|
7714
|
+
if (updateEntry) {
|
|
7715
|
+
const updateVars = {
|
|
7716
|
+
...variables,
|
|
7717
|
+
[updateEntry.inputArgName ?? "input"]: fileData,
|
|
7718
|
+
...updateEntry.positionalArgs?.[0] ? {
|
|
7719
|
+
[updateEntry.positionalArgs[0].graphqlArg]: fileData.key
|
|
7720
|
+
} : {}
|
|
7721
|
+
};
|
|
7722
|
+
const uq = engine.buildQuery(updateEntry, updateVars);
|
|
7723
|
+
await client.request(uq, updateVars);
|
|
7724
|
+
updated++;
|
|
7725
|
+
if (!(opts.json || opts.jsonl || opts.quiet)) {
|
|
7726
|
+
success(`Updated ${label}`);
|
|
7727
|
+
}
|
|
7728
|
+
continue;
|
|
7729
|
+
}
|
|
7730
|
+
} catch (updateErr) {
|
|
7731
|
+
failed++;
|
|
7732
|
+
const msg2 = updateErr instanceof Error ? updateErr.message : String(updateErr);
|
|
7733
|
+
console.error(chalk7.red(`\u2717 ${label}:`), msg2);
|
|
7734
|
+
continue;
|
|
7735
|
+
}
|
|
7736
|
+
}
|
|
7737
|
+
failed++;
|
|
7738
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
7739
|
+
console.error(chalk7.red(`\u2717 ${label}:`), msg);
|
|
7740
|
+
}
|
|
7741
|
+
}
|
|
7742
|
+
if (!(opts.json || opts.jsonl || opts.quiet)) {
|
|
7743
|
+
console.log("");
|
|
7744
|
+
console.log(
|
|
7745
|
+
chalk7.bold(
|
|
7746
|
+
`Done: ${created} created${updated ? `, ${updated} updated` : ""}${failed ? `, ${failed} failed` : ""}`
|
|
7747
|
+
)
|
|
7748
|
+
);
|
|
7749
|
+
}
|
|
7750
|
+
return;
|
|
7751
|
+
}
|
|
7023
7752
|
if (entry.acceptsInput && (flags.data || flags.file)) {
|
|
7024
7753
|
const inputData = await parseInputData({
|
|
7025
7754
|
data: flags.data,
|
|
7026
7755
|
file: flags.file
|
|
7027
7756
|
});
|
|
7757
|
+
const inputFields = engine.getInputFields(
|
|
7758
|
+
entry.operation,
|
|
7759
|
+
entry.operationType,
|
|
7760
|
+
entry.inputArgName
|
|
7761
|
+
);
|
|
7762
|
+
const fieldNames = new Set(inputFields.map((f) => f.name));
|
|
7763
|
+
if (fieldNames.has("projectId") && !inputData.projectId || fieldNames.has("tenantId") && !inputData.tenantId) {
|
|
7764
|
+
const project = await getProjectContext();
|
|
7765
|
+
if (project) {
|
|
7766
|
+
if (fieldNames.has("projectId") && !inputData.projectId) {
|
|
7767
|
+
inputData.projectId = project.id;
|
|
7768
|
+
}
|
|
7769
|
+
if (fieldNames.has("tenantId") && !inputData.tenantId) {
|
|
7770
|
+
inputData.tenantId = project.tenantId;
|
|
7771
|
+
}
|
|
7772
|
+
}
|
|
7773
|
+
}
|
|
7028
7774
|
const argName = entry.inputArgName ?? "input";
|
|
7029
7775
|
variables[argName] = inputData;
|
|
7030
7776
|
}
|
|
7031
7777
|
if (entry.alternateGet && positionals.length > 0 && variables[positionals[0].graphqlArg]) {
|
|
7032
7778
|
const firstArgValue = String(variables[positionals[0].graphqlArg]);
|
|
7033
7779
|
if (!isUUID(firstArgValue)) {
|
|
7780
|
+
if (entry.alternateGet.operation === "recordByKey" && !flags.modelKey) {
|
|
7781
|
+
throw new Error(
|
|
7782
|
+
`"${firstArgValue}" is not a UUID. Use --model-key <key> to look up a record by natural key, or pass a record UUID.`
|
|
7783
|
+
);
|
|
7784
|
+
}
|
|
7034
7785
|
const altEntry = {
|
|
7035
7786
|
...entry,
|
|
7036
7787
|
operation: entry.alternateGet.operation,
|
|
@@ -7043,6 +7794,9 @@ function registerDynamicCommands(program2, globalOpts) {
|
|
|
7043
7794
|
};
|
|
7044
7795
|
delete variables[positionals[0].graphqlArg];
|
|
7045
7796
|
variables[entry.alternateGet.argName] = firstArgValue;
|
|
7797
|
+
if (flags.modelKey) {
|
|
7798
|
+
variables.modelKey = String(flags.modelKey);
|
|
7799
|
+
}
|
|
7046
7800
|
const queryStr2 = engine.buildQuery(altEntry, variables);
|
|
7047
7801
|
const result2 = await client.request(
|
|
7048
7802
|
queryStr2,
|
|
@@ -7062,27 +7816,112 @@ function registerDynamicCommands(program2, globalOpts) {
|
|
|
7062
7816
|
return;
|
|
7063
7817
|
}
|
|
7064
7818
|
}
|
|
7819
|
+
if (entry.group === "records" && entry.name === "publish" && variables.versionId) {
|
|
7820
|
+
const versionIdValue = String(variables.versionId);
|
|
7821
|
+
if (flags.modelKey) {
|
|
7822
|
+
const modelKey = String(flags.modelKey);
|
|
7823
|
+
const lookupQuery = `query RecordByKey($modelKey: String!, $naturalKey: String!) { recordByKey(modelKey: $modelKey, naturalKey: $naturalKey) { id currentVersionId } }`;
|
|
7824
|
+
const lookupResult = await client.request(lookupQuery, {
|
|
7825
|
+
modelKey,
|
|
7826
|
+
naturalKey: versionIdValue
|
|
7827
|
+
});
|
|
7828
|
+
const record = lookupResult.recordByKey;
|
|
7829
|
+
if (!record?.currentVersionId) {
|
|
7830
|
+
throw new Error(
|
|
7831
|
+
`No current version found for record "${versionIdValue}"`
|
|
7832
|
+
);
|
|
7833
|
+
}
|
|
7834
|
+
variables.versionId = record.currentVersionId;
|
|
7835
|
+
} else if (!isUUID(versionIdValue)) {
|
|
7836
|
+
throw new Error(
|
|
7837
|
+
`"${versionIdValue}" is not a UUID. Use --model-key <key> to resolve a natural key, or pass a version/record UUID directly.`
|
|
7838
|
+
);
|
|
7839
|
+
} else {
|
|
7840
|
+
try {
|
|
7841
|
+
const lookupQuery = `query Record($id: ID!) { record(id: $id) { id recordType currentVersionId } }`;
|
|
7842
|
+
const lookupResult = await client.request(lookupQuery, {
|
|
7843
|
+
id: versionIdValue
|
|
7844
|
+
});
|
|
7845
|
+
const record = lookupResult.record;
|
|
7846
|
+
if (record?.recordType === "record" && record?.currentVersionId) {
|
|
7847
|
+
variables.versionId = record.currentVersionId;
|
|
7848
|
+
}
|
|
7849
|
+
} catch {
|
|
7850
|
+
}
|
|
7851
|
+
}
|
|
7852
|
+
}
|
|
7065
7853
|
const queryStr = engine.buildQuery(entry, variables);
|
|
7066
|
-
|
|
7067
|
-
|
|
7068
|
-
|
|
7854
|
+
let result;
|
|
7855
|
+
let usedUpdate = false;
|
|
7856
|
+
try {
|
|
7857
|
+
result = await client.request(queryStr, variables);
|
|
7858
|
+
} catch (createErr) {
|
|
7859
|
+
if (flags.upsert && entry.name === "create") {
|
|
7860
|
+
const updateEntry = COMMANDS.find(
|
|
7861
|
+
(c) => c.group === entry.group && c.name === "update"
|
|
7862
|
+
);
|
|
7863
|
+
const inputArgName = entry.inputArgName ?? "input";
|
|
7864
|
+
const inputData = variables[inputArgName];
|
|
7865
|
+
if (updateEntry && inputData?.key) {
|
|
7866
|
+
const updateVars = {
|
|
7867
|
+
...variables,
|
|
7868
|
+
[updateEntry.inputArgName ?? "input"]: inputData,
|
|
7869
|
+
...updateEntry.positionalArgs?.[0] ? {
|
|
7870
|
+
[updateEntry.positionalArgs[0].graphqlArg]: inputData.key
|
|
7871
|
+
} : {}
|
|
7872
|
+
};
|
|
7873
|
+
const uq = engine.buildQuery(updateEntry, updateVars);
|
|
7874
|
+
result = await client.request(uq, updateVars);
|
|
7875
|
+
usedUpdate = true;
|
|
7876
|
+
} else {
|
|
7877
|
+
throw createErr;
|
|
7878
|
+
}
|
|
7879
|
+
} else {
|
|
7880
|
+
throw createErr;
|
|
7881
|
+
}
|
|
7882
|
+
}
|
|
7883
|
+
const activeEntry = usedUpdate ? COMMANDS.find(
|
|
7884
|
+
(c) => c.group === entry.group && c.name === "update"
|
|
7885
|
+
) ?? entry : entry;
|
|
7886
|
+
const { data, total } = extractResult(result, activeEntry.operation);
|
|
7887
|
+
const responseData = data && typeof data === "object" && !Array.isArray(data) ? data : void 0;
|
|
7888
|
+
const displayEntry = usedUpdate ? activeEntry : entry;
|
|
7889
|
+
if (displayEntry.scalarResult) {
|
|
7069
7890
|
if (!(opts.json || opts.jsonl || opts.quiet)) {
|
|
7070
|
-
if (
|
|
7071
|
-
success(
|
|
7891
|
+
if (displayEntry.successMessage) {
|
|
7892
|
+
success(displayEntry.successMessage, responseData);
|
|
7072
7893
|
}
|
|
7073
7894
|
} else {
|
|
7074
7895
|
formatOutput(data, opts);
|
|
7075
7896
|
}
|
|
7076
7897
|
} else if (Array.isArray(data)) {
|
|
7077
|
-
const cliColumns = toCliColumns(
|
|
7898
|
+
const cliColumns = toCliColumns(displayEntry.columns);
|
|
7078
7899
|
formatList(data, opts, {
|
|
7079
7900
|
columns: cliColumns ?? autoColumns(data),
|
|
7080
7901
|
total
|
|
7081
7902
|
});
|
|
7082
7903
|
} else {
|
|
7083
7904
|
formatOutput(data, opts);
|
|
7084
|
-
if (
|
|
7085
|
-
success(
|
|
7905
|
+
if (displayEntry.successMessage && !(opts.json || opts.jsonl || opts.quiet)) {
|
|
7906
|
+
success(displayEntry.successMessage, responseData);
|
|
7907
|
+
}
|
|
7908
|
+
}
|
|
7909
|
+
if (flags.publish && entry.group === "records" && entry.name === "create" && responseData) {
|
|
7910
|
+
const version2 = responseData.version;
|
|
7911
|
+
const record = responseData.record;
|
|
7912
|
+
const versionId = version2?.id ?? record?.currentVersionId;
|
|
7913
|
+
if (versionId) {
|
|
7914
|
+
const publishQuery = `mutation PublishVersion($versionId: ID!) { publishVersion(versionId: $versionId) }`;
|
|
7915
|
+
await client.request(publishQuery, { versionId });
|
|
7916
|
+
if (!(opts.json || opts.jsonl || opts.quiet)) {
|
|
7917
|
+
success("Published version {id}", { id: versionId });
|
|
7918
|
+
}
|
|
7919
|
+
} else if (!(opts.json || opts.jsonl || opts.quiet)) {
|
|
7920
|
+
console.error(
|
|
7921
|
+
chalk7.yellow(
|
|
7922
|
+
"\u26A0 Could not auto-publish: no version found in response"
|
|
7923
|
+
)
|
|
7924
|
+
);
|
|
7086
7925
|
}
|
|
7087
7926
|
}
|
|
7088
7927
|
})
|
|
@@ -7103,7 +7942,7 @@ function autoColumns(items) {
|
|
|
7103
7942
|
// src/cli.ts
|
|
7104
7943
|
var __filename2 = fileURLToPath2(import.meta.url);
|
|
7105
7944
|
var __dirname2 = dirname6(__filename2);
|
|
7106
|
-
config({ path:
|
|
7945
|
+
config({ path: resolve7(__dirname2, "../.env.local") });
|
|
7107
7946
|
var require2 = createRequire(import.meta.url);
|
|
7108
7947
|
var { version } = require2("../package.json");
|
|
7109
7948
|
var program = new Command();
|
|
@@ -7125,5 +7964,6 @@ registerMediaCommands(program, getGlobalOpts);
|
|
|
7125
7964
|
registerSearchCommands(program, getGlobalOpts);
|
|
7126
7965
|
registerPullCommand(program, getGlobalOpts);
|
|
7127
7966
|
registerCreateExtensionCommand(program, getGlobalOpts);
|
|
7967
|
+
registerInitCommands(program, getGlobalOpts);
|
|
7128
7968
|
registerDynamicCommands(program, getGlobalOpts);
|
|
7129
7969
|
program.parse();
|
package/dist/schema.graphql
CHANGED
|
@@ -1835,6 +1835,11 @@ type SessionContext {
|
|
|
1835
1835
|
|
|
1836
1836
|
"""Current user info"""
|
|
1837
1837
|
user: AdminUser!
|
|
1838
|
+
|
|
1839
|
+
"""
|
|
1840
|
+
Effective role for the current tenant+project context (e.g. PLATFORM_ADMIN, TENANT_OWNER, PROJECT_ADMIN, PROJECT_EDITOR, PROJECT_VIEWER)
|
|
1841
|
+
"""
|
|
1842
|
+
effectiveRole: String
|
|
1838
1843
|
}
|
|
1839
1844
|
|
|
1840
1845
|
type Project {
|
|
@@ -2788,6 +2793,9 @@ type BillingPlanLimit {
|
|
|
2788
2793
|
maxQuantity: BigInt
|
|
2789
2794
|
overagePriceCents: Int
|
|
2790
2795
|
overageUnitSize: Int!
|
|
2796
|
+
|
|
2797
|
+
"""Whether this limit is unlimited (no max quantity)"""
|
|
2798
|
+
isUnlimited: Boolean!
|
|
2791
2799
|
}
|
|
2792
2800
|
|
|
2793
2801
|
type BillingCustomPackage {
|
|
@@ -2818,6 +2826,9 @@ type BillingPackageLimit {
|
|
|
2818
2826
|
includedQuantity: BigInt
|
|
2819
2827
|
maxQuantity: BigInt
|
|
2820
2828
|
overagePriceCents: Int
|
|
2829
|
+
|
|
2830
|
+
"""Whether this limit is unlimited (no max quantity)"""
|
|
2831
|
+
isUnlimited: Boolean!
|
|
2821
2832
|
}
|
|
2822
2833
|
|
|
2823
2834
|
type BillingSubscription {
|
|
@@ -3220,6 +3231,15 @@ type ExperimentFunnel {
|
|
|
3220
3231
|
steps: [FunnelStep!]!
|
|
3221
3232
|
}
|
|
3222
3233
|
|
|
3234
|
+
"""Experiment lifecycle action"""
|
|
3235
|
+
enum ExperimentAction {
|
|
3236
|
+
start
|
|
3237
|
+
pause
|
|
3238
|
+
resume
|
|
3239
|
+
end
|
|
3240
|
+
declareWinner
|
|
3241
|
+
}
|
|
3242
|
+
|
|
3223
3243
|
"""Experiment stored in the dedicated experiments table"""
|
|
3224
3244
|
type ExperimentRecord {
|
|
3225
3245
|
id: ID!
|
|
@@ -3240,6 +3260,9 @@ type ExperimentRecord {
|
|
|
3240
3260
|
funnel: ExperimentFunnel
|
|
3241
3261
|
targetVariantCatalogId: ID
|
|
3242
3262
|
isActive: Boolean!
|
|
3263
|
+
|
|
3264
|
+
"""Actions available for the current experiment status"""
|
|
3265
|
+
allowedActions: [ExperimentAction!]!
|
|
3243
3266
|
createdAt: DateTime!
|
|
3244
3267
|
updatedAt: DateTime!
|
|
3245
3268
|
}
|
|
@@ -5012,6 +5035,18 @@ input UpdateScheduleInput {
|
|
|
5012
5035
|
isActive: Boolean
|
|
5013
5036
|
}
|
|
5014
5037
|
|
|
5038
|
+
"""Computed capabilities for a model based on its config"""
|
|
5039
|
+
type ModelCapabilities {
|
|
5040
|
+
isVersioned: Boolean!
|
|
5041
|
+
isPublishable: Boolean!
|
|
5042
|
+
hasVariants: Boolean!
|
|
5043
|
+
isInlineOnly: Boolean!
|
|
5044
|
+
recordCapable: Boolean!
|
|
5045
|
+
publicApi: Boolean!
|
|
5046
|
+
customerScoped: Boolean!
|
|
5047
|
+
sharingEnabled: Boolean!
|
|
5048
|
+
}
|
|
5049
|
+
|
|
5015
5050
|
type Model {
|
|
5016
5051
|
id: ID!
|
|
5017
5052
|
key: String!
|
|
@@ -5050,6 +5085,9 @@ type Model {
|
|
|
5050
5085
|
"""Whether this model's fields can be edited"""
|
|
5051
5086
|
editable: Boolean!
|
|
5052
5087
|
|
|
5088
|
+
"""Computed capabilities based on config flags"""
|
|
5089
|
+
capabilities: ModelCapabilities!
|
|
5090
|
+
|
|
5053
5091
|
"""Current schema version"""
|
|
5054
5092
|
currentVersionId: String
|
|
5055
5093
|
publishedVersionNumber: Int
|
|
@@ -5128,6 +5166,19 @@ type Record {
|
|
|
5128
5166
|
scheduledPublishAt: DateTime
|
|
5129
5167
|
scheduledUnpublishAt: DateTime
|
|
5130
5168
|
customerId: String
|
|
5169
|
+
|
|
5170
|
+
"""
|
|
5171
|
+
Whether this record can be published (has unpublished changes on a publishable model)
|
|
5172
|
+
"""
|
|
5173
|
+
canPublish: Boolean!
|
|
5174
|
+
|
|
5175
|
+
"""
|
|
5176
|
+
Whether this record can be unpublished (has a published version on a publishable model)
|
|
5177
|
+
"""
|
|
5178
|
+
canUnpublish: Boolean!
|
|
5179
|
+
|
|
5180
|
+
"""Whether the current version differs from the published version"""
|
|
5181
|
+
hasUnpublishedChanges: Boolean!
|
|
5131
5182
|
createdAt: DateTime!
|
|
5132
5183
|
updatedAt: DateTime!
|
|
5133
5184
|
createdBy: String
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eide/foir-cli",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Universal platform CLI for
|
|
3
|
+
"version": "0.1.40",
|
|
4
|
+
"description": "Universal platform CLI for Foir platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
@@ -69,8 +69,8 @@
|
|
|
69
69
|
"tsx": "^4.20.0",
|
|
70
70
|
"typescript": "5.9.2",
|
|
71
71
|
"vitest": "^3.2.4",
|
|
72
|
-
"@
|
|
73
|
-
"@
|
|
72
|
+
"@eide/command-registry": "0.1.0",
|
|
73
|
+
"@foir/platform": "1.0.0"
|
|
74
74
|
},
|
|
75
75
|
"engines": {
|
|
76
76
|
"node": ">=18.0.0"
|