@eide/foir-cli 0.1.44 → 0.1.46

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
@@ -1,12 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- findConfigFile,
4
- loadConfigFile
5
- } from "./chunk-L642MYIL.js";
6
2
 
7
3
  // src/cli.ts
8
4
  import { config } from "dotenv";
9
- import { resolve as resolve6, dirname as dirname6 } from "path";
5
+ import { resolve as resolve6, dirname as dirname5 } from "path";
10
6
  import { fileURLToPath as fileURLToPath2 } from "url";
11
7
  import { createRequire } from "module";
12
8
  import { Command } from "commander";
@@ -218,21 +214,6 @@ async function resolveProjectContext(options) {
218
214
  };
219
215
  }
220
216
  }
221
- try {
222
- const { loadConfigProject } = await import("./loader-7VE4OF73.js");
223
- const configProfile = await loadConfigProject();
224
- if (configProfile) {
225
- const project2 = await getProjectContext(configProfile);
226
- if (project2) {
227
- return {
228
- project: project2,
229
- source: "foir.config.ts",
230
- profileName: configProfile
231
- };
232
- }
233
- }
234
- } catch {
235
- }
236
217
  const defaultProfile = await getDefaultProfile();
237
218
  if (defaultProfile) {
238
219
  const project2 = await getProjectContext(defaultProfile);
@@ -516,7 +497,7 @@ var CLI_API_KEY_SCOPES = [
516
497
  "records:publish",
517
498
  "files:read",
518
499
  "files:write",
519
- "extensions:read",
500
+ "configs:read",
520
501
  "operations:read",
521
502
  "operations:execute"
522
503
  ];
@@ -940,1717 +921,181 @@ function registerMediaCommands(program2, globalOpts) {
940
921
  );
941
922
  }
942
923
 
943
- // src/commands/pull.ts
944
- import { resolve } from "path";
924
+ // src/commands/create-config.ts
945
925
  import chalk4 from "chalk";
926
+ import inquirer2 from "inquirer";
946
927
 
