@eide/foir-cli 0.1.38 → 0.1.39

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",
@@ -5373,7 +5969,10 @@ var COMMANDS = [
5373
5969
  operation: "createRecord",
5374
5970
  operationType: "mutation",
5375
5971
  acceptsInput: true,
5376
- successMessage: "Created record"
5972
+ successMessage: "Created record",
5973
+ customFlags: [
5974
+ { flag: "--publish", description: "Publish the record after creation" }
5975
+ ]
5377
5976
  },
5378
5977
  {
5379
5978
  group: "records",
@@ -5400,9 +5999,21 @@ var COMMANDS = [
5400
5999
  description: "Publish a record version",
5401
6000
  operation: "publishVersion",
5402
6001
  operationType: "mutation",
5403
- positionalArgs: [{ name: "versionId", graphqlArg: "versionId" }],
6002
+ positionalArgs: [
6003
+ {
6004
+ name: "versionId",
6005
+ graphqlArg: "versionId",
6006
+ description: "Version ID, record ID, or natural key (with --model)"
6007
+ }
6008
+ ],
5404
6009
  scalarResult: true,
5405
- successMessage: "Published version"
6010
+ successMessage: "Published version",
6011
+ customFlags: [
6012
+ {
6013
+ flag: "--model <key>",
6014
+ description: "Model key (use with natural key instead of version ID)"
6015
+ }
6016
+ ]
5406
6017
  },
5407
6018
  {
5408
6019
  group: "records",
@@ -6647,6 +7258,7 @@ var COMMANDS = [
6647
7258
  import {
6648
7259
  buildSchema,
6649
7260
  isObjectType,
7261
+ isInputObjectType,
6650
7262
  isListType,
6651
7263
  isNonNullType,
6652
7264
  isScalarType,
@@ -6804,6 +7416,21 @@ function createSchemaEngine(sdl) {
6804
7416
  const argPart = fieldArgs ? `(${fieldArgs})` : "";
6805
7417
  return `${opType} ${opName}${varPart} { ${entry.operation}${argPart} ${selectionSet} }`;
6806
7418
  }
7419
+ function getInputFields(operationName, operationType, inputArgName) {
7420
+ const field = getField(operationName, operationType);
7421
+ if (!field) return [];
7422
+ const argName = inputArgName ?? "input";
7423
+ const arg = field.args.find((a) => a.name === argName);
7424
+ if (!arg) return [];
7425
+ const namedType = unwrapType(arg.type);
7426
+ if (!isInputObjectType(namedType)) return [];
7427
+ const fields = namedType.getFields();
7428
+ return Object.entries(fields).map(([name, f]) => ({
7429
+ name,
7430
+ type: typeToString(f.type),
7431
+ required: isNonNullType(f.type)
7432
+ }));
7433
+ }
6807
7434
  function getCompletions(partial, commandNames) {
6808
7435
  return commandNames.filter(
6809
7436
  (name) => name.toLowerCase().startsWith(partial.toLowerCase())
@@ -6813,19 +7440,20 @@ function createSchemaEngine(sdl) {
6813
7440
  buildQuery,
6814
7441
  getOperationArgs,
6815
7442
  coerceArgs,
7443
+ getInputFields,
6816
7444
  getCompletions
6817
7445
  };
6818
7446
  }
6819
7447
 
6820
7448
  // src/lib/input.ts
6821
- import inquirer3 from "inquirer";
7449
+ import inquirer4 from "inquirer";
6822
7450
 
6823
7451
  // src/lib/config-loader.ts
6824
7452
  import { readFile } from "fs/promises";
6825
7453
  import { pathToFileURL as pathToFileURL2 } from "url";
6826
- import { resolve as resolve4 } from "path";
7454
+ import { resolve as resolve5 } from "path";
6827
7455
  async function loadConfig(filePath) {
6828
- const absPath = resolve4(filePath);
7456
+ const absPath = resolve5(filePath);
6829
7457
  if (filePath.endsWith(".ts")) {
6830
7458
  const configModule = await import(pathToFileURL2(absPath).href);
6831
7459
  return configModule.default;
@@ -6872,7 +7500,7 @@ function isUUID(value) {
6872
7500
  }
6873
7501
  async function confirmAction(message, opts) {
6874
7502
  if (opts?.confirm) return true;
6875
- const { confirmed } = await inquirer3.prompt([
7503
+ const { confirmed } = await inquirer4.prompt([
6876
7504
  {
6877
7505
  type: "confirm",
6878
7506
  name: "confirmed",
@@ -6887,11 +7515,11 @@ async function confirmAction(message, opts) {
6887
7515
  var __filename = fileURLToPath(import.meta.url);
6888
7516
  var __dirname = dirname5(__filename);
6889
7517
  function loadSchemaSDL() {
6890
- const bundledPath = resolve5(__dirname, "schema.graphql");
7518
+ const bundledPath = resolve6(__dirname, "schema.graphql");
6891
7519
  try {
6892
7520
  return readFileSync(bundledPath, "utf-8");
6893
7521
  } catch {
6894
- const monorepoPath = resolve5(
7522
+ const monorepoPath = resolve6(
6895
7523
  __dirname,
6896
7524
  "../../../graphql-core/schema.graphql"
6897
7525
  );
@@ -6991,6 +7619,26 @@ function registerDynamicCommands(program2, globalOpts) {
6991
7619
  if (entry.acceptsInput) {
6992
7620
  cmd = cmd.option("-d, --data <json>", "Data as JSON");
6993
7621
  cmd = cmd.option("-f, --file <path>", "Read data from file");
7622
+ const inputFields = engine.getInputFields(
7623
+ entry.operation,
7624
+ entry.operationType,
7625
+ entry.inputArgName
7626
+ );
7627
+ if (inputFields.length > 0) {
7628
+ const required = inputFields.filter((f) => f.required);
7629
+ const optional = inputFields.filter((f) => !f.required);
7630
+ let fieldHelp = "\nInput fields:";
7631
+ if (required.length > 0) {
7632
+ fieldHelp += "\n Required: " + required.map((f) => `${f.name} (${f.type})`).join(", ");
7633
+ }
7634
+ if (optional.length > 0) {
7635
+ fieldHelp += "\n Optional: " + optional.map((f) => `${f.name} (${f.type})`).join(", ");
7636
+ }
7637
+ cmd = cmd.addHelpText("after", fieldHelp);
7638
+ }
7639
+ }
7640
+ for (const cf of entry.customFlags ?? []) {
7641
+ cmd = cmd.option(cf.flag, cf.description);
6994
7642
  }
6995
7643
  if (entry.requiresConfirmation) {
6996
7644
  cmd = cmd.option("--confirm", "Skip confirmation prompt");
@@ -7009,9 +7657,15 @@ function registerDynamicCommands(program2, globalOpts) {
7009
7657
  }
7010
7658
  }
7011
7659
  const flags = actionArgs[positionals.length] ?? {};
7660
+ const customFlagNames = new Set(
7661
+ (entry.customFlags ?? []).map(
7662
+ (cf) => cf.flag.replace(/ <.*>$/, "").replace(/^--/, "").replace(/-([a-z])/g, (_, c) => c.toUpperCase())
7663
+ )
7664
+ );
7012
7665
  const rawFlags = {};
7013
7666
  for (const [key, val] of Object.entries(flags)) {
7014
7667
  if (key === "data" || key === "file" || key === "confirm") continue;
7668
+ if (customFlagNames.has(key)) continue;
7015
7669
  rawFlags[key] = String(val);
7016
7670
  }
7017
7671
  const coerced = engine.coerceArgs(
@@ -7020,11 +7674,97 @@ function registerDynamicCommands(program2, globalOpts) {
7020
7674
  rawFlags
7021
7675
  );
7022
7676
  Object.assign(variables, coerced);
7677
+ if (flags.dir && entry.acceptsInput) {
7678
+ const dirPath = resolve6(String(flags.dir));
7679
+ const files = readdirSync(dirPath).filter((f) => /\.(json|ts|js|mjs)$/.test(f)).sort();
7680
+ if (files.length === 0) {
7681
+ console.error(
7682
+ chalk7.yellow(`\u26A0 No .json/.ts/.js files found in ${dirPath}`)
7683
+ );
7684
+ return;
7685
+ }
7686
+ let created = 0;
7687
+ let updated = 0;
7688
+ let failed = 0;
7689
+ for (const file of files) {
7690
+ const filePath = resolve6(dirPath, file);
7691
+ const fileData = await parseInputData({ file: filePath });
7692
+ const argName = entry.inputArgName ?? "input";
7693
+ const fileVars = { ...variables, [argName]: fileData };
7694
+ const label = fileData.key ?? fileData.name ?? file;
7695
+ try {
7696
+ const q = engine.buildQuery(entry, fileVars);
7697
+ await client.request(q, fileVars);
7698
+ created++;
7699
+ if (!(opts.json || opts.jsonl || opts.quiet)) {
7700
+ success(`Created ${label}`);
7701
+ }
7702
+ } catch (err) {
7703
+ if (flags.upsert && fileData.key) {
7704
+ try {
7705
+ const updateEntry = COMMANDS.find(
7706
+ (c) => c.group === entry.group && c.name === "update"
7707
+ );
7708
+ if (updateEntry) {
7709
+ const updateVars = {
7710
+ ...variables,
7711
+ [updateEntry.inputArgName ?? "input"]: fileData,
7712
+ ...updateEntry.positionalArgs?.[0] ? {
7713
+ [updateEntry.positionalArgs[0].graphqlArg]: fileData.key
7714
+ } : {}
7715
+ };
7716
+ const uq = engine.buildQuery(updateEntry, updateVars);
7717
+ await client.request(uq, updateVars);
7718
+ updated++;
7719
+ if (!(opts.json || opts.jsonl || opts.quiet)) {
7720
+ success(`Updated ${label}`);
7721
+ }
7722
+ continue;
7723
+ }
7724
+ } catch (updateErr) {
7725
+ failed++;
7726
+ const msg2 = updateErr instanceof Error ? updateErr.message : String(updateErr);
7727
+ console.error(chalk7.red(`\u2717 ${label}:`), msg2);
7728
+ continue;
7729
+ }
7730
+ }
7731
+ failed++;
7732
+ const msg = err instanceof Error ? err.message : String(err);
7733
+ console.error(chalk7.red(`\u2717 ${label}:`), msg);
7734
+ }
7735
+ }
7736
+ if (!(opts.json || opts.jsonl || opts.quiet)) {
7737
+ console.log("");
7738
+ console.log(
7739
+ chalk7.bold(
7740
+ `Done: ${created} created${updated ? `, ${updated} updated` : ""}${failed ? `, ${failed} failed` : ""}`
7741
+ )
7742
+ );
7743
+ }
7744
+ return;
7745
+ }
7023
7746
  if (entry.acceptsInput && (flags.data || flags.file)) {
7024
7747
  const inputData = await parseInputData({
7025
7748
  data: flags.data,
7026
7749
  file: flags.file
7027
7750
  });
7751
+ const inputFields = engine.getInputFields(
7752
+ entry.operation,
7753
+ entry.operationType,
7754
+ entry.inputArgName
7755
+ );
7756
+ const fieldNames = new Set(inputFields.map((f) => f.name));
7757
+ if (fieldNames.has("projectId") && !inputData.projectId || fieldNames.has("tenantId") && !inputData.tenantId) {
7758
+ const project = await getProjectContext();
7759
+ if (project) {
7760
+ if (fieldNames.has("projectId") && !inputData.projectId) {
7761
+ inputData.projectId = project.id;
7762
+ }
7763
+ if (fieldNames.has("tenantId") && !inputData.tenantId) {
7764
+ inputData.tenantId = project.tenantId;
7765
+ }
7766
+ }
7767
+ }
7028
7768
  const argName = entry.inputArgName ?? "input";
7029
7769
  variables[argName] = inputData;
7030
7770
  }
@@ -7062,27 +7802,119 @@ function registerDynamicCommands(program2, globalOpts) {
7062
7802
  return;
7063
7803
  }
7064
7804
  }
7805
+ if (entry.group === "records" && entry.name === "publish" && variables.versionId) {
7806
+ const versionIdValue = String(variables.versionId);
7807
+ if (flags.model) {
7808
+ const lookupQuery = `query RecordByKey($naturalKey: String!) { recordByKey(naturalKey: $naturalKey) { id currentVersion { id } } }`;
7809
+ const lookupResult = await client.request(lookupQuery, {
7810
+ naturalKey: versionIdValue
7811
+ });
7812
+ const record = lookupResult.recordByKey;
7813
+ const currentVersion = record?.currentVersion;
7814
+ if (!currentVersion?.id) {
7815
+ throw new Error(
7816
+ `No current version found for record "${versionIdValue}"`
7817
+ );
7818
+ }
7819
+ variables.versionId = currentVersion.id;
7820
+ } else if (!isUUID(versionIdValue)) {
7821
+ const lookupQuery = `query RecordByKey($naturalKey: String!) { recordByKey(naturalKey: $naturalKey) { id currentVersion { id } } }`;
7822
+ const lookupResult = await client.request(lookupQuery, {
7823
+ naturalKey: versionIdValue
7824
+ });
7825
+ const record = lookupResult.recordByKey;
7826
+ const currentVersion = record?.currentVersion;
7827
+ if (currentVersion?.id) {
7828
+ variables.versionId = currentVersion.id;
7829
+ }
7830
+ } else {
7831
+ try {
7832
+ const lookupQuery = `query Record($id: ID!) { record(id: $id) { id recordType currentVersion { id } } }`;
7833
+ const lookupResult = await client.request(lookupQuery, {
7834
+ id: versionIdValue
7835
+ });
7836
+ const record = lookupResult.record;
7837
+ if (record?.recordType === "record" && record?.currentVersion) {
7838
+ const cv = record.currentVersion;
7839
+ if (cv.id) {
7840
+ variables.versionId = cv.id;
7841
+ }
7842
+ }
7843
+ } catch {
7844
+ }
7845
+ }
7846
+ }
7065
7847
  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) {
7848
+ let result;
7849
+ let usedUpdate = false;
7850
+ try {
7851
+ result = await client.request(queryStr, variables);
7852
+ } catch (createErr) {
7853
+ if (flags.upsert && entry.name === "create") {
7854
+ const updateEntry = COMMANDS.find(
7855
+ (c) => c.group === entry.group && c.name === "update"
7856
+ );
7857
+ const inputArgName = entry.inputArgName ?? "input";
7858
+ const inputData = variables[inputArgName];
7859
+ if (updateEntry && inputData?.key) {
7860
+ const updateVars = {
7861
+ ...variables,
7862
+ [updateEntry.inputArgName ?? "input"]: inputData,
7863
+ ...updateEntry.positionalArgs?.[0] ? {
7864
+ [updateEntry.positionalArgs[0].graphqlArg]: inputData.key
7865
+ } : {}
7866
+ };
7867
+ const uq = engine.buildQuery(updateEntry, updateVars);
7868
+ result = await client.request(uq, updateVars);
7869
+ usedUpdate = true;
7870
+ } else {
7871
+ throw createErr;
7872
+ }
7873
+ } else {
7874
+ throw createErr;
7875
+ }
7876
+ }
7877
+ const activeEntry = usedUpdate ? COMMANDS.find(
7878
+ (c) => c.group === entry.group && c.name === "update"
7879
+ ) ?? entry : entry;
7880
+ const { data, total } = extractResult(result, activeEntry.operation);
7881
+ const responseData = data && typeof data === "object" && !Array.isArray(data) ? data : void 0;
7882
+ const displayEntry = usedUpdate ? activeEntry : entry;
7883
+ if (displayEntry.scalarResult) {
7069
7884
  if (!(opts.json || opts.jsonl || opts.quiet)) {
7070
- if (entry.successMessage) {
7071
- success(entry.successMessage);
7885
+ if (displayEntry.successMessage) {
7886
+ success(displayEntry.successMessage, responseData);
7072
7887
  }
7073
7888
  } else {
7074
7889
  formatOutput(data, opts);
7075
7890
  }
7076
7891
  } else if (Array.isArray(data)) {
7077
- const cliColumns = toCliColumns(entry.columns);
7892
+ const cliColumns = toCliColumns(displayEntry.columns);
7078
7893
  formatList(data, opts, {
7079
7894
  columns: cliColumns ?? autoColumns(data),
7080
7895
  total
7081
7896
  });
7082
7897
  } else {
7083
7898
  formatOutput(data, opts);
7084
- if (entry.successMessage && !(opts.json || opts.jsonl || opts.quiet)) {
7085
- success(entry.successMessage);
7899
+ if (displayEntry.successMessage && !(opts.json || opts.jsonl || opts.quiet)) {
7900
+ success(displayEntry.successMessage, responseData);
7901
+ }
7902
+ }
7903
+ if (flags.publish && entry.group === "records" && entry.name === "create" && responseData) {
7904
+ const version2 = responseData.version;
7905
+ const versionId = version2?.id;
7906
+ if (versionId) {
7907
+ const publishQuery = `mutation PublishVersion($versionId: ID!) { publishVersion(versionId: $versionId) }`;
7908
+ await client.request(publishQuery, { versionId });
7909
+ if (!(opts.json || opts.jsonl || opts.quiet)) {
7910
+ success("Published version {id}", { id: versionId });
7911
+ }
7912
+ } else if (!(opts.json || opts.jsonl || opts.quiet)) {
7913
+ console.error(
7914
+ chalk7.yellow(
7915
+ "\u26A0 Could not auto-publish: no version found in response"
7916
+ )
7917
+ );
7086
7918
  }
7087
7919
  }
7088
7920
  })
@@ -7103,7 +7935,7 @@ function autoColumns(items) {
7103
7935
  // src/cli.ts
7104
7936
  var __filename2 = fileURLToPath2(import.meta.url);
7105
7937
  var __dirname2 = dirname6(__filename2);
7106
- config({ path: resolve6(__dirname2, "../.env.local") });
7938
+ config({ path: resolve7(__dirname2, "../.env.local") });
7107
7939
  var require2 = createRequire(import.meta.url);
7108
7940
  var { version } = require2("../package.json");
7109
7941
  var program = new Command();
@@ -7125,5 +7957,6 @@ registerMediaCommands(program, getGlobalOpts);
7125
7957
  registerSearchCommands(program, getGlobalOpts);
7126
7958
  registerPullCommand(program, getGlobalOpts);
7127
7959
  registerCreateExtensionCommand(program, getGlobalOpts);
7960
+ registerInitCommands(program, getGlobalOpts);
7128
7961
  registerDynamicCommands(program, getGlobalOpts);
7129
7962
  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.39",
4
+ "description": "Universal platform CLI for Foir platform",
5
5
  "type": "module",
6
6
  "publishConfig": {
7
7
  "access": "public"
@@ -35,6 +35,21 @@
35
35
  "dist",
36
36
  "README.md"
37
37
  ],
38
+ "scripts": {
39
+ "build": "tsup",
40
+ "dev:cli": "tsx src/cli.ts",
41
+ "check-types": "tsc --noEmit",
42
+ "lint": "eslint src/",
43
+ "lint:fix": "eslint src/ --fix",
44
+ "test": "vitest run",
45
+ "test:watch": "vitest watch",
46
+ "codegen": "graphql-codegen --config codegen.ts",
47
+ "codegen:watch": "graphql-codegen --config codegen.ts --watch",
48
+ "prepublishOnly": "pnpm run build",
49
+ "release:patch": "pnpm version patch",
50
+ "release:minor": "pnpm version minor",
51
+ "release:major": "pnpm version major"
52
+ },
38
53
  "keywords": [
39
54
  "foir",
40
55
  "eide",
@@ -58,19 +73,19 @@
58
73
  "prettier": "^3.4.2"
59
74
  },
60
75
  "devDependencies": {
76
+ "@foir/platform": "workspace:*",
61
77
  "@graphql-codegen/cli": "^5.0.3",
62
78
  "@graphql-codegen/typed-document-node": "^5.0.12",
63
79
  "@graphql-codegen/typescript": "^4.1.2",
64
80
  "@graphql-codegen/typescript-operations": "^4.4.0",
65
81
  "@graphql-typed-document-node/core": "^3.2.0",
66
82
  "@types/inquirer": "^9.0.7",
83
+ "@eide/command-registry": "workspace:*",
67
84
  "@types/node": "^22.5.0",
68
85
  "tsup": "^8.5.1",
69
86
  "tsx": "^4.20.0",
70
87
  "typescript": "5.9.2",
71
- "vitest": "^3.2.4",
72
- "@foir/platform": "1.0.0",
73
- "@eide/command-registry": "0.1.0"
88
+ "vitest": "^3.2.4"
74
89
  },
75
90
  "engines": {
76
91
  "node": ">=18.0.0"
@@ -79,19 +94,5 @@
79
94
  "type": "git",
80
95
  "url": "https://github.com/eidebuild/eide.git",
81
96
  "directory": "packages/cli"
82
- },
83
- "scripts": {
84
- "build": "tsup",
85
- "dev:cli": "tsx src/cli.ts",
86
- "check-types": "tsc --noEmit",
87
- "lint": "eslint src/",
88
- "lint:fix": "eslint src/ --fix",
89
- "test": "vitest run",
90
- "test:watch": "vitest watch",
91
- "codegen": "graphql-codegen --config codegen.ts",
92
- "codegen:watch": "graphql-codegen --config codegen.ts --watch",
93
- "release:patch": "pnpm version patch",
94
- "release:minor": "pnpm version minor",
95
- "release:major": "pnpm version major"
96
97
  }
97
- }
98
+ }