@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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/cli.ts
4
4
  import { config } from "dotenv";
5
- import { resolve as resolve6, dirname as dirname6 } from "path";
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("Hint: Run `foir login` to authenticate.")
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
- "Hint: You may not have permission. Check your API key scopes."
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 = error instanceof Error ? error.message : String(error);
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((resolve7) => {
229
+ const available = await new Promise((resolve8) => {
208
230
  const server = http.createServer();
209
231
  server.listen(port, () => {
210
232
  server.close();
211
- resolve7(true);
233
+ resolve8(true);
212
234
  });
213
- server.on("error", () => resolve7(false));
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((resolve7, reject) => {
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
- resolve7(code);
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
- console.log(chalk2.green(`\u2713 ${message}`));
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 = { "kind": "Document", "definitions": [{ "kind": "OperationDefinition", "operation": "query", "name": { "kind": "Name", "value": "GetCustomerProfileSchema" }, "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "customerProfileSchema" }, "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }, { "kind": "Field", "name": { "kind": "Name", "value": "fields" }, "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "key" } }, { "kind": "Field", "name": { "kind": "Name", "value": "type" } }, { "kind": "Field", "name": { "kind": "Name", "value": "label" } }, { "kind": "Field", "name": { "kind": "Name", "value": "required" } }, { "kind": "Field", "name": { "kind": "Name", "value": "helpText" } }, { "kind": "Field", "name": { "kind": "Name", "value": "defaultValue" } }, { "kind": "Field", "name": { "kind": "Name", "value": "config" } }, { "kind": "Field", "name": { "kind": "Name", "value": "validation" }, "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "rule" } }, { "kind": "Field", "name": { "kind": "Name", "value": "value" } }, { "kind": "Field", "name": { "kind": "Name", "value": "message" } }] } }] } }, { "kind": "Field", "name": { "kind": "Name", "value": "publicFields" } }, { "kind": "Field", "name": { "kind": "Name", "value": "version" } }, { "kind": "Field", "name": { "kind": "Name", "value": "createdAt" } }, { "kind": "Field", "name": { "kind": "Name", "value": "updatedAt" } }] } }] } }] };
857
- var ModelsForCodegenDocument = { "kind": "Document", "definitions": [{ "kind": "OperationDefinition", "operation": "query", "name": { "kind": "Name", "value": "ModelsForCodegen" }, "variableDefinitions": [{ "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "search" } }, "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": "offset" } }, "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "Int" } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "models" }, "arguments": [{ "kind": "Argument", "name": { "kind": "Name", "value": "search" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "search" } } }, { "kind": "Argument", "name": { "kind": "Name", "value": "limit" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "limit" } } }, { "kind": "Argument", "name": { "kind": "Name", "value": "offset" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "offset" } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "items" }, "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }, { "kind": "Field", "name": { "kind": "Name", "value": "key" } }, { "kind": "Field", "name": { "kind": "Name", "value": "name" } }, { "kind": "Field", "name": { "kind": "Name", "value": "pluralName" } }, { "kind": "Field", "name": { "kind": "Name", "value": "description" } }, { "kind": "Field", "name": { "kind": "Name", "value": "category" } }, { "kind": "Field", "name": { "kind": "Name", "value": "fields" } }, { "kind": "Field", "name": { "kind": "Name", "value": "config" } }, { "kind": "Field", "name": { "kind": "Name", "value": "hooks" } }, { "kind": "Field", "name": { "kind": "Name", "value": "createdAt" } }, { "kind": "Field", "name": { "kind": "Name", "value": "updatedAt" } }] } }, { "kind": "Field", "name": { "kind": "Name", "value": "total" } }] } }] } }] };
858
- 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" } }] } }] } }] } }] };
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 resolve5, dirname as dirname5 } from "path";
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: [{ name: "versionId", graphqlArg: "versionId" }],
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 inquirer3 from "inquirer";
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 resolve4 } from "path";
7460
+ import { resolve as resolve5 } from "path";
6827
7461
  async function loadConfig(filePath) {
6828
- const absPath = resolve4(filePath);
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 inquirer3.prompt([
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 = resolve5(__dirname, "schema.graphql");
7524
+ const bundledPath = resolve6(__dirname, "schema.graphql");
6891
7525
  try {
6892
7526
  return readFileSync(bundledPath, "utf-8");
6893
7527
  } catch {
6894
- const monorepoPath = resolve5(
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
- const result = await client.request(queryStr, variables);
7067
- const { data, total } = extractResult(result, entry.operation);
7068
- if (entry.scalarResult) {
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 (entry.successMessage) {
7071
- success(entry.successMessage);
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(entry.columns);
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 (entry.successMessage && !(opts.json || opts.jsonl || opts.quiet)) {
7085
- success(entry.successMessage);
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: resolve6(__dirname2, "../.env.local") });
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();
@@ -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.38",
4
- "description": "Universal platform CLI for EIDE — scriptable, composable resource management",
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
- "@foir/platform": "1.0.0",
73
- "@eide/command-registry": "0.1.0"
72
+ "@eide/command-registry": "0.1.0",
73
+ "@foir/platform": "1.0.0"
74
74
  },
75
75
  "engines": {
76
76
  "node": ">=18.0.0"