947
- // src/config/pull-config.ts
948
- var DEFAULT_OUTPUT_DIR = "./src/generated";
949
- async function loadPullConfig(flags) {
950
- let fileConfig = {};
951
- const configPath = findConfigFile(flags.config);
952
- if (configPath) {
953
- const full = await loadConfigFile(configPath);
954
- fileConfig = full.pull ?? {};
928
+ // src/scaffold/scaffold.ts
929
+ import * as fs4 from "fs";
930
+ import * as path2 from "path";
931
+
932
+ // src/scaffold/package-manager.ts
933
+ import * as fs3 from "fs";
934
+ import * as path from "path";
935
+ function detectPackageManager() {
936
+ const lockFiles = [
937
+ { file: "pnpm-lock.yaml", manager: "pnpm" },
938
+ { file: "yarn.lock", manager: "yarn" },
939
+ { file: "package-lock.json", manager: "npm" }
940
+ ];
941
+ let dir = process.cwd();
942
+ while (true) {
943
+ for (const { file, manager } of lockFiles) {
944
+ if (fs3.existsSync(path.join(dir, file))) {
945
+ return getManagerInfo(manager);
946
+ }
947
+ }
948
+ const parentDir = path.dirname(dir);
949
+ if (parentDir === dir) {
950
+ break;
951
+ }
952
+ dir = parentDir;
955
953
  }
956
- let outputDir;
957
- if (flags.out) {
958
- outputDir = flags.out;
959
- } else if (typeof fileConfig.output === "string") {
960
- outputDir = fileConfig.output;
961
- } else if (typeof fileConfig.output === "object" && fileConfig.output?.types) {
962
- const legacyTypes = fileConfig.output.types;
963
- outputDir = legacyTypes.replace(/\/types\/?$/, "") || DEFAULT_OUTPUT_DIR;
964
- } else {
965
- outputDir = DEFAULT_OUTPUT_DIR;
954
+ return getManagerInfo("npm");
955
+ }
956
+ function getManagerInfo(manager) {
957
+ switch (manager) {
958
+ case "pnpm":
959
+ return {
960
+ name: "pnpm",
961
+ installCommand: "pnpm install",
962
+ execCommand: "pnpm dlx"
963
+ };
964
+ case "yarn":
965
+ return {
966
+ name: "yarn",
967
+ installCommand: "yarn install",
968
+ execCommand: "yarn dlx"
969
+ };
970
+ case "npm":
971
+ return {
972
+ name: "npm",
973
+ installCommand: "npm install",
974
+ execCommand: "npx"
975
+ };
976
+ }
977
+ }
978
+
979
+ // src/scaffold/scaffold.ts
980
+ async function scaffold(projectName, configType, apiUrl) {
981
+ const projectDir = path2.resolve(process.cwd(), projectName);
982
+ if (fs4.existsSync(projectDir)) {
983
+ throw new Error(
984
+ `Directory "${projectName}" already exists. Choose a different name or remove the existing directory.`
985
+ );
986
+ }
987
+ const files = getFiles(projectName, configType, apiUrl);
988
+ for (const [filePath, content] of Object.entries(files)) {
989
+ const fullPath = path2.join(projectDir, filePath);
990
+ const dir = path2.dirname(fullPath);
991
+ if (!fs4.existsSync(dir)) {
992
+ fs4.mkdirSync(dir, { recursive: true });
993
+ }
994
+ fs4.writeFileSync(fullPath, content, "utf-8");
995
+ }
996
+ const pm = detectPackageManager();
997
+ console.log(" Files created:");
998
+ console.log();
999
+ for (const filePath of Object.keys(files)) {
1000
+ console.log(` ${filePath}`);
966
1001
  }
967
- const only = flags.only ? flags.only.split(",").map((s) => s.trim()) : fileConfig.only ?? [];
968
- const includeInline = fileConfig.includeInline ?? true;
969
- const prettier = flags.noPrettier ? false : fileConfig.prettier ?? true;
970
- const dryRun = flags.dryRun ?? false;
1002
+ console.log();
1003
+ console.log(" Done! Next steps:");
1004
+ console.log();
1005
+ console.log(` cd ${projectName}`);
1006
+ console.log(` ${pm.installCommand}`);
1007
+ console.log(` cp ui/.env.example ui/.env.local`);
1008
+ console.log(` cp api/.env.example api/.env.local`);
1009
+ console.log(` ${pm.name === "npm" ? "npm run" : pm.name} dev`);
1010
+ console.log();
1011
+ }
1012
+ function getFiles(projectName, configType, apiUrl) {
971
1013
  return {
972
- output: { types: outputDir },
973
- only,
974
- includeInline,
975
- prettier,
976
- dryRun
1014
+ // Root
1015
+ "package.json": getRootPackageJson(projectName),
1016
+ // Config manifest
1017
+ "foir.config.ts": getFoirConfig(projectName, configType),
1018
+ // UI (Vite SPA)
1019
+ "ui/package.json": getUiPackageJson(projectName),
1020
+ "ui/tsconfig.json": getUiTsconfig(),
1021
+ "ui/vite.config.ts": getUiViteConfig(),
1022
+ "ui/index.html": getUiIndexHtml(projectName),
1023
+ "ui/.env.example": getUiEnvExample(apiUrl),
1024
+ "ui/.gitignore": getUiGitignore(),
1025
+ "ui/src/main.tsx": getUiMain(),
1026
+ "ui/src/App.tsx": getUiApp(configType),
1027
+ "ui/src/index.css": getUiCss(),
1028
+ "ui/src/vite-env.d.ts": '/// <reference types="vite/client" />\n',
1029
+ // API (Hono)
1030
+ "api/package.json": getApiPackageJson(projectName),
1031
+ "api/tsconfig.json": getApiTsconfig(),
1032
+ "api/.env.example": getApiEnvExample(apiUrl),
1033
+ "api/.gitignore": "node_modules\ndist\n.env\n.env.local\n",
1034
+ "api/src/index.ts": getApiIndex(),
1035
+ "api/src/routes/webhooks.ts": getApiWebhooks(),
1036
+ "api/src/routes/health.ts": getApiHealth()
1037
+ };
1038
+ }
1039
+ function getRootPackageJson(name) {
1040
+ const pkg = {
1041
+ name,
1042
+ version: "0.1.0",
1043
+ private: true,
1044
+ scripts: {
1045
+ dev: 'concurrently "pnpm --filter ./ui dev" "pnpm --filter ./api dev"',
1046
+ build: "pnpm --filter ./ui build && pnpm --filter ./api build"
1047
+ },
1048
+ devDependencies: {
1049
+ concurrently: "^9.0.0",
1050
+ "@eide/foir-cli": "^0.1.0"
1051
+ }
977
1052
  };
1053
+ return JSON.stringify(pkg, null, 2) + "\n";
978
1054
  }
1055
+ function getFoirConfig(name, configType) {
1056
+ const displayName = name.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
1057
+ return `import { defineConfig } from '@eide/foir-cli/configs';
979
1058
 
980
- // src/graphql/generated.ts
981
- var GetCustomerProfileSchemaDocument = {
982
- kind: "Document",
983
- definitions: [
984
- {
985
- kind: "OperationDefinition",
986
- operation: "query",
987
- name: { kind: "Name", value: "GetCustomerProfileSchema" },
988
- selectionSet: {
989
- kind: "SelectionSet",
990
- selections: [
991
- {
992
- kind: "Field",
993
- name: { kind: "Name", value: "customerProfileSchema" },
994
- selectionSet: {
995
- kind: "SelectionSet",
996
- selections: [
997
- { kind: "Field", name: { kind: "Name", value: "id" } },
998
- {
999
- kind: "Field",
1000
- name: { kind: "Name", value: "fields" },
1001
- selectionSet: {
1002
- kind: "SelectionSet",
1003
- selections: [
1004
- { kind: "Field", name: { kind: "Name", value: "key" } },
1005
- { kind: "Field", name: { kind: "Name", value: "type" } },
1006
- { kind: "Field", name: { kind: "Name", value: "label" } },
1007
- {
1008
- kind: "Field",
1009
- name: { kind: "Name", value: "required" }
1010
- },
1011
- {
1012
- kind: "Field",
1013
- name: { kind: "Name", value: "helpText" }
1014
- },
1015
- {
1016
- kind: "Field",
1017
- name: { kind: "Name", value: "defaultValue" }
1018
- },
1019
- {
1020
- kind: "Field",
1021
- name: { kind: "Name", value: "config" }
1022
- },
1023
- {
1024
- kind: "Field",
1025
- name: { kind: "Name", value: "validation" },
1026
- selectionSet: {
1027
- kind: "SelectionSet",
1028
- selections: [
1029
- {
1030
- kind: "Field",
1031
- name: { kind: "Name", value: "rule" }
1032
- },
1033
- {
1034
- kind: "Field",
1035
- name: { kind: "Name", value: "value" }
1036
- },
1037
- {
1038
- kind: "Field",
1039
- name: { kind: "Name", value: "message" }
1040
- }
1041
- ]
1042
- }
1043
- }
1044
- ]
1045
- }
1046
- },
1047
- {
1048
- kind: "Field",
1049
- name: { kind: "Name", value: "publicFields" }
1050
- },
1051
- { kind: "Field", name: { kind: "Name", value: "version" } },
1052
- { kind: "Field", name: { kind: "Name", value: "createdAt" } },
1053
- { kind: "Field", name: { kind: "Name", value: "updatedAt" } }
1054
- ]
1055
- }
1056
- }
1057
- ]
1058
- }
1059
- }
1060
- ]
1061
- };
1062
- var ModelsForCodegenDocument = {
1063
- kind: "Document",
1064
- definitions: [
1065
- {
1066
- kind: "OperationDefinition",
1067
- operation: "query",
1068
- name: { kind: "Name", value: "ModelsForCodegen" },
1069
- variableDefinitions: [
1070
- {
1071
- kind: "VariableDefinition",
1072
- variable: {
1073
- kind: "Variable",
1074
- name: { kind: "Name", value: "search" }
1075
- },
1076
- type: { kind: "NamedType", name: { kind: "Name", value: "String" } }
1077
- },
1078
- {
1079
- kind: "VariableDefinition",
1080
- variable: {
1081
- kind: "Variable",
1082
- name: { kind: "Name", value: "limit" }
1083
- },
1084
- type: { kind: "NamedType", name: { kind: "Name", value: "Int" } }
1085
- },
1086
- {
1087
- kind: "VariableDefinition",
1088
- variable: {
1089
- kind: "Variable",
1090
- name: { kind: "Name", value: "offset" }
1091
- },
1092
- type: { kind: "NamedType", name: { kind: "Name", value: "Int" } }
1093
- }
1094
- ],
1095
- selectionSet: {
1096
- kind: "SelectionSet",
1097
- selections: [
1098
- {
1099
- kind: "Field",
1100
- name: { kind: "Name", value: "models" },
1101
- arguments: [
1102
- {
1103
- kind: "Argument",
1104
- name: { kind: "Name", value: "search" },
1105
- value: {
1106
- kind: "Variable",
1107
- name: { kind: "Name", value: "search" }
1108
- }
1109
- },
1110
- {
1111
- kind: "Argument",
1112
- name: { kind: "Name", value: "limit" },
1113
- value: {
1114
- kind: "Variable",
1115
- name: { kind: "Name", value: "limit" }
1116
- }
1117
- },
1118
- {
1119
- kind: "Argument",
1120
- name: { kind: "Name", value: "offset" },
1121
- value: {
1122
- kind: "Variable",
1123
- name: { kind: "Name", value: "offset" }
1124
- }
1125
- }
1126
- ],
1127
- selectionSet: {
1128
- kind: "SelectionSet",
1129
- selections: [
1130
- {
1131
- kind: "Field",
1132
- name: { kind: "Name", value: "items" },
1133
- selectionSet: {
1134
- kind: "SelectionSet",
1135
- selections: [
1136
- { kind: "Field", name: { kind: "Name", value: "id" } },
1137
- { kind: "Field", name: { kind: "Name", value: "key" } },
1138
- { kind: "Field", name: { kind: "Name", value: "name" } },
1139
- {
1140
- kind: "Field",
1141
- name: { kind: "Name", value: "pluralName" }
1142
- },
1143
- {
1144
- kind: "Field",
1145
- name: { kind: "Name", value: "description" }
1146
- },
1147
- {
1148
- kind: "Field",
1149
- name: { kind: "Name", value: "category" }
1150
- },
1151
- {
1152
- kind: "Field",
1153
- name: { kind: "Name", value: "fields" }
1154
- },
1155
- {
1156
- kind: "Field",
1157
- name: { kind: "Name", value: "config" }
1158
- },
1159
- { kind: "Field", name: { kind: "Name", value: "hooks" } },
1160
- {
1161
- kind: "Field",
1162
- name: { kind: "Name", value: "createdAt" }
1163
- },
1164
- {
1165
- kind: "Field",
1166
- name: { kind: "Name", value: "updatedAt" }
1167
- }
1168
- ]
1169
- }
1170
- },
1171
- { kind: "Field", name: { kind: "Name", value: "total" } }
1172
- ]
1173
- }
1174
- }
1175
- ]
1176
- }
1177
- }
1178
- ]
1179
- };
1180
- var GlobalSearchDocument = {
1181
- kind: "Document",
1182
- definitions: [
1183
- {
1184
- kind: "OperationDefinition",
1185
- operation: "query",
1186
- name: { kind: "Name", value: "GlobalSearch" },
1187
- variableDefinitions: [
1188
- {
1189
- kind: "VariableDefinition",
1190
- variable: {
1191
- kind: "Variable",
1192
- name: { kind: "Name", value: "query" }
1193
- },
1194
- type: {
1195
- kind: "NonNullType",
1196
- type: {
1197
- kind: "NamedType",
1198
- name: { kind: "Name", value: "String" }
1199
- }
1200
- }
1201
- },
1202
- {
1203
- kind: "VariableDefinition",
1204
- variable: {
1205
- kind: "Variable",
1206
- name: { kind: "Name", value: "limit" }
1207
- },
1208
- type: { kind: "NamedType", name: { kind: "Name", value: "Int" } }
1209
- },
1210
- {
1211
- kind: "VariableDefinition",
1212
- variable: {
1213
- kind: "Variable",
1214
- name: { kind: "Name", value: "modelKeys" }
1215
- },
1216
- type: {
1217
- kind: "ListType",
1218
- type: {
1219
- kind: "NonNullType",
1220
- type: {
1221
- kind: "NamedType",
1222
- name: { kind: "Name", value: "String" }
1223
- }
1224
- }
1225
- }
1226
- },
1227
- {
1228
- kind: "VariableDefinition",
1229
- variable: {
1230
- kind: "Variable",
1231
- name: { kind: "Name", value: "includeMedia" }
1232
- },
1233
- type: { kind: "NamedType", name: { kind: "Name", value: "Boolean" } }
1234
- }
1235
- ],
1236
- selectionSet: {
1237
- kind: "SelectionSet",
1238
- selections: [
1239
- {
1240
- kind: "Field",
1241
- name: { kind: "Name", value: "globalSearch" },
1242
- arguments: [
1243
- {
1244
- kind: "Argument",
1245
- name: { kind: "Name", value: "query" },
1246
- value: {
1247
- kind: "Variable",
1248
- name: { kind: "Name", value: "query" }
1249
- }
1250
- },
1251
- {
1252
- kind: "Argument",
1253
- name: { kind: "Name", value: "limit" },
1254
- value: {
1255
- kind: "Variable",
1256
- name: { kind: "Name", value: "limit" }
1257
- }
1258
- },
1259
- {
1260
- kind: "Argument",
1261
- name: { kind: "Name", value: "modelKeys" },
1262
- value: {
1263
- kind: "Variable",
1264
- name: { kind: "Name", value: "modelKeys" }
1265
- }
1266
- },
1267
- {
1268
- kind: "Argument",
1269
- name: { kind: "Name", value: "includeMedia" },
1270
- value: {
1271
- kind: "Variable",
1272
- name: { kind: "Name", value: "includeMedia" }
1273
- }
1274
- }
1275
- ],
1276
- selectionSet: {
1277
- kind: "SelectionSet",
1278
- selections: [
1279
- {
1280
- kind: "Field",
1281
- name: { kind: "Name", value: "records" },
1282
- selectionSet: {
1283
- kind: "SelectionSet",
1284
- selections: [
1285
- { kind: "Field", name: { kind: "Name", value: "id" } },
1286
- {
1287
- kind: "Field",
1288
- name: { kind: "Name", value: "modelKey" }
1289
- },
1290
- { kind: "Field", name: { kind: "Name", value: "title" } },
1291
- {
1292
- kind: "Field",
1293
- name: { kind: "Name", value: "naturalKey" }
1294
- },
1295
- {
1296
- kind: "Field",
1297
- name: { kind: "Name", value: "subtitle" }
1298
- },
1299
- {
1300
- kind: "Field",
1301
- name: { kind: "Name", value: "updatedAt" }
1302
- }
1303
- ]
1304
- }
1305
- },
1306
- {
1307
- kind: "Field",
1308
- name: { kind: "Name", value: "media" },
1309
- selectionSet: {
1310
- kind: "SelectionSet",
1311
- selections: [
1312
- { kind: "Field", name: { kind: "Name", value: "id" } },
1313
- {
1314
- kind: "Field",
1315
- name: { kind: "Name", value: "fileName" }
1316
- },
1317
- {
1318
- kind: "Field",
1319
- name: { kind: "Name", value: "altText" }
1320
- },
1321
- {
1322
- kind: "Field",
1323
- name: { kind: "Name", value: "fileUrl" }
1324
- }
1325
- ]
1326
- }
1327
- }
1328
- ]
1329
- }
1330
- }
1331
- ]
1332
- }
1333
- }
1334
- ]
1335
- };
1336
-
1337
- // src/codegen/fetch-models.ts
1338
- function normalizeConfig(raw) {
1339
- return {
1340
- records: Boolean(raw.records ?? true),
1341
- inline: Boolean(raw.inline ?? false),
1342
- publicApi: Boolean(raw.publicApi ?? false),
1343
- versioning: Boolean(raw.versioning ?? false),
1344
- publishing: Boolean(raw.publishing ?? false),
1345
- variants: Boolean(raw.variants ?? false),
1346
- customerScoped: Boolean(raw.customerScoped ?? false),
1347
- sharing: raw.sharing ? {
1348
- enabled: Boolean(
1349
- raw.sharing.enabled ?? false
1350
- ),
1351
- requireAcceptance: Boolean(
1352
- raw.sharing.requireAcceptance ?? true
1353
- )
1354
- } : void 0,
1355
- embeddings: raw.embeddings
1356
- };
1357
- }
1358
- function normalizeField(raw) {
1359
- const field = raw;
1360
- const options = { ...field.options ?? {} };
1361
- const resolvedType = field.type === "composite" && field.config?.schema ? field.config.schema : field.type;
1362
- if (!options.itemType) {
1363
- const resolved = field.itemType ?? field.config?.itemType ?? field.config?.itemSchema;
1364
- if (resolved) {
1365
- options.itemType = resolved;
1366
- }
1367
- }
1368
- if (field.config?.minItems !== void 0 && options.minItems === void 0) {
1369
- options.minItems = field.config.minItems;
1370
- }
1371
- if (field.config?.maxItems !== void 0 && options.maxItems === void 0) {
1372
- options.maxItems = field.config.maxItems;
1373
- }
1374
- return {
1375
- key: field.key,
1376
- type: resolvedType,
1377
- label: field.label,
1378
- required: field.required,
1379
- helpText: field.helpText,
1380
- options: Object.keys(options).length > 0 ? options : void 0
1381
- };
1382
- }
1383
- async function fetchModelsForCodegen(client) {
1384
- const data = await client.request(ModelsForCodegenDocument, {
1385
- limit: 500
1386
- });
1387
- return data.models.items.map((item) => ({
1388
- key: item.key,
1389
- name: item.name,
1390
- pluralName: item.pluralName ?? void 0,
1391
- description: item.description ?? void 0,
1392
- category: item.category ?? void 0,
1393
- fields: (item.fields ?? []).map(
1394
- (f) => normalizeField(f)
1395
- ),
1396
- config: normalizeConfig(item.config ?? {}),
1397
- hooks: item.hooks ?? void 0
1398
- }));
1399
- }
1400
- function filterModels(models, options) {
1401
- let filtered = models;
1402
- if (options.only && options.only.length > 0) {
1403
- const onlySet = new Set(options.only);
1404
- filtered = filtered.filter((m) => onlySet.has(m.key));
1405
- }
1406
- if (!options.includeInline) {
1407
- filtered = filtered.filter((m) => m.config.records);
1408
- }
1409
- return filtered;
1410
- }
1411
-
1412
- // src/codegen/fetch-customer-profile-schema.ts
1413
- async function fetchCustomerProfileSchema(client) {
1414
- const data = await client.request(GetCustomerProfileSchemaDocument);
1415
- if (!data.customerProfileSchema) return null;
1416
- return {
1417
- id: data.customerProfileSchema.id,
1418
- version: data.customerProfileSchema.version,
1419
- fields: (data.customerProfileSchema.fields ?? []).map((f) => ({
1420
- key: f.key,
1421
- type: f.type,
1422
- label: f.label,
1423
- required: f.required ?? void 0,
1424
- helpText: f.helpText ?? void 0,
1425
- options: f.config
1426
- }))
1427
- };
1428
- }
1429
-
1430
- // src/codegen/field-mapping.ts
1431
- var PRIMITIVE_FIELD_TYPES = /* @__PURE__ */ new Set([
1432
- "text",
1433
- "richtext",
1434
- "number",
1435
- "boolean",
1436
- "email",
1437
- "phone",
1438
- "url",
1439
- "date",
1440
- "image",
1441
- "video",
1442
- "file",
1443
- "currency",
1444
- "select",
1445
- "multiselect",
1446
- "json",
1447
- "list",
1448
- "reference",
1449
- "link",
1450
- "flexible",
1451
- "model"
1452
- ]);
1453
- function isPrimitiveFieldType(type) {
1454
- return PRIMITIVE_FIELD_TYPES.has(type);
1455
- }
1456
- var FIELD_TYPE_MAPPING = {
1457
- text: { outputType: "string", inputType: "string" },
1458
- richtext: {
1459
- outputType: "RichtextValue",
1460
- inputType: "RichtextValue",
1461
- needsImport: "field-types"
1462
- },
1463
- number: { outputType: "number", inputType: "number" },
1464
- boolean: { outputType: "boolean", inputType: "boolean" },
1465
- email: { outputType: "string", inputType: "string" },
1466
- phone: { outputType: "string", inputType: "string" },
1467
- url: { outputType: "string", inputType: "string" },
1468
- date: { outputType: "Date", inputType: "string" },
1469
- image: {
1470
- outputType: "ImageValue",
1471
- inputType: "string",
1472
- needsImport: "field-types"
1473
- },
1474
- video: {
1475
- outputType: "VideoValue",
1476
- inputType: "string",
1477
- needsImport: "field-types"
1478
- },
1479
- file: {
1480
- outputType: "FileValue",
1481
- inputType: "string",
1482
- needsImport: "field-types"
1483
- },
1484
- currency: {
1485
- outputType: "CurrencyValue",
1486
- inputType: "CurrencyValue",
1487
- needsImport: "field-types"
1488
- },
1489
- select: { outputType: "string", inputType: "string" },
1490
- multiselect: { outputType: "string[]", inputType: "string[]" },
1491
- json: { outputType: "unknown", inputType: "unknown" },
1492
- list: { outputType: "unknown[]", inputType: "unknown[]" },
1493
- flexible: {
1494
- outputType: "FlexibleFieldItem[]",
1495
- inputType: "FlexibleFieldItem[]",
1496
- needsImport: "field-types"
1497
- },
1498
- reference: {
1499
- outputType: "ReferenceValue",
1500
- inputType: "ReferenceValue",
1501
- needsImport: "field-types"
1502
- },
1503
- link: {
1504
- outputType: "LinkValue",
1505
- inputType: "LinkValue",
1506
- needsImport: "field-types"
1507
- },
1508
- model: { outputType: "string", inputType: "string" }
1509
- };
1510
- function getFieldType(field, mode = "output") {
1511
- if (!field?.type) return "unknown";
1512
- const mapping = FIELD_TYPE_MAPPING[field.type];
1513
- if (!mapping) {
1514
- if (isPrimitiveFieldType(field.type)) return "unknown";
1515
- return toPascalCase(field.type);
1516
- }
1517
- let tsType = mode === "output" ? mapping.outputType : mapping.inputType;
1518
- if (field.type === "select" && field.options?.options) {
1519
- const options = field.options.options;
1520
- if (options.length > 0) {
1521
- tsType = options.map((o) => `'${o.value}'`).join(" | ");
1522
- }
1523
- }
1524
- if (field.type === "multiselect" && field.options?.options) {
1525
- const options = field.options.options;
1526
- if (options.length > 0) {
1527
- tsType = `(${options.map((o) => `'${o.value}'`).join(" | ")})[]`;
1528
- }
1529
- }
1530
- if (field.type === "list" && field.options?.itemType) {
1531
- const itemType = field.options.itemType;
1532
- const itemMapping = FIELD_TYPE_MAPPING[itemType];
1533
- if (itemMapping) {
1534
- tsType = `${mode === "output" ? itemMapping.outputType : itemMapping.inputType}[]`;
1535
- } else if (!isPrimitiveFieldType(itemType)) {
1536
- tsType = `${toPascalCase(itemType)}[]`;
1537
- }
1538
- }
1539
- return tsType;
1540
- }
1541
- function getReferenceTypeModelRefs(fields) {
1542
- const refs = /* @__PURE__ */ new Set();
1543
- for (const field of fields) {
1544
- if (field.type === "reference" && field.options?.referenceTypes) {
1545
- for (const rt of field.options.referenceTypes) {
1546
- refs.add(rt);
1547
- }
1548
- }
1549
- }
1550
- return refs;
1551
- }
1552
- function getInlineSchemaReferences(fields) {
1553
- const refs = /* @__PURE__ */ new Set();
1554
- for (const field of fields) {
1555
- if (!isPrimitiveFieldType(field.type) && !FIELD_TYPE_MAPPING[field.type]) {
1556
- refs.add(field.type);
1557
- }
1558
- if (field.type === "list" && field.options?.itemType) {
1559
- const itemType = field.options.itemType;
1560
- if (!isPrimitiveFieldType(itemType) && !FIELD_TYPE_MAPPING[itemType]) {
1561
- refs.add(itemType);
1562
- }
1563
- }
1564
- }
1565
- return refs;
1566
- }
1567
- function toCamelCase(str) {
1568
- if (!str) return "unknown";
1569
- return str.replace(
1570
- /[-_]([a-z])/g,
1571
- (_, letter) => letter.toUpperCase()
1572
- );
1573
- }
1574
- function toPascalCase(str) {
1575
- if (!str) return "Unknown";
1576
- const camel = toCamelCase(str);
1577
- return camel.charAt(0).toUpperCase() + camel.slice(1);
1578
- }
1579
- function sanitizeFieldName(key) {
1580
- if (!key) return "unknown";
1581
- const camel = toCamelCase(key);
1582
- if (/^[0-9]/.test(camel)) return `_${camel}`;
1583
- return camel;
1584
- }
1585
- function generateFieldDef(field) {
1586
- const parts = [];
1587
- parts.push(`key: '${field.key ?? "unknown"}'`);
1588
- parts.push(`type: '${field.type ?? "text"}'`);
1589
- parts.push(
1590
- `label: '${(field.label ?? field.key ?? "Unknown").replace(/'/g, "\\'")}'`
1591
- );
1592
- if (field.required) parts.push("required: true");
1593
- if (field.helpText)
1594
- parts.push(`helpText: '${field.helpText.replace(/'/g, "\\'")}'`);
1595
- if (field.type === "text" && field.options) {
1596
- if (field.options.maxLength)
1597
- parts.push(`maxLength: ${field.options.maxLength}`);
1598
- if (field.options.minLength)
1599
- parts.push(`minLength: ${field.options.minLength}`);
1600
- }
1601
- if (field.type === "number" && field.options) {
1602
- if (field.options.min !== void 0)
1603
- parts.push(`min: ${field.options.min}`);
1604
- if (field.options.max !== void 0)
1605
- parts.push(`max: ${field.options.max}`);
1606
- if (field.options.step !== void 0)
1607
- parts.push(`step: ${field.options.step}`);
1608
- }
1609
- if ((field.type === "select" || field.type === "multiselect") && field.options?.options) {
1610
- const options = field.options.options;
1611
- const optionsStr = options.filter((o) => o.value !== void 0).map((o) => {
1612
- const label = (o.label ?? o.value ?? "").replace(/'/g, "\\'");
1613
- const value = (o.value ?? "").replace(/'/g, "\\'");
1614
- return `{ label: '${label}', value: '${value}' }`;
1615
- }).join(", ");
1616
- parts.push(`options: [${optionsStr}]`);
1617
- }
1618
- if (field.type === "reference" && field.options?.referenceTypes) {
1619
- const refTypes = field.options.referenceTypes;
1620
- parts.push(`referenceTypes: [${refTypes.map((t) => `'${t}'`).join(", ")}]`);
1621
- if (field.options.multiple) parts.push("multiple: true");
1622
- }
1623
- if (field.type === "list" && field.options?.itemType) {
1624
- parts.push(`itemType: '${field.options.itemType}'`);
1625
- if (field.options.minItems !== void 0)
1626
- parts.push(`minItems: ${field.options.minItems}`);
1627
- if (field.options.maxItems !== void 0)
1628
- parts.push(`maxItems: ${field.options.maxItems}`);
1629
- }
1630
- if ((field.type === "image" || field.type === "video" || field.type === "file") && field.options) {
1631
- if (field.options.allowedTypes) {
1632
- const types = field.options.allowedTypes;
1633
- parts.push(`allowedTypes: [${types.map((t) => `'${t}'`).join(", ")}]`);
1634
- }
1635
- if (field.options.maxSize) parts.push(`maxSize: ${field.options.maxSize}`);
1636
- }
1637
- return `{ ${parts.join(", ")} }`;
1638
- }
1639
-
1640
- // src/codegen/generators/model-types.ts
1641
- function isInlineOnlyModel(model) {
1642
- return model.config.inline && !model.config.records;
1643
- }
1644
- function generateModelTypes(model, allModels) {
1645
- const typeName = toPascalCase(model.key);
1646
- const configName = toCamelCase(model.key) + "Config";
1647
- const fields = model.fields ?? [];
1648
- const fieldTypeImports = getFieldTypeImportsForFields(fields);
1649
- const inlineSchemaRefs = getInlineSchemaReferences(fields);
1650
- const referenceModelRefs = getReferenceTypeModelRefs(fields);
1651
- let code = buildImportStatements(
1652
- model,
1653
- fieldTypeImports,
1654
- inlineSchemaRefs,
1655
- referenceModelRefs,
1656
- allModels
1657
- );
1658
- if (isInlineOnlyModel(model)) {
1659
- code += generateDataInterface(model, fields, typeName, allModels);
1660
- return code;
1661
- }
1662
- code += generateConfigObject(model, fields, configName);
1663
- code += "\n";
1664
- code += generateDataInterface(model, fields, typeName + "Data", allModels);
1665
- return code;
1666
- }
1667
- function buildImportStatements(model, fieldTypeImports, inlineSchemaRefs, referenceModelRefs, allModels) {
1668
- const imports = [];
1669
- if (fieldTypeImports.size > 0) {
1670
- const types = Array.from(fieldTypeImports).sort().join(", ");
1671
- imports.push(`import type { ${types} } from '@eide/foir-client';`);
1672
- }
1673
- const allModelRefKeys = /* @__PURE__ */ new Set([
1674
- ...inlineSchemaRefs,
1675
- ...referenceModelRefs
1676
- ]);
1677
- for (const refKey of allModelRefKeys) {
1678
- if (refKey === model.key) continue;
1679
- const refModel = allModels.find((m) => m.key === refKey);
1680
- if (refModel) {
1681
- const refTypeName = isInlineOnlyModel(refModel) ? toPascalCase(refKey) : toPascalCase(refKey) + "Data";
1682
- imports.push(`import type { ${refTypeName} } from './${refKey}.js';`);
1683
- }
1684
- }
1685
- return imports.length > 0 ? imports.join("\n") + "\n\n" : "";
1686
- }
1687
- function generateConfigObject(model, fields, configName) {
1688
- const lines = [];
1689
- lines.push("/**");
1690
- lines.push(` * ${model.name} Configuration`);
1691
- if (model.description) lines.push(` * ${model.description}`);
1692
- lines.push(` *`);
1693
- lines.push(` * @generated from model '${model.key}'`);
1694
- lines.push(" */");
1695
- const escapedName = (model.name ?? model.key).replace(/'/g, "\\'");
1696
- lines.push(`export const ${configName} = {`);
1697
- lines.push(` key: '${model.key}',`);
1698
- lines.push(` name: '${escapedName}',`);
1699
- if (model.description) {
1700
- lines.push(` description: '${model.description.replace(/'/g, "\\'")}',`);
1701
- }
1702
- lines.push("");
1703
- lines.push(" // Capability flags");
1704
- lines.push(` records: ${model.config.records},`);
1705
- lines.push(` inline: ${model.config.inline},`);
1706
- lines.push(` publicApi: ${model.config.publicApi},`);
1707
- lines.push(` versioning: ${model.config.versioning},`);
1708
- lines.push(` publishing: ${model.config.publishing},`);
1709
- lines.push(` variants: ${model.config.variants},`);
1710
- lines.push(` customerScoped: ${model.config.customerScoped},`);
1711
- if (model.config.embeddings?.enabled) {
1712
- lines.push("");
1713
- lines.push(" // Embeddings");
1714
- lines.push(
1715
- ` embeddings: ${JSON.stringify(model.config.embeddings, null, 4).replace(/\n/g, "\n ")},`
1716
- );
1717
- }
1718
- if (model.hooks && Object.keys(model.hooks).length > 0) {
1719
- lines.push("");
1720
- lines.push(" // Lifecycle hooks");
1721
- lines.push(
1722
- ` hooks: ${JSON.stringify(model.hooks, null, 4).replace(/\n/g, "\n ")},`
1723
- );
1724
- } else {
1725
- lines.push("");
1726
- lines.push(" hooks: {},");
1727
- }
1728
- lines.push("");
1729
- lines.push(" // Field definitions");
1730
- if (fields.length === 0) {
1731
- lines.push(" fieldDefs: [],");
1732
- } else {
1733
- lines.push(" fieldDefs: [");
1734
- for (const field of fields) {
1735
- lines.push(` ${generateFieldDef(field)},`);
1736
- }
1737
- lines.push(" ],");
1738
- }
1739
- lines.push("} as const;");
1740
- return lines.join("\n") + "\n";
1741
- }
1742
- function generateDataInterface(model, fields, interfaceName, allModels) {
1743
- const lines = [];
1744
- lines.push("/**");
1745
- lines.push(` * ${model.name} Data`);
1746
- lines.push(` * Field values only \u2014 no system fields`);
1747
- lines.push(` *`);
1748
- lines.push(` * @generated from model '${model.key}'`);
1749
- lines.push(" */");
1750
- lines.push(`export interface ${interfaceName} {`);
1751
- for (const field of fields) {
1752
- const fieldName = sanitizeFieldName(field.key);
1753
- let fieldType = getFieldType(field, "output");
1754
- const refModel = allModels.find((m) => m.key === field.type);
1755
- if (refModel && !isInlineOnlyModel(refModel)) {
1756
- fieldType = toPascalCase(field.type) + "Data";
1757
- }
1758
- if (field.type === "list" && field.options?.itemType) {
1759
- const itemRefModel = allModels.find(
1760
- (m) => m.key === field.options.itemType
1761
- );
1762
- if (itemRefModel && !isInlineOnlyModel(itemRefModel)) {
1763
- fieldType = toPascalCase(field.options.itemType) + "Data[]";
1764
- }
1765
- }
1766
- if (field.type === "reference" && field.options?.referenceTypes) {
1767
- const refTypes = field.options.referenceTypes;
1768
- const resolvedPreviewTypes = [];
1769
- for (const refKey of refTypes) {
1770
- const targetModel = allModels.find((m) => m.key === refKey);
1771
- if (targetModel) {
1772
- const targetTypeName = isInlineOnlyModel(targetModel) ? toPascalCase(refKey) : toPascalCase(refKey) + "Data";
1773
- resolvedPreviewTypes.push(`Partial<${targetTypeName}>`);
1774
- }
1775
- }
1776
- if (resolvedPreviewTypes.length === refTypes.length && resolvedPreviewTypes.length > 0) {
1777
- fieldType = `ReferenceValue<${resolvedPreviewTypes.join(" | ")}>`;
1778
- }
1779
- }
1780
- const optional = field.required ? "" : "?";
1781
- const comment = field.helpText ? ` /** ${field.helpText} */
1782
- ` : "";
1783
- lines.push(comment + ` ${fieldName}${optional}: ${fieldType};`);
1784
- }
1785
- lines.push("}");
1786
- return lines.join("\n") + "\n";
1787
- }
1788
- function getFieldTypeImportsForFields(fields) {
1789
- const imports = /* @__PURE__ */ new Set();
1790
- for (const field of fields) {
1791
- const mapping = FIELD_TYPE_MAPPING[field.type];
1792
- if (mapping?.needsImport === "field-types") {
1793
- imports.add(mapping.outputType.replace(/\[\]$/, ""));
1794
- }
1795
- if (field.type === "list" && field.options?.itemType) {
1796
- const itemMapping = FIELD_TYPE_MAPPING[field.options.itemType];
1797
- if (itemMapping?.needsImport === "field-types") {
1798
- imports.add(itemMapping.outputType.replace(/\[\]$/, ""));
1799
- }
1800
- }
1801
- }
1802
- return imports;
1803
- }
1804
-
1805
- // src/codegen/generators/model-index.ts
1806
- function isInlineOnlyModel2(model) {
1807
- return model.config.inline && !model.config.records;
1808
- }
1809
- function generateModelIndex(models) {
1810
- let code = `/**
1811
- * Model Types and Configs \u2014 Generated re-exports
1812
- *
1813
- * @generated by foir \u2014 DO NOT EDIT MANUALLY
1814
- */
1815
-
1816
- `;
1817
- for (const model of models) {
1818
- const typeName = toPascalCase(model.key);
1819
- const configName = toCamelCase(model.key) + "Config";
1820
- if (isInlineOnlyModel2(model)) {
1821
- code += `export type { ${typeName} } from './${model.key}.js';
1822
- `;
1823
- } else {
1824
- code += `export { ${configName} } from './${model.key}.js';
1825
- `;
1826
- code += `export type { ${typeName}Data } from './${model.key}.js';
1827
- `;
1828
- if (model.config.publicApi) {
1829
- code += `export type { ${typeName}Where, ${typeName}SortField } from './${model.key}.filters.js';
1830
- `;
1831
- }
1832
- }
1833
- }
1834
- return code;
1835
- }
1836
-
1837
- // src/codegen/generators/client-factory.ts
1838
- function generateClientFactory(models, hasCustomerProfile) {
1839
- const publicModels = models.filter(
1840
- (m) => m.config.publicApi && m.config.records
1841
- );
1842
- const lines = [];
1843
- lines.push(`/**
1844
- * Typed Foir client for this project.
1845
- *
1846
- * @generated by foir \u2014 DO NOT EDIT MANUALLY
1847
- */
1848
-
1849
- import {
1850
- createBaseClient,
1851
- createModelAccessor,
1852
- createAuthClient,
1853
- createFilesClient,
1854
- createNotificationsClient,
1855
- createSharingClient,
1856
- createSearchClient,
1857
- createProfileClient,
1858
- createOperationsClient,
1859
- type ClientConfig,
1860
- type ModelAccessor,
1861
- type FoirClient,
1862
- } from '@eide/foir-client';
1863
- `);
1864
- for (const model of publicModels) {
1865
- const typeName = toPascalCase(model.key);
1866
- const dataType = `${typeName}Data`;
1867
- const whereType = `${typeName}Where`;
1868
- const sortType = `${typeName}SortField`;
1869
- lines.push(
1870
- `import type { ${dataType}, ${whereType}, ${sortType} } from './models/${model.key}.js';`
1871
- );
1872
- }
1873
- if (hasCustomerProfile) {
1874
- lines.push(
1875
- `import type { CustomerProfileData } from './models/customer-profile.js';`
1876
- );
1877
- }
1878
- lines.push("");
1879
- lines.push("export interface TypedClient extends FoirClient {");
1880
- for (const model of publicModels) {
1881
- const typeName = toPascalCase(model.key);
1882
- const accessorName = toCamelCase(model.key);
1883
- const dataType = `${typeName}Data`;
1884
- const whereType = `${typeName}Where`;
1885
- const sortType = `${typeName}SortField`;
1886
- lines.push(` ${accessorName}: ModelAccessor<${dataType}, ${whereType}, ${sortType}>;`);
1887
- }
1888
- if (hasCustomerProfile) {
1889
- lines.push(
1890
- ` profile: ReturnType<typeof createProfileClient<CustomerProfileData>>;`
1891
- );
1892
- }
1893
- lines.push("}");
1894
- lines.push("");
1895
- lines.push("export function createClient(config: ClientConfig): TypedClient {");
1896
- lines.push(" const base = createBaseClient(config);");
1897
- lines.push("");
1898
- lines.push(" return {");
1899
- lines.push(" auth: createAuthClient(base),");
1900
- lines.push(" files: createFilesClient(base),");
1901
- lines.push(" notifications: createNotificationsClient(base),");
1902
- lines.push(" sharing: createSharingClient(base),");
1903
- lines.push(" search: createSearchClient(base),");
1904
- if (hasCustomerProfile) {
1905
- lines.push(" profile: createProfileClient<CustomerProfileData>(base),");
1906
- } else {
1907
- lines.push(" profile: createProfileClient(base),");
1908
- }
1909
- lines.push(" operations: createOperationsClient(base),");
1910
- for (const model of publicModels) {
1911
- const typeName = toPascalCase(model.key);
1912
- const accessorName = toCamelCase(model.key);
1913
- const dataType = `${typeName}Data`;
1914
- const whereType = `${typeName}Where`;
1915
- const sortType = `${typeName}SortField`;
1916
- lines.push(
1917
- ` ${accessorName}: createModelAccessor<${dataType}, ${whereType}, ${sortType}>(base, '${model.key}'),`
1918
- );
1919
- }
1920
- lines.push(` model<TData = unknown>(modelKey: string) {`);
1921
- lines.push(` return createModelAccessor<TData>(base, modelKey);`);
1922
- lines.push(` },`);
1923
- lines.push(" request: base.request,");
1924
- lines.push(" setCustomerToken: base.setCustomerToken,");
1925
- lines.push(" setLocale: base.setLocale,");
1926
- lines.push(" setPreview: base.setPreview,");
1927
- lines.push(" } as TypedClient;");
1928
- lines.push("}");
1929
- lines.push("");
1930
- lines.push("// Re-export types from @eide/foir-client for convenience");
1931
- lines.push(`export type {
1932
- ClientConfig,
1933
- TypedRecord,
1934
- TypedList,
1935
- ModelAccessor,
1936
- JsonValue,
1937
- ImageValue,
1938
- VideoValue,
1939
- FileValue,
1940
- LinkValue,
1941
- ReferenceValue,
1942
- FlexibleFieldItem,
1943
- RichtextValue,
1944
- CurrencyValue,
1945
- SelectMap,
1946
- ApplySelect,
1947
- FoirClient,
1948
- } from '@eide/foir-client';`);
1949
- lines.push("");
1950
- for (const model of publicModels) {
1951
- const typeName = toPascalCase(model.key);
1952
- const dataType = `${typeName}Data`;
1953
- const whereType = `${typeName}Where`;
1954
- const sortType = `${typeName}SortField`;
1955
- lines.push(
1956
- `export type { ${dataType}, ${whereType}, ${sortType} } from './models/${model.key}.js';`
1957
- );
1958
- }
1959
- if (hasCustomerProfile) {
1960
- lines.push(
1961
- `export type { CustomerProfileData } from './models/customer-profile.js';`
1962
- );
1963
- }
1964
- return lines.join("\n") + "\n";
1965
- }
1966
-
1967
- // src/codegen/generators/schema-manifest.ts
1968
- function generateSchemaManifest(models, cpSchema) {
1969
- const manifest = {
1970
- $schema: "https://foir.io/schema/v1.json",
1971
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1972
- models: {}
1973
- };
1974
- for (const model of models) {
1975
- manifest.models[model.key] = {
1976
- name: model.name,
1977
- pluralName: model.pluralName,
1978
- description: model.description,
1979
- category: model.category,
1980
- config: model.config,
1981
- fields: model.fields.map((f) => ({
1982
- key: f.key,
1983
- type: f.type,
1984
- label: f.label,
1985
- required: f.required,
1986
- helpText: f.helpText,
1987
- options: f.options
1988
- }))
1989
- };
1990
- }
1991
- if (cpSchema && cpSchema.fields.length > 0) {
1992
- manifest.customerProfile = {
1993
- fields: cpSchema.fields.map((f) => ({
1994
- key: f.key,
1995
- type: f.type,
1996
- label: f.label,
1997
- required: f.required,
1998
- helpText: f.helpText,
1999
- options: f.options
2000
- }))
2001
- };
2002
- }
2003
- return JSON.stringify(manifest, null, 2) + "\n";
2004
- }
2005
-
2006
- // src/codegen/generators/model-filters.ts
2007
- function isInlineOnlyModel3(model) {
2008
- return model.config.inline && !model.config.records;
2009
- }
2010
- function getFilterType(fieldType) {
2011
- switch (fieldType) {
2012
- case "text":
2013
- case "richtext":
2014
- case "email":
2015
- case "phone":
2016
- case "url":
2017
- case "select":
2018
- return "StringFilter";
2019
- case "number":
2020
- case "currency":
2021
- return "NumberFilter";
2022
- case "boolean":
2023
- return "BooleanFilter";
2024
- case "date":
2025
- return "DateFilter";
2026
- default:
2027
- return null;
2028
- }
2029
- }
2030
- function isSortable(fieldType) {
2031
- return ["text", "number", "date", "boolean", "email", "url", "select"].includes(fieldType);
2032
- }
2033
- function generateModelWhere(model) {
2034
- if (isInlineOnlyModel3(model)) return "";
2035
- const typeName = toPascalCase(model.key);
2036
- const whereName = `${typeName}Where`;
2037
- const fields = model.fields ?? [];
2038
- const filterImports = /* @__PURE__ */ new Set();
2039
- const filterFields = [];
2040
- for (const field of fields) {
2041
- const filterType = getFilterType(field.type);
2042
- if (filterType) {
2043
- filterImports.add(filterType);
2044
- const fieldName = sanitizeFieldName(field.key);
2045
- filterFields.push(` ${fieldName}?: ${filterType};`);
2046
- }
2047
- }
2048
- filterImports.add("DateFilter");
2049
- filterFields.push(" createdAt?: DateFilter;");
2050
- filterFields.push(" updatedAt?: DateFilter;");
2051
- const imports = Array.from(filterImports).sort().join(", ");
2052
- const lines = [];
2053
- lines.push(`import type { ${imports} } from '@eide/foir-client';`);
2054
- lines.push("");
2055
- lines.push(`export interface ${whereName} {`);
2056
- lines.push(...filterFields);
2057
- lines.push(` AND?: ${whereName}[];`);
2058
- lines.push(` OR?: ${whereName}[];`);
2059
- lines.push(` NOT?: ${whereName};`);
2060
- lines.push("}");
2061
- return lines.join("\n") + "\n";
2062
- }
2063
- function generateModelSortField(model) {
2064
- if (isInlineOnlyModel3(model)) return "";
2065
- const typeName = toPascalCase(model.key);
2066
- const sortName = `${typeName}SortField`;
2067
- const fields = model.fields ?? [];
2068
- const sortableFields = [];
2069
- for (const field of fields) {
2070
- if (isSortable(field.type)) {
2071
- sortableFields.push(`'${field.key}'`);
2072
- }
2073
- }
2074
- sortableFields.push(`'createdAt'`);
2075
- sortableFields.push(`'updatedAt'`);
2076
- const unique = [...new Set(sortableFields)];
2077
- return `export type ${sortName} = ${unique.join(" | ")};
2078
- `;
2079
- }
2080
-
2081
- // src/codegen/generators/model-zod.ts
2082
- function isInlineOnlyModel4(model) {
2083
- return model.config.inline && !model.config.records;
2084
- }
2085
- function getZodExpression(field, allModels) {
2086
- const imports = /* @__PURE__ */ new Set();
2087
- let expr;
2088
- switch (field.type) {
2089
- case "text":
2090
- case "email":
2091
- case "phone":
2092
- case "url": {
2093
- imports.add("textField");
2094
- const opts = [];
2095
- if (field.options?.maxLength) opts.push(`maxLength: ${field.options.maxLength}`);
2096
- if (field.options?.minLength) opts.push(`minLength: ${field.options.minLength}`);
2097
- if (field.options?.pattern) opts.push(`pattern: '${field.options.pattern}'`);
2098
- expr = opts.length > 0 ? `textField({ ${opts.join(", ")} })` : `textField()`;
2099
- break;
2100
- }
2101
- case "number": {
2102
- imports.add("numberField");
2103
- const opts = [];
2104
- if (field.options?.min !== void 0) opts.push(`min: ${field.options.min}`);
2105
- if (field.options?.max !== void 0) opts.push(`max: ${field.options.max}`);
2106
- expr = opts.length > 0 ? `numberField({ ${opts.join(", ")} })` : `numberField()`;
2107
- break;
2108
- }
2109
- case "boolean":
2110
- imports.add("booleanField");
2111
- expr = "booleanField()";
2112
- break;
2113
- case "date":
2114
- imports.add("dateField");
2115
- expr = "dateField()";
2116
- break;
2117
- case "richtext":
2118
- imports.add("richtextValueSchema");
2119
- expr = "richtextValueSchema";
2120
- break;
2121
- case "image":
2122
- imports.add("imageValueSchema");
2123
- expr = "imageValueSchema";
2124
- break;
2125
- case "video":
2126
- imports.add("videoValueSchema");
2127
- expr = "videoValueSchema";
2128
- break;
2129
- case "file":
2130
- imports.add("fileValueSchema");
2131
- expr = "fileValueSchema";
2132
- break;
2133
- case "currency":
2134
- imports.add("currencyValueSchema");
2135
- expr = "currencyValueSchema";
2136
- break;
2137
- case "link":
2138
- imports.add("linkValueSchema");
2139
- expr = "linkValueSchema";
2140
- break;
2141
- case "reference":
2142
- imports.add("referenceValueSchema");
2143
- expr = "referenceValueSchema";
2144
- break;
2145
- case "select": {
2146
- if (field.options?.options) {
2147
- imports.add("selectField");
2148
- const options = field.options.options;
2149
- const values = options.map((o) => `'${o.value}'`).join(", ");
2150
- expr = `selectField([${values}])`;
2151
- } else {
2152
- expr = "z.string()";
2153
- }
2154
- break;
2155
- }
2156
- case "multiselect": {
2157
- if (field.options?.options) {
2158
- imports.add("multiselectField");
2159
- const options = field.options.options;
2160
- const values = options.map((o) => `'${o.value}'`).join(", ");
2161
- expr = `multiselectField([${values}])`;
2162
- } else {
2163
- expr = "z.array(z.string())";
2164
- }
2165
- break;
2166
- }
2167
- case "flexible":
2168
- imports.add("flexibleFieldItemSchema");
2169
- expr = "z.array(flexibleFieldItemSchema)";
2170
- break;
2171
- case "json":
2172
- imports.add("jsonValueSchema");
2173
- expr = "jsonValueSchema";
2174
- break;
2175
- case "list": {
2176
- if (field.options?.itemType) {
2177
- const itemType = field.options.itemType;
2178
- const refModel = allModels.find((m) => m.key === itemType);
2179
- if (refModel) {
2180
- const refSchemaName = `${toPascalCase(itemType)}${isInlineOnlyModel4(refModel) ? "" : "Data"}Schema`;
2181
- expr = `z.array(${refSchemaName})`;
2182
- } else {
2183
- imports.add("jsonValueSchema");
2184
- expr = "z.array(jsonValueSchema)";
2185
- }
2186
- } else {
2187
- imports.add("jsonValueSchema");
2188
- expr = "z.array(jsonValueSchema)";
2189
- }
2190
- break;
2191
- }
2192
- default: {
2193
- const refModel = allModels.find((m) => m.key === field.type);
2194
- if (refModel) {
2195
- const refSchemaName = `${toPascalCase(field.type)}${isInlineOnlyModel4(refModel) ? "" : "Data"}Schema`;
2196
- expr = refSchemaName;
2197
- } else {
2198
- imports.add("jsonValueSchema");
2199
- expr = "jsonValueSchema";
2200
- }
2201
- }
2202
- }
2203
- if (!field.required) {
2204
- expr = `${expr}.optional()`;
2205
- }
2206
- return { expression: expr, imports };
2207
- }
2208
- function generateModelZodSchema(model, allModels) {
2209
- const typeName = toPascalCase(model.key);
2210
- const schemaName = isInlineOnlyModel4(model) ? `${typeName}Schema` : `${typeName}DataSchema`;
2211
- const fields = model.fields ?? [];
2212
- if (fields.length === 0) {
2213
- return `import { z } from '@eide/foir-client/validation';
2214
-
2215
- export const ${schemaName} = z.object({});
2216
- `;
2217
- }
2218
- const allImports = /* @__PURE__ */ new Set();
2219
- const fieldLines = [];
2220
- const inlineSchemaImports = [];
2221
- for (const field of fields) {
2222
- const { expression, imports } = getZodExpression(field, allModels);
2223
- for (const imp of imports) allImports.add(imp);
2224
- const fieldName = sanitizeFieldName(field.key);
2225
- fieldLines.push(` ${fieldName}: ${expression},`);
2226
- if (!isPrimitiveFieldType(field.type) && field.type !== "list") {
2227
- const refModel = allModels.find((m) => m.key === field.type);
2228
- if (refModel && refModel.key !== model.key) {
2229
- const refSchemaName = `${toPascalCase(field.type)}${isInlineOnlyModel4(refModel) ? "" : "Data"}Schema`;
2230
- inlineSchemaImports.push(
2231
- `import { ${refSchemaName} } from './${field.type}.zod.js';`
2232
- );
2233
- }
2234
- }
2235
- if (field.type === "list" && field.options?.itemType) {
2236
- const itemType = field.options.itemType;
2237
- const refModel = allModels.find((m) => m.key === itemType);
2238
- if (refModel && refModel.key !== model.key) {
2239
- const refSchemaName = `${toPascalCase(itemType)}${isInlineOnlyModel4(refModel) ? "" : "Data"}Schema`;
2240
- inlineSchemaImports.push(
2241
- `import { ${refSchemaName} } from './${itemType}.zod.js';`
2242
- );
2243
- }
2244
- }
2245
- }
2246
- const lines = [];
2247
- lines.push(`/**`);
2248
- lines.push(` * Zod validation schema for ${model.name}`);
2249
- lines.push(` *`);
2250
- lines.push(` * @generated by foir \u2014 DO NOT EDIT MANUALLY`);
2251
- lines.push(` */`);
2252
- lines.push("");
2253
- lines.push(`import { z } from '@eide/foir-client/validation';`);
2254
- if (allImports.size > 0) {
2255
- const sorted = Array.from(allImports).sort();
2256
- lines.push(`import { ${sorted.join(", ")} } from '@eide/foir-client/validation';`);
2257
- }
2258
- const uniqueInlineImports = [...new Set(inlineSchemaImports)];
2259
- for (const imp of uniqueInlineImports) {
2260
- lines.push(imp);
2261
- }
2262
- lines.push("");
2263
- lines.push(`export const ${schemaName} = z.object({`);
2264
- lines.push(...fieldLines);
2265
- lines.push("});");
2266
- lines.push("");
2267
- lines.push(`export type ${isInlineOnlyModel4(model) ? typeName : typeName + "Data"}Validated = z.infer<typeof ${schemaName}>;`);
2268
- return lines.join("\n") + "\n";
2269
- }
2270
-
2271
- // src/codegen/generators/customer-profile-types.ts
2272
- function generateCustomerProfileTypes(schema) {
2273
- const fields = schema.fields ?? [];
2274
- const fieldTypeImports = getFieldTypeImportsForFields2(fields);
2275
- let code = `/**
2276
- * Customer Profile Types
2277
- *
2278
- * @generated by foir from customer profile schema (version ${schema.version}) \u2014 DO NOT EDIT MANUALLY
2279
- */
2280
-
2281
- `;
2282
- if (fieldTypeImports.size > 0) {
2283
- const types = Array.from(fieldTypeImports).sort().join(", ");
2284
- code += `import type { ${types} } from '../field-types.js';
2285
-
2286
- `;
2287
- }
2288
- code += `/**
2289
- * Customer Profile Data
2290
- * Typed field values for customer profiles
2291
- *
2292
- * @generated from customer profile schema (version ${schema.version})
2293
- */
2294
- `;
2295
- code += `export interface CustomerProfileData {
2296
- `;
2297
- for (const field of fields) {
2298
- const fieldName = sanitizeFieldName(field.key);
2299
- const fieldType = getFieldType(field, "output");
2300
- const optional = field.required ? "" : "?";
2301
- if (field.helpText) {
2302
- code += ` /** ${field.helpText} */
2303
- `;
2304
- }
2305
- code += ` ${fieldName}${optional}: ${fieldType};
2306
- `;
2307
- }
2308
- code += `}
2309
- `;
2310
- return code;
2311
- }
2312
- function getFieldTypeImportsForFields2(fields) {
2313
- const imports = /* @__PURE__ */ new Set();
2314
- for (const field of fields) {
2315
- const mapping = FIELD_TYPE_MAPPING[field.type];
2316
- if (mapping?.needsImport === "field-types") {
2317
- imports.add(mapping.outputType);
2318
- }
2319
- }
2320
- return imports;
2321
- }
2322
-
2323
- // src/codegen/write-files.ts
2324
- import { mkdir, writeFile } from "fs/promises";
2325
- import { dirname as dirname2 } from "path";
2326
- async function writeGeneratedFile(filePath, content, usePrettier = true) {
2327
- await mkdir(dirname2(filePath), { recursive: true });
2328
- let formattedContent = content;
2329
- const isSwift = filePath.endsWith(".swift");
2330
- if (usePrettier && !isSwift) {
2331
- try {
2332
- const prettier = await import("prettier");
2333
- const parser = filePath.endsWith(".graphql") ? "graphql" : "typescript";
2334
- formattedContent = await prettier.format(content, {
2335
- parser,
2336
- semi: true,
2337
- singleQuote: true,
2338
- trailingComma: "es5",
2339
- printWidth: 100
2340
- });
2341
- } catch {
2342
- }
2343
- }
2344
- await writeFile(filePath, formattedContent, "utf-8");
2345
- }
2346
- async function writeFiles(files, usePrettier = true) {
2347
- await Promise.all(
2348
- files.map(
2349
- (file) => writeGeneratedFile(file.path, file.content, usePrettier)
2350
- )
2351
- );
2352
- }
2353
-
2354
- // src/commands/pull.ts
2355
- function registerPullCommand(program2, globalOpts) {
2356
- program2.command("pull").description(
2357
- "Generate typed client, model types, and validation schemas from platform models"
2358
- ).option("--config <path>", "Path to config file").option("--only <models>", "Comma-separated model keys to generate").option("--no-prettier", "Skip Prettier formatting").option("--dry-run", "Show what would be generated without writing").option("--out <dir>", "Override output directory").action(
2359
- withErrorHandler(
2360
- globalOpts,
2361
- async (cmdOpts) => {
2362
- const opts = globalOpts();
2363
- const flags = {
2364
- config: cmdOpts.config,
2365
- only: cmdOpts.only,
2366
- noPrettier: cmdOpts.prettier === false,
2367
- dryRun: !!cmdOpts.dryRun,
2368
- out: cmdOpts.out
2369
- };
2370
- const config2 = await loadPullConfig(flags);
2371
- const client = await createClient(opts);
2372
- console.log(chalk4.dim("Fetching models\u2026"));
2373
- const [allModels, cpSchema] = await Promise.all([
2374
- fetchModelsForCodegen(client),
2375
- fetchCustomerProfileSchema(client)
2376
- ]);
2377
- if (allModels.length === 0 && !cpSchema) {
2378
- console.log(chalk4.yellow("No models found. Nothing to generate."));
2379
- return;
2380
- }
2381
- const models = filterModels(allModels, {
2382
- only: config2.only.length > 0 ? config2.only : void 0,
2383
- includeInline: config2.includeInline
2384
- });
2385
- console.log(
2386
- chalk4.dim(
2387
- `Found ${allModels.length} model(s), generating for ${models.length}.`
2388
- )
2389
- );
2390
- const cwd = process.cwd();
2391
- const outDir = resolve(cwd, config2.output.types);
2392
- const modelsDir = resolve(outDir, "models");
2393
- const files = [];
2394
- const hasCustomerProfile = !!(cpSchema && cpSchema.fields.length > 0);
2395
- const publicModels = models.filter(
2396
- (m) => m.config.publicApi && m.config.records
2397
- );
2398
- for (const model of models) {
2399
- files.push({
2400
- path: resolve(modelsDir, `${model.key}.ts`),
2401
- content: generateModelTypes(model, models)
2402
- });
2403
- files.push({
2404
- path: resolve(modelsDir, `${model.key}.zod.ts`),
2405
- content: generateModelZodSchema(model, models)
2406
- });
2407
- }
2408
- for (const model of publicModels) {
2409
- const whereCode = generateModelWhere(model);
2410
- const sortCode = generateModelSortField(model);
2411
- if (whereCode || sortCode) {
2412
- files.push({
2413
- path: resolve(modelsDir, `${model.key}.filters.ts`),
2414
- content: `/**
2415
- * Filter & sort types for ${model.name}
2416
- *
2417
- * @generated by foir \u2014 DO NOT EDIT MANUALLY
2418
- */
2419
-
2420
- ${whereCode}
2421
- ${sortCode}`
2422
- });
2423
- }
2424
- }
2425
- if (hasCustomerProfile) {
2426
- files.push({
2427
- path: resolve(modelsDir, "customer-profile.ts"),
2428
- content: generateCustomerProfileTypes(cpSchema)
2429
- });
2430
- }
2431
- files.push({
2432
- path: resolve(modelsDir, "index.ts"),
2433
- content: generateModelIndex(models)
2434
- });
2435
- files.push({
2436
- path: resolve(outDir, "client.ts"),
2437
- content: generateClientFactory(publicModels, hasCustomerProfile)
2438
- });
2439
- files.push({
2440
- path: resolve(outDir, "schema.json"),
2441
- content: generateSchemaManifest(models, cpSchema)
2442
- });
2443
- files.push({
2444
- path: resolve(outDir, "index.ts"),
2445
- content: generateRootIndex(hasCustomerProfile)
2446
- });
2447
- if (config2.dryRun) {
2448
- console.log(
2449
- chalk4.bold("\nDry run \u2014 files that would be generated:\n")
2450
- );
2451
- for (const file of files) {
2452
- const rel = file.path.replace(cwd + "/", "");
2453
- console.log(` ${chalk4.green("+")} ${rel}`);
2454
- }
2455
- console.log(`
2456
- ${chalk4.dim(`${files.length} file(s) total`)}`);
2457
- return;
2458
- }
2459
- await writeFiles(files, config2.prettier);
2460
- const cpSuffix = hasCustomerProfile ? " + customer profile" : "";
2461
- console.log(
2462
- chalk4.green(`
2463
- \u2713 Generated ${files.length} file(s)`) + chalk4.dim(
2464
- ` (${models.length} model(s), ${publicModels.length} typed accessor(s)${cpSuffix})`
2465
- )
2466
- );
2467
- console.log(chalk4.dim(` Output: ${outDir}`));
2468
- }
2469
- )
2470
- );
2471
- }
2472
- function generateRootIndex(includeCustomerProfile) {
2473
- let code = `/**
2474
- * Generated Foir client and model types.
2475
- *
2476
- * @generated by foir \u2014 DO NOT EDIT MANUALLY
2477
- */
2478
-
2479
- export { createClient } from './client.js';
2480
- export type { TypedClient } from './client.js';
2481
- export * from './models/index.js';
2482
- `;
2483
- if (includeCustomerProfile) {
2484
- code += `export type { CustomerProfileData } from './models/customer-profile.js';
2485
- `;
2486
- }
2487
- return code;
2488
- }
2489
-
2490
- // src/commands/create-extension.ts
2491
- import chalk5 from "chalk";
2492
- import inquirer2 from "inquirer";
2493
-
2494
- // src/scaffold/scaffold.ts
2495
- import * as fs4 from "fs";
2496
- import * as path2 from "path";
2497
-
2498
- // src/scaffold/package-manager.ts
2499
- import * as fs3 from "fs";
2500
- import * as path from "path";
2501
- function detectPackageManager() {
2502
- const lockFiles = [
2503
- { file: "pnpm-lock.yaml", manager: "pnpm" },
2504
- { file: "yarn.lock", manager: "yarn" },
2505
- { file: "package-lock.json", manager: "npm" }
2506
- ];
2507
- let dir = process.cwd();
2508
- while (true) {
2509
- for (const { file, manager } of lockFiles) {
2510
- if (fs3.existsSync(path.join(dir, file))) {
2511
- return getManagerInfo(manager);
2512
- }
2513
- }
2514
- const parentDir = path.dirname(dir);
2515
- if (parentDir === dir) {
2516
- break;
2517
- }
2518
- dir = parentDir;
2519
- }
2520
- return getManagerInfo("npm");
2521
- }
2522
- function getManagerInfo(manager) {
2523
- switch (manager) {
2524
- case "pnpm":
2525
- return {
2526
- name: "pnpm",
2527
- installCommand: "pnpm install",
2528
- execCommand: "pnpm dlx"
2529
- };
2530
- case "yarn":
2531
- return {
2532
- name: "yarn",
2533
- installCommand: "yarn install",
2534
- execCommand: "yarn dlx"
2535
- };
2536
- case "npm":
2537
- return {
2538
- name: "npm",
2539
- installCommand: "npm install",
2540
- execCommand: "npx"
2541
- };
2542
- }
2543
- }
2544
-
2545
- // src/scaffold/scaffold.ts
2546
- async function scaffold(projectName, extensionType, apiUrl) {
2547
- const projectDir = path2.resolve(process.cwd(), projectName);
2548
- if (fs4.existsSync(projectDir)) {
2549
- throw new Error(
2550
- `Directory "${projectName}" already exists. Choose a different name or remove the existing directory.`
2551
- );
2552
- }
2553
- const files = getFiles(projectName, extensionType, apiUrl);
2554
- for (const [filePath, content] of Object.entries(files)) {
2555
- const fullPath = path2.join(projectDir, filePath);
2556
- const dir = path2.dirname(fullPath);
2557
- if (!fs4.existsSync(dir)) {
2558
- fs4.mkdirSync(dir, { recursive: true });
2559
- }
2560
- fs4.writeFileSync(fullPath, content, "utf-8");
2561
- }
2562
- const pm = detectPackageManager();
2563
- console.log(" Files created:");
2564
- console.log();
2565
- for (const filePath of Object.keys(files)) {
2566
- console.log(` ${filePath}`);
2567
- }
2568
- console.log();
2569
- console.log(" Done! Next steps:");
2570
- console.log();
2571
- console.log(` cd ${projectName}`);
2572
- console.log(` ${pm.installCommand}`);
2573
- console.log(` cp ui/.env.example ui/.env.local`);
2574
- console.log(` cp api/.env.example api/.env.local`);
2575
- console.log(` ${pm.name === "npm" ? "npm run" : pm.name} dev`);
2576
- console.log();
2577
- }
2578
- function getFiles(projectName, extensionType, apiUrl) {
2579
- return {
2580
- // Root
2581
- "package.json": getRootPackageJson(projectName),
2582
- "extension.manifest.json": getManifest(projectName, extensionType),
2583
- // UI (Vite SPA)
2584
- "ui/package.json": getUiPackageJson(projectName),
2585
- "ui/tsconfig.json": getUiTsconfig(),
2586
- "ui/vite.config.ts": getUiViteConfig(),
2587
- "ui/index.html": getUiIndexHtml(projectName),
2588
- "ui/.env.example": getUiEnvExample(apiUrl),
2589
- "ui/.gitignore": getUiGitignore(),
2590
- "ui/src/main.tsx": getUiMain(),
2591
- "ui/src/App.tsx": getUiApp(extensionType),
2592
- "ui/src/index.css": getUiCss(),
2593
- "ui/src/vite-env.d.ts": '/// <reference types="vite/client" />\n',
2594
- // API (Hono)
2595
- "api/package.json": getApiPackageJson(projectName),
2596
- "api/tsconfig.json": getApiTsconfig(),
2597
- "api/.env.example": getApiEnvExample(apiUrl),
2598
- "api/.gitignore": "node_modules\ndist\n.env\n.env.local\n",
2599
- "api/src/index.ts": getApiIndex(),
2600
- "api/src/routes/webhooks.ts": getApiWebhooks(),
2601
- "api/src/routes/health.ts": getApiHealth(),
2602
- "api/src/lib/platform.ts": getApiPlatform()
2603
- };
2604
- }
2605
- function getRootPackageJson(name) {
2606
- const pkg = {
2607
- name,
2608
- version: "0.1.0",
2609
- private: true,
2610
- scripts: {
2611
- dev: 'concurrently "pnpm --filter ./ui dev" "pnpm --filter ./api dev"',
2612
- build: "pnpm --filter ./ui build && pnpm --filter ./api build"
2613
- },
2614
- devDependencies: {
2615
- concurrently: "^9.0.0"
2616
- }
2617
- };
2618
- return JSON.stringify(pkg, null, 2) + "\n";
2619
- }
2620
- function getManifest(name, extensionType) {
2621
- const manifest = {
2622
- name: name.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
2623
- version: "0.1.0",
2624
- type: extensionType,
2625
- description: `${extensionType} extension`,
2626
- entityTypes: []
2627
- };
2628
- return JSON.stringify(manifest, null, 2) + "\n";
2629
- }
2630
- function getUiPackageJson(name) {
2631
- const pkg = {
2632
- name: `${name}-ui`,
2633
- version: "0.1.0",
2634
- private: true,
2635
- type: "module",
2636
- scripts: {
2637
- dev: "vite",
2638
- build: "tsc && vite build",
2639
- preview: "vite preview"
2640
- },
2641
- dependencies: {
2642
- "@eide/extension-sdk": "^0.1.0",
2643
- react: "^19.0.0",
2644
- "react-dom": "^19.0.0"
2645
- },
2646
- devDependencies: {
2647
- "@tailwindcss/vite": "^4.0.6",
2648
- "@types/react": "^19.0.0",
2649
- "@types/react-dom": "^19.0.0",
2650
- "@vitejs/plugin-react": "^4.3.4",
2651
- tailwindcss: "^4.0.0",
2652
- typescript: "^5.0.0",
2653
- vite: "^6.0.7"
1059
+ export default defineConfig({
1060
+ key: '${name}',
1061
+ name: '${displayName}',
1062
+ configType: '${configType}',
1063
+
1064
+ // Uncomment and configure as needed:
1065
+ // models: [],
1066
+ // operations: [],
1067
+ // schedules: [],
1068
+ // hooks: [],
1069
+ // segments: [],
1070
+ // authProviders: [],
1071
+ // placements: [],
1072
+ });
1073
+ `;
1074
+ }
1075
+ function getUiPackageJson(name) {
1076
+ const pkg = {
1077
+ name: `${name}-ui`,
1078
+ version: "0.1.0",
1079
+ private: true,
1080
+ type: "module",
1081
+ scripts: {
1082
+ dev: "vite",
1083
+ build: "tsc && vite build",
1084
+ preview: "vite preview"
1085
+ },
1086
+ dependencies: {
1087
+ "@eide/foir-editor-sdk": "^0.1.0",
1088
+ react: "^19.0.0",
1089
+ "react-dom": "^19.0.0"
1090
+ },
1091
+ devDependencies: {
1092
+ "@tailwindcss/vite": "^4.0.6",
1093
+ "@types/react": "^19.0.0",
1094
+ "@types/react-dom": "^19.0.0",
1095
+ "@vitejs/plugin-react": "^4.3.4",
1096
+ tailwindcss: "^4.0.0",
1097
+ typescript: "^5.0.0",
1098
+ vite: "^6.0.7"
2654
1099
  }
2655
1100
  };
2656
1101
  return JSON.stringify(pkg, null, 2) + "\n";
@@ -2736,13 +1181,13 @@ dist
2736
1181
  function getUiMain() {
2737
1182
  return `import { StrictMode } from 'react';
2738
1183
  import { createRoot } from 'react-dom/client';
2739
- import { ExtensionProvider, useExtension } from '@eide/extension-sdk';
1184
+ import { EditorProvider, useEditor } from '@eide/foir-editor-sdk';
2740
1185
  import { useEffect } from 'react';
2741
1186
  import { App } from './App';
2742
1187
  import './index.css';
2743
1188
 
2744
1189
  function ThemeSync({ children }: { children: React.ReactNode }) {
2745
- const { theme } = useExtension();
1190
+ const { theme } = useEditor();
2746
1191
 
2747
1192
  useEffect(() => {
2748
1193
  const root = document.documentElement;
@@ -2755,17 +1200,17 @@ function ThemeSync({ children }: { children: React.ReactNode }) {
2755
1200
 
2756
1201
  createRoot(document.getElementById('root')!).render(
2757
1202
  <StrictMode>
2758
- <ExtensionProvider>
1203
+ <EditorProvider>
2759
1204
  <ThemeSync>
2760
1205
  <App />
2761
1206
  </ThemeSync>
2762
- </ExtensionProvider>
1207
+ </EditorProvider>
2763
1208
  </StrictMode>
2764
1209
  );
2765
1210
  `;
2766
1211
  }
2767
- function getUiApp(extensionType) {
2768
- switch (extensionType) {
1212
+ function getUiApp(configType) {
1213
+ switch (configType) {
2769
1214
  case "custom-editor":
2770
1215
  return getCustomEditorApp();
2771
1216
  case "widget":
@@ -2775,10 +1220,10 @@ function getUiApp(extensionType) {
2775
1220
  }
2776
1221
  }
2777
1222
  function getCustomEditorApp() {
2778
- return `import { useExtension, useAutoResize } from '@eide/extension-sdk';
1223
+ return `import { useEditor, useAutoResize } from '@eide/foir-editor-sdk';
2779
1224
 
2780
1225
  export function App() {
2781
- const { isReady, init, updateField, setDirty } = useExtension();
1226
+ const { isReady, init, updateField, setDirty } = useEditor();
2782
1227
  const containerRef = useAutoResize({ minHeight: 600 });
2783
1228
 
2784
1229
  if (!isReady) return null;
@@ -2786,7 +1231,7 @@ export function App() {
2786
1231
  return (
2787
1232
  <div ref={containerRef} className="p-6 space-y-4">
2788
1233
  <h1 className="text-lg font-semibold">
2789
- Editing: {init?.entityModelKey}
1234
+ Editing: {init?.modelKey}
2790
1235
  </h1>
2791
1236
  <p className="text-sm text-gray-500">
2792
1237
  Record: {init?.recordId}
@@ -2798,10 +1243,10 @@ export function App() {
2798
1243
  `;
2799
1244
  }
2800
1245
  function getWidgetApp() {
2801
- return `import { useExtension, useAutoResize } from '@eide/extension-sdk';
1246
+ return `import { useEditor, useAutoResize } from '@eide/foir-editor-sdk';
2802
1247
 
2803
1248
  export function App() {
2804
- const { isReady, init, client } = useExtension();
1249
+ const { isReady, init, client } = useEditor();
2805
1250
  const containerRef = useAutoResize({ minHeight: 300 });
2806
1251
 
2807
1252
  if (!isReady) return null;
@@ -2810,7 +1255,7 @@ export function App() {
2810
1255
  <div ref={containerRef} className="p-6 space-y-4">
2811
1256
  <h2 className="text-lg font-semibold">Dashboard Widget</h2>
2812
1257
  <p className="text-sm text-gray-500">
2813
- Connected to: {init?.entityModelKey}
1258
+ Connected to: {init?.modelKey}
2814
1259
  </p>
2815
1260
  {/* Add your widget content here */}
2816
1261
  </div>
@@ -2819,19 +1264,19 @@ export function App() {
2819
1264
  `;
2820
1265
  }
2821
1266
  function getWorkflowApp() {
2822
- return `import { useExtension, useAutoResize } from '@eide/extension-sdk';
1267
+ return `import { useEditor, useAutoResize } from '@eide/foir-editor-sdk';
2823
1268
 
2824
1269
  export function App() {
2825
- const { isReady, init, client, requestSave } = useExtension();
1270
+ const { isReady, init, client, requestSave } = useEditor();
2826
1271
  const containerRef = useAutoResize({ minHeight: 400 });
2827
1272
 
2828
1273
  if (!isReady) return null;
2829
1274
 
2830
1275
  return (
2831
1276
  <div ref={containerRef} className="p-6 space-y-4">
2832
- <h1 className="text-lg font-semibold">Workflow Extension</h1>
1277
+ <h1 className="text-lg font-semibold">Workflow Config</h1>
2833
1278
  <p className="text-sm text-gray-500">
2834
- Processing: {init?.entityModelKey} / {init?.recordId}
1279
+ Processing: {init?.modelKey} / {init?.recordId}
2835
1280
  </p>
2836
1281
  {/* Add your workflow steps here */}
2837
1282
  </div>
@@ -2871,7 +1316,7 @@ function getApiPackageJson(name) {
2871
1316
  start: "node dist/index.js"
2872
1317
  },
2873
1318
  dependencies: {
2874
- "@eide/extension-sdk": "^0.1.0",
1319
+ "@eide/foir-editor-sdk": "^0.1.0",
2875
1320
  hono: "^4.0.0",
2876
1321
  "@hono/node-server": "^1.0.0"
2877
1322
  },
@@ -2907,8 +1352,8 @@ function getApiEnvExample(apiUrl) {
2907
1352
  PLATFORM_BASE_URL=${baseUrl}
2908
1353
  PLATFORM_API_KEY=sk_your_api_key_here
2909
1354
 
2910
- # Extension
2911
- EXTENSION_KEY=${"{your-extension-key}"}
1355
+ # Config
1356
+ CONFIG_KEY=${"{your-config-key}"}
2912
1357
  WEBHOOK_SECRET=your_webhook_secret_here
2913
1358
 
2914
1359
  # Server
@@ -2929,25 +1374,25 @@ app.route('/', health);
2929
1374
 
2930
1375
  const port = parseInt(process.env.PORT || '3002', 10);
2931
1376
 
2932
- console.log(\`Extension API running on http://localhost:\${port}\`);
1377
+ console.log(\`Config API running on http://localhost:\${port}\`);
2933
1378
 
2934
1379
  serve({ fetch: app.fetch, port });
2935
1380
  `;
2936
1381
  }
2937
1382
  function getApiWebhooks() {
2938
1383
  return `import { Hono } from 'hono';
2939
- import { verifyWebhookSignature } from '@eide/extension-sdk/server';
1384
+ import { verifyWebhookSignature } from '@eide/foir-editor-sdk/server';
2940
1385
 
2941
1386
  const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET || '';
2942
1387
 
2943
1388
  export const webhooks = new Hono();
2944
1389
 
2945
1390
  /**
2946
- * Receive entity lifecycle events from the platform.
1391
+ * Receive record lifecycle events from the platform.
2947
1392
  */
2948
- webhooks.post('/entity-changed', async (c) => {
1393
+ webhooks.post('/record-changed', async (c) => {
2949
1394
  const body = await c.req.text();
2950
- const signature = c.req.header('x-eide-signature') ?? '';
1395
+ const signature = c.req.header('x-foir-signature') ?? '';
2951
1396
 
2952
1397
  const valid = await verifyWebhookSignature(body, signature, WEBHOOK_SECRET);
2953
1398
  if (!valid) {
@@ -2955,7 +1400,7 @@ webhooks.post('/entity-changed', async (c) => {
2955
1400
  }
2956
1401
 
2957
1402
  const payload = JSON.parse(body);
2958
- console.log('[Webhook] Entity changed:', payload.event, payload.entityId);
1403
+ console.log('[Webhook] Record changed:', payload.event, payload.recordId);
2959
1404
 
2960
1405
  // TODO: Handle the event (sync, transform, notify, etc.)
2961
1406
 
@@ -2973,36 +1418,14 @@ health.get('/health', (c) => {
2973
1418
  });
2974
1419
  `;
2975
1420
  }
2976
- function getApiPlatform() {
2977
- return `import { createExtensionClient } from '@eide/extension-sdk/server';
2978
-
2979
- /**
2980
- * Pre-configured platform client for this extension.
2981
- *
2982
- * Uses env vars for configuration:
2983
- * - PLATFORM_BASE_URL: Platform API base URL
2984
- * - PLATFORM_API_KEY: Project-scoped API key (sk_*)
2985
- * - EXTENSION_KEY: This extension's extension key
2986
- */
2987
- export const platform = createExtensionClient({
2988
- baseUrl: process.env.PLATFORM_BASE_URL || 'http://localhost:4000',
2989
- apiKey: process.env.PLATFORM_API_KEY || '',
2990
- extensionKey: process.env.EXTENSION_KEY || '',
2991
- });
2992
- `;
2993
- }
2994
1421
 
2995
- // src/commands/create-extension.ts
2996
- var EXTENSION_TYPES = [
2997
- "custom-editor",
2998
- "workflow",
2999
- "widget"
3000
- ];
3001
- function isValidExtensionType(value) {
3002
- return EXTENSION_TYPES.includes(value);
1422
+ // src/commands/create-config.ts
1423
+ var CONFIG_TYPES = ["custom-editor", "workflow", "widget"];
1424
+ function isValidConfigType(value) {
1425
+ return CONFIG_TYPES.includes(value);
3003
1426
  }
3004
- function registerCreateExtensionCommand(program2, globalOpts) {
3005
- program2.command("create-extension [name]").description("Scaffold a new Foir extension").option("--type <type>", "Extension type: custom-editor, workflow, widget").option(
1427
+ function registerCreateConfigCommand(program2, globalOpts) {
1428
+ program2.command("create-config [name]").description("Scaffold a new Foir config").option("--type <type>", "Config type: custom-editor, workflow, widget").option(
3006
1429
  "--api-url <url>",
3007
1430
  "Platform API URL",
3008
1431
  "http://localhost:4000/graphql"
@@ -3011,48 +1434,206 @@ function registerCreateExtensionCommand(program2, globalOpts) {
3011
1434
  globalOpts,
3012
1435
  async (name, cmdOpts) => {
3013
1436
  console.log();
3014
- console.log(chalk5.bold(" Create Foir Extension"));
3015
- console.log(chalk5.gray(" ---------------------"));
1437
+ console.log(chalk4.bold(" Create Foir Config"));
1438
+ console.log(chalk4.gray(" ------------------"));
3016
1439
  console.log();
3017
- let extensionName = name;
3018
- if (!extensionName) {
1440
+ let configName = name;
1441
+ if (!configName) {
3019
1442
  const { inputName } = await inquirer2.prompt([
3020
1443
  {
3021
1444
  type: "input",
3022
1445
  name: "inputName",
3023
- message: "Extension name:",
3024
- default: "my-extension"
1446
+ message: "Config name:",
1447
+ default: "my-config"
3025
1448
  }
3026
1449
  ]);
3027
- extensionName = inputName;
1450
+ configName = inputName;
3028
1451
  }
3029
- let extensionType;
3030
- if (cmdOpts?.type && isValidExtensionType(cmdOpts.type)) {
3031
- extensionType = cmdOpts.type;
1452
+ let configType;
1453
+ if (cmdOpts?.type && isValidConfigType(cmdOpts.type)) {
1454
+ configType = cmdOpts.type;
3032
1455
  } else {
3033
1456
  const { selectedType } = await inquirer2.prompt([
3034
1457
  {
3035
1458
  type: "list",
3036
1459
  name: "selectedType",
3037
- message: "Extension type:",
3038
- choices: EXTENSION_TYPES,
1460
+ message: "Config type:",
1461
+ choices: CONFIG_TYPES,
3039
1462
  default: "custom-editor"
3040
1463
  }
3041
1464
  ]);
3042
- extensionType = selectedType;
1465
+ configType = selectedType;
3043
1466
  }
3044
1467
  const apiUrl = cmdOpts?.apiUrl ?? "http://localhost:4000/graphql";
3045
1468
  console.log();
3046
1469
  console.log(
3047
- ` Scaffolding ${chalk5.cyan(`"${extensionName}"`)} (${extensionType})...`
1470
+ ` Scaffolding ${chalk4.cyan(`"${configName}"`)} (${configType})...`
3048
1471
  );
3049
1472
  console.log();
3050
- await scaffold(extensionName, extensionType, apiUrl);
1473
+ await scaffold(configName, configType, apiUrl);
3051
1474
  }
3052
1475
  )
3053
1476
  );
3054
1477
  }
3055
1478
 
1479
+ // src/graphql/generated.ts
1480
+ var GlobalSearchDocument = {
1481
+ kind: "Document",
1482
+ definitions: [
1483
+ {
1484
+ kind: "OperationDefinition",
1485
+ operation: "query",
1486
+ name: { kind: "Name", value: "GlobalSearch" },
1487
+ variableDefinitions: [
1488
+ {
1489
+ kind: "VariableDefinition",
1490
+ variable: {
1491
+ kind: "Variable",
1492
+ name: { kind: "Name", value: "query" }
1493
+ },
1494
+ type: {
1495
+ kind: "NonNullType",
1496
+ type: {
1497
+ kind: "NamedType",
1498
+ name: { kind: "Name", value: "String" }
1499
+ }
1500
+ }
1501
+ },
1502
+ {
1503
+ kind: "VariableDefinition",
1504
+ variable: {
1505
+ kind: "Variable",
1506
+ name: { kind: "Name", value: "limit" }
1507
+ },
1508
+ type: { kind: "NamedType", name: { kind: "Name", value: "Int" } }
1509
+ },
1510
+ {
1511
+ kind: "VariableDefinition",
1512
+ variable: {
1513
+ kind: "Variable",
1514
+ name: { kind: "Name", value: "modelKeys" }
1515
+ },
1516
+ type: {
1517
+ kind: "ListType",
1518
+ type: {
1519
+ kind: "NonNullType",
1520
+ type: {
1521
+ kind: "NamedType",
1522
+ name: { kind: "Name", value: "String" }
1523
+ }
1524
+ }
1525
+ }
1526
+ },
1527
+ {
1528
+ kind: "VariableDefinition",
1529
+ variable: {
1530
+ kind: "Variable",
1531
+ name: { kind: "Name", value: "includeMedia" }
1532
+ },
1533
+ type: { kind: "NamedType", name: { kind: "Name", value: "Boolean" } }
1534
+ }
1535
+ ],
1536
+ selectionSet: {
1537
+ kind: "SelectionSet",
1538
+ selections: [
1539
+ {
1540
+ kind: "Field",
1541
+ name: { kind: "Name", value: "globalSearch" },
1542
+ arguments: [
1543
+ {
1544
+ kind: "Argument",
1545
+ name: { kind: "Name", value: "query" },
1546
+ value: {
1547
+ kind: "Variable",
1548
+ name: { kind: "Name", value: "query" }
1549
+ }
1550
+ },
1551
+ {
1552
+ kind: "Argument",
1553
+ name: { kind: "Name", value: "limit" },
1554
+ value: {
1555
+ kind: "Variable",
1556
+ name: { kind: "Name", value: "limit" }
1557
+ }
1558
+ },
1559
+ {
1560
+ kind: "Argument",
1561
+ name: { kind: "Name", value: "modelKeys" },
1562
+ value: {
1563
+ kind: "Variable",
1564
+ name: { kind: "Name", value: "modelKeys" }
1565
+ }
1566
+ },
1567
+ {
1568
+ kind: "Argument",
1569
+ name: { kind: "Name", value: "includeMedia" },
1570
+ value: {
1571
+ kind: "Variable",
1572
+ name: { kind: "Name", value: "includeMedia" }
1573
+ }
1574
+ }
1575
+ ],
1576
+ selectionSet: {
1577
+ kind: "SelectionSet",
1578
+ selections: [
1579
+ {
1580
+ kind: "Field",
1581
+ name: { kind: "Name", value: "records" },
1582
+ selectionSet: {
1583
+ kind: "SelectionSet",
1584
+ selections: [
1585
+ { kind: "Field", name: { kind: "Name", value: "id" } },
1586
+ {
1587
+ kind: "Field",
1588
+ name: { kind: "Name", value: "modelKey" }
1589
+ },
1590
+ { kind: "Field", name: { kind: "Name", value: "title" } },
1591
+ {
1592
+ kind: "Field",
1593
+ name: { kind: "Name", value: "naturalKey" }
1594
+ },
1595
+ {
1596
+ kind: "Field",
1597
+ name: { kind: "Name", value: "subtitle" }
1598
+ },
1599
+ {
1600
+ kind: "Field",
1601
+ name: { kind: "Name", value: "updatedAt" }
1602
+ }
1603
+ ]
1604
+ }
1605
+ },
1606
+ {
1607
+ kind: "Field",
1608
+ name: { kind: "Name", value: "media" },
1609
+ selectionSet: {
1610
+ kind: "SelectionSet",
1611
+ selections: [
1612
+ { kind: "Field", name: { kind: "Name", value: "id" } },
1613
+ {
1614
+ kind: "Field",
1615
+ name: { kind: "Name", value: "fileName" }
1616
+ },
1617
+ {
1618
+ kind: "Field",
1619
+ name: { kind: "Name", value: "altText" }
1620
+ },
1621
+ {
1622
+ kind: "Field",
1623
+ name: { kind: "Name", value: "fileUrl" }
1624
+ }
1625
+ ]
1626
+ }
1627
+ }
1628
+ ]
1629
+ }
1630
+ }
1631
+ ]
1632
+ }
1633
+ }
1634
+ ]
1635
+ };
1636
+
3056
1637
  // src/commands/search.ts
3057
1638
  function registerSearchCommands(program2, globalOpts) {
3058
1639
  program2.command("search <query>").description("Search across all records and media").option(
@@ -3122,9 +1703,9 @@ Media (${data.globalSearch.media.length}):`);
3122
1703
 
3123
1704
  // src/commands/init.ts
3124
1705
  import { existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
3125
- import { writeFile as writeFile2 } from "fs/promises";
3126
- import { resolve as resolve3, join as join4 } from "path";
3127
- import chalk6 from "chalk";
1706
+ import { writeFile } from "fs/promises";
1707
+ import { resolve as resolve2, join as join4 } from "path";
1708
+ import chalk5 from "chalk";
3128
1709
  import inquirer3 from "inquirer";
3129
1710
  var FIELD_DEFAULTS = {
3130
1711
  text: "",
@@ -3214,18 +1795,18 @@ function registerInitCommands(program2, globalOpts) {
3214
1795
  async (key, opts) => {
3215
1796
  const globalFlags = globalOpts();
3216
1797
  const template = generateModelTemplate(key);
3217
- const outDir = resolve3(opts.output);
1798
+ const outDir = resolve2(opts.output);
3218
1799
  if (!existsSync3(outDir)) {
3219
1800
  mkdirSync2(outDir, { recursive: true });
3220
1801
  }
3221
1802
  const ext = opts.ts ? "ts" : "json";
3222
1803
  const filePath = join4(outDir, `${key}.${ext}`);
3223
1804
  const content = opts.ts ? formatAsTypeScript(template) : JSON.stringify(template, null, 2) + "\n";
3224
- await writeFile2(filePath, content, "utf-8");
1805
+ await writeFile(filePath, content, "utf-8");
3225
1806
  if (!(globalFlags.json || globalFlags.jsonl || globalFlags.quiet)) {
3226
1807
  success(`Created ${filePath}`);
3227
1808
  console.log(
3228
- chalk6.gray(
1809
+ chalk5.gray(
3229
1810
  `
3230
1811
  Edit the file, then run:
3231
1812
  foir models create -f ${filePath}`
@@ -3251,7 +1832,7 @@ Edit the file, then run:
3251
1832
  const models = result.models.items;
3252
1833
  if (models.length === 0) {
3253
1834
  console.log(
3254
- chalk6.yellow(
1835
+ chalk5.yellow(
3255
1836
  "No models found. Create models first with `foir models create`."
3256
1837
  )
3257
1838
  );
@@ -3264,7 +1845,7 @@ Edit the file, then run:
3264
1845
  const found = models.find((m) => m.key === key);
3265
1846
  if (!found) {
3266
1847
  console.error(
3267
- chalk6.red(`Model "${key}" not found.`),
1848
+ chalk5.red(`Model "${key}" not found.`),
3268
1849
  "Available:",
3269
1850
  models.map((m) => m.key).join(", ")
3270
1851
  );
@@ -3292,7 +1873,7 @@ Edit the file, then run:
3292
1873
  console.log("No models selected.");
3293
1874
  return;
3294
1875
  }
3295
- const outDir = resolve3(opts.output);
1876
+ const outDir = resolve2(opts.output);
3296
1877
  if (!existsSync3(outDir)) {
3297
1878
  mkdirSync2(outDir, { recursive: true });
3298
1879
  }
@@ -3302,7 +1883,7 @@ Edit the file, then run:
3302
1883
  const ext = opts.ts ? "ts" : "json";
3303
1884
  const filePath = join4(outDir, `${model.key}.${ext}`);
3304
1885
  const content = opts.ts ? formatAsTypeScript(seed) : JSON.stringify(seed, null, 2) + "\n";
3305
- await writeFile2(filePath, content, "utf-8");
1886
+ await writeFile(filePath, content, "utf-8");
3306
1887
  createdFiles.push(filePath);
3307
1888
  if (!(globalFlags.json || globalFlags.jsonl || globalFlags.quiet)) {
3308
1889
  success(`Created ${filePath}`);
@@ -3310,7 +1891,7 @@ Edit the file, then run:
3310
1891
  }
3311
1892
  if (!(globalFlags.json || globalFlags.jsonl || globalFlags.quiet)) {
3312
1893
  console.log(
3313
- chalk6.gray(
1894
+ chalk5.gray(
3314
1895
  `
3315
1896
  Edit the files, then run:
3316
1897
  foir records create --dir ${outDir} --publish`
@@ -3324,18 +1905,17 @@ Edit the files, then run:
3324
1905
  );
3325
1906
  }
3326
1907
 
3327
- // src/commands/profiles.ts
3328
- import chalk7 from "chalk";
3329
-
3330
- // src/lib/input.ts
3331
- import inquirer4 from "inquirer";
1908
+ // src/commands/push.ts
1909
+ import chalk6 from "chalk";
1910
+ import { existsSync as existsSync4 } from "fs";
1911
+ import { resolve as resolve4 } from "path";
3332
1912
 
3333
1913
  // src/lib/config-loader.ts
3334
1914
  import { readFile } from "fs/promises";
3335
1915
  import { pathToFileURL } from "url";
3336
- import { resolve as resolve4 } from "path";
1916
+ import { resolve as resolve3 } from "path";
3337
1917
  async function loadConfig(filePath) {
3338
- const absPath = resolve4(filePath);
1918
+ const absPath = resolve3(filePath);
3339
1919
  if (filePath.endsWith(".ts")) {
3340
1920
  const configModule = await import(pathToFileURL(absPath).href);
3341
1921
  return configModule.default;
@@ -3353,7 +1933,194 @@ async function loadConfig(filePath) {
3353
1933
  );
3354
1934
  }
3355
1935
 
1936
+ // src/commands/push.ts
1937
+ var CONFIG_FILE_NAMES = [
1938
+ "foir.config.ts",
1939
+ "foir.config.js",
1940
+ "foir.config.mjs",
1941
+ "foir.config.json"
1942
+ ];
1943
+ var APPLY_CONFIG_MUTATION = (
1944
+ /* GraphQL */
1945
+ `
1946
+ mutation ApplyConfig($input: ApplyConfigInput!) {
1947
+ applyConfig(input: $input) {
1948
+ configId
1949
+ configKey
1950
+ credentials {
1951
+ platformApiKey
1952
+ platformEditorKey
1953
+ webhookSecret
1954
+ }
1955
+ modelsCreated
1956
+ modelsUpdated
1957
+ operationsCreated
1958
+ operationsUpdated
1959
+ segmentsCreated
1960
+ segmentsUpdated
1961
+ schedulesCreated
1962
+ schedulesUpdated
1963
+ hooksCreated
1964
+ hooksUpdated
1965
+ isUpdate
1966
+ }
1967
+ }
1968
+ `
1969
+ );
1970
+ function discoverConfigFile() {
1971
+ for (const name of CONFIG_FILE_NAMES) {
1972
+ const path3 = resolve4(process.cwd(), name);
1973
+ if (existsSync4(path3)) return path3;
1974
+ }
1975
+ return null;
1976
+ }
1977
+ function registerPushCommand(program2, globalOpts) {
1978
+ program2.command("push").description("Push foir.config.ts to the platform").option("--config <path>", "Path to config file (default: auto-discover)").option("--force", "Force reinstall (delete and recreate)", false).action(
1979
+ withErrorHandler(
1980
+ globalOpts,
1981
+ async (opts) => {
1982
+ const configPath = opts.config ? resolve4(opts.config) : discoverConfigFile();
1983
+ if (!configPath) {
1984
+ throw new Error(
1985
+ "No config file found. Create a foir.config.ts or use --config <path>."
1986
+ );
1987
+ }
1988
+ if (!existsSync4(configPath)) {
1989
+ throw new Error(`Config file not found: ${configPath}`);
1990
+ }
1991
+ console.log(chalk6.dim(`Loading ${configPath}...`));
1992
+ const config2 = await loadConfig(configPath);
1993
+ if (!config2?.key || !config2?.name) {
1994
+ throw new Error(
1995
+ 'Config must have at least "key" and "name" fields.'
1996
+ );
1997
+ }
1998
+ if (opts.force) {
1999
+ config2.force = true;
2000
+ }
2001
+ const client = await createClient(globalOpts());
2002
+ console.log(
2003
+ chalk6.dim(`Pushing config "${config2.key}" to platform...`)
2004
+ );
2005
+ const data = await client.request(
2006
+ APPLY_CONFIG_MUTATION,
2007
+ { input: config2 }
2008
+ );
2009
+ const result = data.applyConfig;
2010
+ console.log();
2011
+ if (result.isUpdate) {
2012
+ console.log(chalk6.green("Config updated successfully."));
2013
+ } else {
2014
+ console.log(chalk6.green("Config applied successfully."));
2015
+ }
2016
+ console.log();
2017
+ console.log(` Config ID: ${chalk6.cyan(result.configId)}`);
2018
+ console.log(` Config Key: ${chalk6.cyan(result.configKey)}`);
2019
+ console.log();
2020
+ const stats = [
2021
+ ["Models", result.modelsCreated, result.modelsUpdated],
2022
+ ["Operations", result.operationsCreated, result.operationsUpdated],
2023
+ ["Segments", result.segmentsCreated, result.segmentsUpdated],
2024
+ ["Schedules", result.schedulesCreated, result.schedulesUpdated],
2025
+ ["Hooks", result.hooksCreated, result.hooksUpdated]
2026
+ ].filter(([, c, u]) => c > 0 || u > 0);
2027
+ if (stats.length > 0) {
2028
+ for (const [label, created, updated] of stats) {
2029
+ const parts = [];
2030
+ if (created > 0)
2031
+ parts.push(chalk6.green(`${created} created`));
2032
+ if (updated > 0)
2033
+ parts.push(chalk6.yellow(`${updated} updated`));
2034
+ console.log(` ${label}: ${parts.join(", ")}`);
2035
+ }
2036
+ console.log();
2037
+ }
2038
+ if (result.credentials) {
2039
+ console.log(chalk6.bold.yellow("Credentials (save these now):"));
2040
+ console.log();
2041
+ console.log(
2042
+ ` PLATFORM_API_KEY: ${chalk6.cyan(result.credentials.platformApiKey)}`
2043
+ );
2044
+ console.log(
2045
+ ` PLATFORM_EDITOR_KEY: ${chalk6.cyan(result.credentials.platformEditorKey)}`
2046
+ );
2047
+ console.log(
2048
+ ` WEBHOOK_SECRET: ${chalk6.cyan(result.credentials.webhookSecret)}`
2049
+ );
2050
+ console.log();
2051
+ console.log(
2052
+ chalk6.dim(
2053
+ "These credentials are only shown once. Store them securely."
2054
+ )
2055
+ );
2056
+ }
2057
+ }
2058
+ )
2059
+ );
2060
+ }
2061
+
2062
+ // src/commands/remove.ts
2063
+ import chalk7 from "chalk";
2064
+ import inquirer4 from "inquirer";
2065
+ var GET_CONFIG_QUERY = (
2066
+ /* GraphQL */
2067
+ `
2068
+ query GetConfigByKey($key: String!) {
2069
+ configByKey(key: $key) {
2070
+ id
2071
+ key
2072
+ name
2073
+ configType
2074
+ }
2075
+ }
2076
+ `
2077
+ );
2078
+ var UNREGISTER_MUTATION = (
2079
+ /* GraphQL */
2080
+ `
2081
+ mutation UnregisterConfig($id: ID!) {
2082
+ unregisterConfig(id: $id)
2083
+ }
2084
+ `
2085
+ );
2086
+ function registerRemoveCommand(program2, globalOpts) {
2087
+ program2.command("remove <key>").description("Remove a config and all its provisioned resources").option("--force", "Skip confirmation prompt", false).action(
2088
+ withErrorHandler(
2089
+ globalOpts,
2090
+ async (key, opts) => {
2091
+ const client = await createClient(globalOpts());
2092
+ const { configByKey: config2 } = await client.request(GET_CONFIG_QUERY, { key });
2093
+ if (!config2) {
2094
+ throw new Error(`Config not found: ${key}`);
2095
+ }
2096
+ if (!opts.force) {
2097
+ const { confirmed } = await inquirer4.prompt([
2098
+ {
2099
+ type: "confirm",
2100
+ name: "confirmed",
2101
+ message: `Remove config "${config2.name}" (${config2.key})? This will delete all its models, operations, hooks, and schedules.`,
2102
+ default: false
2103
+ }
2104
+ ]);
2105
+ if (!confirmed) {
2106
+ console.log(chalk7.dim("Cancelled."));
2107
+ return;
2108
+ }
2109
+ }
2110
+ await client.request(UNREGISTER_MUTATION, { id: config2.id });
2111
+ console.log(
2112
+ chalk7.green(`Removed config "${config2.name}" (${config2.key}).`)
2113
+ );
2114
+ }
2115
+ )
2116
+ );
2117
+ }
2118
+
2119
+ // src/commands/profiles.ts
2120
+ import chalk8 from "chalk";
2121
+
3356
2122
  // src/lib/input.ts
2123
+ import inquirer5 from "inquirer";
3357
2124
  async function parseInputData(opts) {
3358
2125
  if (opts.data) {
3359
2126
  return JSON.parse(opts.data);
@@ -3382,7 +2149,7 @@ function isUUID(value) {
3382
2149
  }
3383
2150
  async function confirmAction(message, opts) {
3384
2151
  if (opts?.confirm) return true;
3385
- const { confirmed } = await inquirer4.prompt([
2152
+ const { confirmed } = await inquirer5.prompt([
3386
2153
  {
3387
2154
  type: "confirm",
3388
2155
  name: "confirmed",
@@ -3553,7 +2320,7 @@ function registerProfilesCommand(program2, globalOpts) {
3553
2320
  if (opts.json || opts.jsonl) {
3554
2321
  formatOutput({ deleted: name }, opts);
3555
2322
  } else {
3556
- console.log(chalk7.green(`Deleted profile "${name}".`));
2323
+ console.log(chalk8.green(`Deleted profile "${name}".`));
3557
2324
  }
3558
2325
  }
3559
2326
  )
@@ -3562,9 +2329,9 @@ function registerProfilesCommand(program2, globalOpts) {
3562
2329
 
3563
2330
  // src/commands/register-commands.ts
3564
2331
  import { readFileSync, readdirSync } from "fs";
3565
- import { resolve as resolve5, dirname as dirname5 } from "path";
2332
+ import { resolve as resolve5, dirname as dirname4 } from "path";
3566
2333
  import { fileURLToPath } from "url";
3567
- import chalk8 from "chalk";
2334
+ import chalk9 from "chalk";
3568
2335
 
3569
2336
  // ../command-registry/src/command-map.ts
3570
2337
  var COMMANDS = [
@@ -4190,65 +2957,65 @@ var COMMANDS = [
4190
2957
  // EXTENSIONS
4191
2958
  // =========================================================================
4192
2959
  {
4193
- group: "extensions",
2960
+ group: "configs",
4194
2961
  name: "list",
4195
- description: "List extensions",
4196
- operation: "extensions",
2962
+ description: "List configs",
2963
+ operation: "configs",
4197
2964
  operationType: "query",
4198
2965
  columns: [
4199
2966
  { key: "id", header: "ID", width: 28 },
4200
2967
  { key: "key", header: "Key", width: 20 },
4201
2968
  { key: "name", header: "Name", width: 20 },
4202
- { key: "extensionType", header: "Type", width: 12 },
2969
+ { key: "configType", header: "Type", width: 12 },
4203
2970
  { key: "enabled", header: "Enabled", width: 8, format: "boolean" },
4204
2971
  { key: "syncStatus", header: "Sync", width: 10 }
4205
2972
  ]
4206
2973
  },
4207
2974
  {
4208
- group: "extensions",
2975
+ group: "configs",
4209
2976
  name: "get",
4210
- description: "Get an extension",
4211
- operation: "extension",
2977
+ description: "Get a config",
2978
+ operation: "config",
4212
2979
  operationType: "query",
4213
2980
  positionalArgs: [{ name: "id", graphqlArg: "id" }],
4214
- alternateGet: { operation: "extensionByKey", argName: "key" }
2981
+ alternateGet: { operation: "configByKey", argName: "key" }
4215
2982
  },
4216
2983
  {
4217
- group: "extensions",
2984
+ group: "configs",
4218
2985
  name: "register",
4219
- description: "Register an extension",
4220
- operation: "registerExtension",
2986
+ description: "Register a config",
2987
+ operation: "registerConfig",
4221
2988
  operationType: "mutation",
4222
2989
  acceptsInput: true,
4223
- successMessage: "Registered extension"
2990
+ successMessage: "Registered config"
4224
2991
  },
4225
2992
  {
4226
- group: "extensions",
4227
- name: "install",
4228
- description: "Install an extension",
4229
- operation: "installExtension",
2993
+ group: "configs",
2994
+ name: "apply",
2995
+ description: "Apply a config",
2996
+ operation: "applyConfig",
4230
2997
  operationType: "mutation",
4231
2998
  acceptsInput: true,
4232
- successMessage: "Installed extension"
2999
+ successMessage: "Applied config"
4233
3000
  },
4234
3001
  {
4235
- group: "extensions",
3002
+ group: "configs",
4236
3003
  name: "uninstall",
4237
- description: "Unregister an extension",
4238
- operation: "unregisterExtension",
3004
+ description: "Unregister a config",
3005
+ operation: "unregisterConfig",
4239
3006
  operationType: "mutation",
4240
3007
  positionalArgs: [{ name: "id", graphqlArg: "id" }],
4241
3008
  requiresConfirmation: true,
4242
3009
  scalarResult: true,
4243
- successMessage: "Unregistered extension"
3010
+ successMessage: "Unregistered config"
4244
3011
  },
4245
3012
  {
4246
- group: "extensions",
3013
+ group: "configs",
4247
3014
  name: "sync",
4248
- description: "Trigger extension sync",
4249
- operation: "triggerExtensionSync",
3015
+ description: "Trigger config sync",
3016
+ operation: "triggerConfigSync",
4250
3017
  operationType: "mutation",
4251
- positionalArgs: [{ name: "extensionId", graphqlArg: "extensionId" }],
3018
+ positionalArgs: [{ name: "configId", graphqlArg: "configId" }],
4252
3019
  successMessage: "Triggered sync"
4253
3020
  },
4254
3021
  // =========================================================================
@@ -5163,7 +3930,7 @@ function createSchemaEngine(sdl) {
5163
3930
 
5164
3931
  // src/commands/register-commands.ts
5165
3932
  var __filename = fileURLToPath(import.meta.url);
5166
- var __dirname = dirname5(__filename);
3933
+ var __dirname = dirname4(__filename);
5167
3934
  function loadSchemaSDL() {
5168
3935
  const bundledPath = resolve5(__dirname, "schema.graphql");
5169
3936
  try {
@@ -5329,7 +4096,7 @@ function registerDynamicCommands(program2, globalOpts) {
5329
4096
  const files = readdirSync(dirPath).filter((f) => /\.(json|ts|js|mjs)$/.test(f)).sort();
5330
4097
  if (files.length === 0) {
5331
4098
  console.error(
5332
- chalk8.yellow(`\u26A0 No .json/.ts/.js files found in ${dirPath}`)
4099
+ chalk9.yellow(`\u26A0 No .json/.ts/.js files found in ${dirPath}`)
5333
4100
  );
5334
4101
  return;
5335
4102
  }
@@ -5374,19 +4141,19 @@ function registerDynamicCommands(program2, globalOpts) {
5374
4141
  } catch (updateErr) {
5375
4142
  failed++;
5376
4143
  const msg2 = updateErr instanceof Error ? updateErr.message : String(updateErr);
5377
- console.error(chalk8.red(`\u2717 ${label}:`), msg2);
4144
+ console.error(chalk9.red(`\u2717 ${label}:`), msg2);
5378
4145
  continue;
5379
4146
  }
5380
4147
  }
5381
4148
  failed++;
5382
4149
  const msg = err instanceof Error ? err.message : String(err);
5383
- console.error(chalk8.red(`\u2717 ${label}:`), msg);
4150
+ console.error(chalk9.red(`\u2717 ${label}:`), msg);
5384
4151
  }
5385
4152
  }
5386
4153
  if (!(opts.json || opts.jsonl || opts.quiet)) {
5387
4154
  console.log("");
5388
4155
  console.log(
5389
- chalk8.bold(
4156
+ chalk9.bold(
5390
4157
  `Done: ${created} created${updated ? `, ${updated} updated` : ""}${failed ? `, ${failed} failed` : ""}`
5391
4158
  )
5392
4159
  );
@@ -5562,7 +4329,7 @@ function registerDynamicCommands(program2, globalOpts) {
5562
4329
  }
5563
4330
  } else if (!(opts.json || opts.jsonl || opts.quiet)) {
5564
4331
  console.error(
5565
- chalk8.yellow(
4332
+ chalk9.yellow(
5566
4333
  "\u26A0 Could not auto-publish: no version found in response"
5567
4334
  )
5568
4335
  );
@@ -5585,7 +4352,7 @@ function autoColumns(items) {
5585
4352
 
5586
4353
  // src/cli.ts
5587
4354
  var __filename2 = fileURLToPath2(import.meta.url);
5588
- var __dirname2 = dirname6(__filename2);
4355
+ var __dirname2 = dirname5(__filename2);
5589
4356
  config({ path: resolve6(__dirname2, "../.env.local") });
5590
4357
  var require2 = createRequire(import.meta.url);
5591
4358
  var { version } = require2("../package.json");
@@ -5608,8 +4375,9 @@ registerWhoamiCommand(program, getGlobalOpts);
5608
4375
  registerProfilesCommand(program, getGlobalOpts);
5609
4376
  registerMediaCommands(program, getGlobalOpts);
5610
4377
  registerSearchCommands(program, getGlobalOpts);
5611
- registerPullCommand(program, getGlobalOpts);
5612
- registerCreateExtensionCommand(program, getGlobalOpts);
4378
+ registerPushCommand(program, getGlobalOpts);
4379
+ registerRemoveCommand(program, getGlobalOpts);
4380
+ registerCreateConfigCommand(program, getGlobalOpts);
5613
4381
  registerInitCommands(program, getGlobalOpts);
5614
4382
  registerDynamicCommands(program, getGlobalOpts);
5615
4383
  program.parse();