@eide/foir-cli 0.1.45 → 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 +315 -138
- package/dist/{generated-xEC9Cfzw.d.ts → lib/config-helpers.d.ts} +95 -77
- package/dist/lib/config-helpers.js +41 -0
- package/dist/schema.graphql +93 -63
- package/package.json +4 -12
- package/dist/lib/extension-helpers.d.ts +0 -100
- package/dist/lib/extension-helpers.js +0 -23
- package/dist/lib/hook-helpers.d.ts +0 -108
- package/dist/lib/hook-helpers.js +0 -15
- package/dist/lib/seed-helpers.d.ts +0 -124
- package/dist/lib/seed-helpers.js +0 -23
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { config } from "dotenv";
|
|
5
|
-
import { resolve as
|
|
5
|
+
import { resolve as resolve6, dirname as dirname5 } from "path";
|
|
6
6
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7
7
|
import { createRequire } from "module";
|
|
8
8
|
import { Command } from "commander";
|
|
@@ -343,13 +343,13 @@ function withErrorHandler(optsFn, fn) {
|
|
|
343
343
|
// src/commands/login.ts
|
|
344
344
|
async function findAvailablePort(start, end) {
|
|
345
345
|
for (let port = start; port <= end; port++) {
|
|
346
|
-
const available = await new Promise((
|
|
346
|
+
const available = await new Promise((resolve7) => {
|
|
347
347
|
const server = http.createServer();
|
|
348
348
|
server.listen(port, () => {
|
|
349
349
|
server.close();
|
|
350
|
-
|
|
350
|
+
resolve7(true);
|
|
351
351
|
});
|
|
352
|
-
server.on("error", () =>
|
|
352
|
+
server.on("error", () => resolve7(false));
|
|
353
353
|
});
|
|
354
354
|
if (available) return port;
|
|
355
355
|
}
|
|
@@ -387,7 +387,7 @@ async function loginAction(globalOpts) {
|
|
|
387
387
|
const state = crypto.randomBytes(16).toString("hex");
|
|
388
388
|
const port = await findAvailablePort(9876, 9900);
|
|
389
389
|
const redirectUri = `http://localhost:${port}/callback`;
|
|
390
|
-
const authCode = await new Promise((
|
|
390
|
+
const authCode = await new Promise((resolve7, reject) => {
|
|
391
391
|
const server = http.createServer((req, res) => {
|
|
392
392
|
const url = new URL(req.url, `http://localhost:${port}`);
|
|
393
393
|
if (url.pathname === "/callback") {
|
|
@@ -420,7 +420,7 @@ async function loginAction(globalOpts) {
|
|
|
420
420
|
`<html><head><meta http-equiv="refresh" content="2;url=${mainUrl}"></head><body style="font-family:system-ui;text-align:center;padding:50px"><h1>Authentication successful!</h1><p>You can close this window.</p></body></html>`
|
|
421
421
|
);
|
|
422
422
|
server.close();
|
|
423
|
-
|
|
423
|
+
resolve7(code);
|
|
424
424
|
}
|
|
425
425
|
});
|
|
426
426
|
server.listen(port);
|
|
@@ -497,7 +497,7 @@ var CLI_API_KEY_SCOPES = [
|
|
|
497
497
|
"records:publish",
|
|
498
498
|
"files:read",
|
|
499
499
|
"files:write",
|
|
500
|
-
"
|
|
500
|
+
"configs:read",
|
|
501
501
|
"operations:read",
|
|
502
502
|
"operations:execute"
|
|
503
503
|
];
|
|
@@ -921,7 +921,7 @@ function registerMediaCommands(program2, globalOpts) {
|
|
|
921
921
|
);
|
|
922
922
|
}
|
|
923
923
|
|
|
924
|
-
// src/commands/create-
|
|
924
|
+
// src/commands/create-config.ts
|
|
925
925
|
import chalk4 from "chalk";
|
|
926
926
|
import inquirer2 from "inquirer";
|
|
927
927
|
|
|
@@ -977,14 +977,14 @@ function getManagerInfo(manager) {
|
|
|
977
977
|
}
|
|
978
978
|
|
|
979
979
|
// src/scaffold/scaffold.ts
|
|
980
|
-
async function scaffold(projectName,
|
|
980
|
+
async function scaffold(projectName, configType, apiUrl) {
|
|
981
981
|
const projectDir = path2.resolve(process.cwd(), projectName);
|
|
982
982
|
if (fs4.existsSync(projectDir)) {
|
|
983
983
|
throw new Error(
|
|
984
984
|
`Directory "${projectName}" already exists. Choose a different name or remove the existing directory.`
|
|
985
985
|
);
|
|
986
986
|
}
|
|
987
|
-
const files = getFiles(projectName,
|
|
987
|
+
const files = getFiles(projectName, configType, apiUrl);
|
|
988
988
|
for (const [filePath, content] of Object.entries(files)) {
|
|
989
989
|
const fullPath = path2.join(projectDir, filePath);
|
|
990
990
|
const dir = path2.dirname(fullPath);
|
|
@@ -1009,11 +1009,12 @@ async function scaffold(projectName, extensionType, apiUrl) {
|
|
|
1009
1009
|
console.log(` ${pm.name === "npm" ? "npm run" : pm.name} dev`);
|
|
1010
1010
|
console.log();
|
|
1011
1011
|
}
|
|
1012
|
-
function getFiles(projectName,
|
|
1012
|
+
function getFiles(projectName, configType, apiUrl) {
|
|
1013
1013
|
return {
|
|
1014
1014
|
// Root
|
|
1015
1015
|
"package.json": getRootPackageJson(projectName),
|
|
1016
|
-
|
|
1016
|
+
// Config manifest
|
|
1017
|
+
"foir.config.ts": getFoirConfig(projectName, configType),
|
|
1017
1018
|
// UI (Vite SPA)
|
|
1018
1019
|
"ui/package.json": getUiPackageJson(projectName),
|
|
1019
1020
|
"ui/tsconfig.json": getUiTsconfig(),
|
|
@@ -1022,7 +1023,7 @@ function getFiles(projectName, extensionType, apiUrl) {
|
|
|
1022
1023
|
"ui/.env.example": getUiEnvExample(apiUrl),
|
|
1023
1024
|
"ui/.gitignore": getUiGitignore(),
|
|
1024
1025
|
"ui/src/main.tsx": getUiMain(),
|
|
1025
|
-
"ui/src/App.tsx": getUiApp(
|
|
1026
|
+
"ui/src/App.tsx": getUiApp(configType),
|
|
1026
1027
|
"ui/src/index.css": getUiCss(),
|
|
1027
1028
|
"ui/src/vite-env.d.ts": '/// <reference types="vite/client" />\n',
|
|
1028
1029
|
// API (Hono)
|
|
@@ -1032,8 +1033,7 @@ function getFiles(projectName, extensionType, apiUrl) {
|
|
|
1032
1033
|
"api/.gitignore": "node_modules\ndist\n.env\n.env.local\n",
|
|
1033
1034
|
"api/src/index.ts": getApiIndex(),
|
|
1034
1035
|
"api/src/routes/webhooks.ts": getApiWebhooks(),
|
|
1035
|
-
"api/src/routes/health.ts": getApiHealth()
|
|
1036
|
-
"api/src/lib/platform.ts": getApiPlatform()
|
|
1036
|
+
"api/src/routes/health.ts": getApiHealth()
|
|
1037
1037
|
};
|
|
1038
1038
|
}
|
|
1039
1039
|
function getRootPackageJson(name) {
|
|
@@ -1046,20 +1046,31 @@ function getRootPackageJson(name) {
|
|
|
1046
1046
|
build: "pnpm --filter ./ui build && pnpm --filter ./api build"
|
|
1047
1047
|
},
|
|
1048
1048
|
devDependencies: {
|
|
1049
|
-
concurrently: "^9.0.0"
|
|
1049
|
+
concurrently: "^9.0.0",
|
|
1050
|
+
"@eide/foir-cli": "^0.1.0"
|
|
1050
1051
|
}
|
|
1051
1052
|
};
|
|
1052
1053
|
return JSON.stringify(pkg, null, 2) + "\n";
|
|
1053
1054
|
}
|
|
1054
|
-
function
|
|
1055
|
-
const
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
}
|
|
1062
|
-
|
|
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';
|
|
1058
|
+
|
|
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
|
+
`;
|
|
1063
1074
|
}
|
|
1064
1075
|
function getUiPackageJson(name) {
|
|
1065
1076
|
const pkg = {
|
|
@@ -1073,7 +1084,7 @@ function getUiPackageJson(name) {
|
|
|
1073
1084
|
preview: "vite preview"
|
|
1074
1085
|
},
|
|
1075
1086
|
dependencies: {
|
|
1076
|
-
"@eide/
|
|
1087
|
+
"@eide/foir-editor-sdk": "^0.1.0",
|
|
1077
1088
|
react: "^19.0.0",
|
|
1078
1089
|
"react-dom": "^19.0.0"
|
|
1079
1090
|
},
|
|
@@ -1170,13 +1181,13 @@ dist
|
|
|
1170
1181
|
function getUiMain() {
|
|
1171
1182
|
return `import { StrictMode } from 'react';
|
|
1172
1183
|
import { createRoot } from 'react-dom/client';
|
|
1173
|
-
import {
|
|
1184
|
+
import { EditorProvider, useEditor } from '@eide/foir-editor-sdk';
|
|
1174
1185
|
import { useEffect } from 'react';
|
|
1175
1186
|
import { App } from './App';
|
|
1176
1187
|
import './index.css';
|
|
1177
1188
|
|
|
1178
1189
|
function ThemeSync({ children }: { children: React.ReactNode }) {
|
|
1179
|
-
const { theme } =
|
|
1190
|
+
const { theme } = useEditor();
|
|
1180
1191
|
|
|
1181
1192
|
useEffect(() => {
|
|
1182
1193
|
const root = document.documentElement;
|
|
@@ -1189,17 +1200,17 @@ function ThemeSync({ children }: { children: React.ReactNode }) {
|
|
|
1189
1200
|
|
|
1190
1201
|
createRoot(document.getElementById('root')!).render(
|
|
1191
1202
|
<StrictMode>
|
|
1192
|
-
<
|
|
1203
|
+
<EditorProvider>
|
|
1193
1204
|
<ThemeSync>
|
|
1194
1205
|
<App />
|
|
1195
1206
|
</ThemeSync>
|
|
1196
|
-
</
|
|
1207
|
+
</EditorProvider>
|
|
1197
1208
|
</StrictMode>
|
|
1198
1209
|
);
|
|
1199
1210
|
`;
|
|
1200
1211
|
}
|
|
1201
|
-
function getUiApp(
|
|
1202
|
-
switch (
|
|
1212
|
+
function getUiApp(configType) {
|
|
1213
|
+
switch (configType) {
|
|
1203
1214
|
case "custom-editor":
|
|
1204
1215
|
return getCustomEditorApp();
|
|
1205
1216
|
case "widget":
|
|
@@ -1209,10 +1220,10 @@ function getUiApp(extensionType) {
|
|
|
1209
1220
|
}
|
|
1210
1221
|
}
|
|
1211
1222
|
function getCustomEditorApp() {
|
|
1212
|
-
return `import {
|
|
1223
|
+
return `import { useEditor, useAutoResize } from '@eide/foir-editor-sdk';
|
|
1213
1224
|
|
|
1214
1225
|
export function App() {
|
|
1215
|
-
const { isReady, init, updateField, setDirty } =
|
|
1226
|
+
const { isReady, init, updateField, setDirty } = useEditor();
|
|
1216
1227
|
const containerRef = useAutoResize({ minHeight: 600 });
|
|
1217
1228
|
|
|
1218
1229
|
if (!isReady) return null;
|
|
@@ -1220,7 +1231,7 @@ export function App() {
|
|
|
1220
1231
|
return (
|
|
1221
1232
|
<div ref={containerRef} className="p-6 space-y-4">
|
|
1222
1233
|
<h1 className="text-lg font-semibold">
|
|
1223
|
-
Editing: {init?.
|
|
1234
|
+
Editing: {init?.modelKey}
|
|
1224
1235
|
</h1>
|
|
1225
1236
|
<p className="text-sm text-gray-500">
|
|
1226
1237
|
Record: {init?.recordId}
|
|
@@ -1232,10 +1243,10 @@ export function App() {
|
|
|
1232
1243
|
`;
|
|
1233
1244
|
}
|
|
1234
1245
|
function getWidgetApp() {
|
|
1235
|
-
return `import {
|
|
1246
|
+
return `import { useEditor, useAutoResize } from '@eide/foir-editor-sdk';
|
|
1236
1247
|
|
|
1237
1248
|
export function App() {
|
|
1238
|
-
const { isReady, init, client } =
|
|
1249
|
+
const { isReady, init, client } = useEditor();
|
|
1239
1250
|
const containerRef = useAutoResize({ minHeight: 300 });
|
|
1240
1251
|
|
|
1241
1252
|
if (!isReady) return null;
|
|
@@ -1244,7 +1255,7 @@ export function App() {
|
|
|
1244
1255
|
<div ref={containerRef} className="p-6 space-y-4">
|
|
1245
1256
|
<h2 className="text-lg font-semibold">Dashboard Widget</h2>
|
|
1246
1257
|
<p className="text-sm text-gray-500">
|
|
1247
|
-
Connected to: {init?.
|
|
1258
|
+
Connected to: {init?.modelKey}
|
|
1248
1259
|
</p>
|
|
1249
1260
|
{/* Add your widget content here */}
|
|
1250
1261
|
</div>
|
|
@@ -1253,19 +1264,19 @@ export function App() {
|
|
|
1253
1264
|
`;
|
|
1254
1265
|
}
|
|
1255
1266
|
function getWorkflowApp() {
|
|
1256
|
-
return `import {
|
|
1267
|
+
return `import { useEditor, useAutoResize } from '@eide/foir-editor-sdk';
|
|
1257
1268
|
|
|
1258
1269
|
export function App() {
|
|
1259
|
-
const { isReady, init, client, requestSave } =
|
|
1270
|
+
const { isReady, init, client, requestSave } = useEditor();
|
|
1260
1271
|
const containerRef = useAutoResize({ minHeight: 400 });
|
|
1261
1272
|
|
|
1262
1273
|
if (!isReady) return null;
|
|
1263
1274
|
|
|
1264
1275
|
return (
|
|
1265
1276
|
<div ref={containerRef} className="p-6 space-y-4">
|
|
1266
|
-
<h1 className="text-lg font-semibold">Workflow
|
|
1277
|
+
<h1 className="text-lg font-semibold">Workflow Config</h1>
|
|
1267
1278
|
<p className="text-sm text-gray-500">
|
|
1268
|
-
Processing: {init?.
|
|
1279
|
+
Processing: {init?.modelKey} / {init?.recordId}
|
|
1269
1280
|
</p>
|
|
1270
1281
|
{/* Add your workflow steps here */}
|
|
1271
1282
|
</div>
|
|
@@ -1305,7 +1316,7 @@ function getApiPackageJson(name) {
|
|
|
1305
1316
|
start: "node dist/index.js"
|
|
1306
1317
|
},
|
|
1307
1318
|
dependencies: {
|
|
1308
|
-
"@eide/
|
|
1319
|
+
"@eide/foir-editor-sdk": "^0.1.0",
|
|
1309
1320
|
hono: "^4.0.0",
|
|
1310
1321
|
"@hono/node-server": "^1.0.0"
|
|
1311
1322
|
},
|
|
@@ -1341,8 +1352,8 @@ function getApiEnvExample(apiUrl) {
|
|
|
1341
1352
|
PLATFORM_BASE_URL=${baseUrl}
|
|
1342
1353
|
PLATFORM_API_KEY=sk_your_api_key_here
|
|
1343
1354
|
|
|
1344
|
-
#
|
|
1345
|
-
|
|
1355
|
+
# Config
|
|
1356
|
+
CONFIG_KEY=${"{your-config-key}"}
|
|
1346
1357
|
WEBHOOK_SECRET=your_webhook_secret_here
|
|
1347
1358
|
|
|
1348
1359
|
# Server
|
|
@@ -1363,25 +1374,25 @@ app.route('/', health);
|
|
|
1363
1374
|
|
|
1364
1375
|
const port = parseInt(process.env.PORT || '3002', 10);
|
|
1365
1376
|
|
|
1366
|
-
console.log(\`
|
|
1377
|
+
console.log(\`Config API running on http://localhost:\${port}\`);
|
|
1367
1378
|
|
|
1368
1379
|
serve({ fetch: app.fetch, port });
|
|
1369
1380
|
`;
|
|
1370
1381
|
}
|
|
1371
1382
|
function getApiWebhooks() {
|
|
1372
1383
|
return `import { Hono } from 'hono';
|
|
1373
|
-
import { verifyWebhookSignature } from '@eide/
|
|
1384
|
+
import { verifyWebhookSignature } from '@eide/foir-editor-sdk/server';
|
|
1374
1385
|
|
|
1375
1386
|
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET || '';
|
|
1376
1387
|
|
|
1377
1388
|
export const webhooks = new Hono();
|
|
1378
1389
|
|
|
1379
1390
|
/**
|
|
1380
|
-
* Receive
|
|
1391
|
+
* Receive record lifecycle events from the platform.
|
|
1381
1392
|
*/
|
|
1382
|
-
webhooks.post('/
|
|
1393
|
+
webhooks.post('/record-changed', async (c) => {
|
|
1383
1394
|
const body = await c.req.text();
|
|
1384
|
-
const signature = c.req.header('x-
|
|
1395
|
+
const signature = c.req.header('x-foir-signature') ?? '';
|
|
1385
1396
|
|
|
1386
1397
|
const valid = await verifyWebhookSignature(body, signature, WEBHOOK_SECRET);
|
|
1387
1398
|
if (!valid) {
|
|
@@ -1389,7 +1400,7 @@ webhooks.post('/entity-changed', async (c) => {
|
|
|
1389
1400
|
}
|
|
1390
1401
|
|
|
1391
1402
|
const payload = JSON.parse(body);
|
|
1392
|
-
console.log('[Webhook]
|
|
1403
|
+
console.log('[Webhook] Record changed:', payload.event, payload.recordId);
|
|
1393
1404
|
|
|
1394
1405
|
// TODO: Handle the event (sync, transform, notify, etc.)
|
|
1395
1406
|
|
|
@@ -1407,36 +1418,14 @@ health.get('/health', (c) => {
|
|
|
1407
1418
|
});
|
|
1408
1419
|
`;
|
|
1409
1420
|
}
|
|
1410
|
-
function getApiPlatform() {
|
|
1411
|
-
return `import { createExtensionClient } from '@eide/extension-sdk/server';
|
|
1412
1421
|
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
* - PLATFORM_BASE_URL: Platform API base URL
|
|
1418
|
-
* - PLATFORM_API_KEY: Project-scoped API key (sk_*)
|
|
1419
|
-
* - EXTENSION_KEY: This extension's extension key
|
|
1420
|
-
*/
|
|
1421
|
-
export const platform = createExtensionClient({
|
|
1422
|
-
baseUrl: process.env.PLATFORM_BASE_URL || 'http://localhost:4000',
|
|
1423
|
-
apiKey: process.env.PLATFORM_API_KEY || '',
|
|
1424
|
-
extensionKey: process.env.EXTENSION_KEY || '',
|
|
1425
|
-
});
|
|
1426
|
-
`;
|
|
1422
|
+
// src/commands/create-config.ts
|
|
1423
|
+
var CONFIG_TYPES = ["custom-editor", "workflow", "widget"];
|
|
1424
|
+
function isValidConfigType(value) {
|
|
1425
|
+
return CONFIG_TYPES.includes(value);
|
|
1427
1426
|
}
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
var EXTENSION_TYPES = [
|
|
1431
|
-
"custom-editor",
|
|
1432
|
-
"workflow",
|
|
1433
|
-
"widget"
|
|
1434
|
-
];
|
|
1435
|
-
function isValidExtensionType(value) {
|
|
1436
|
-
return EXTENSION_TYPES.includes(value);
|
|
1437
|
-
}
|
|
1438
|
-
function registerCreateExtensionCommand(program2, globalOpts) {
|
|
1439
|
-
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(
|
|
1440
1429
|
"--api-url <url>",
|
|
1441
1430
|
"Platform API URL",
|
|
1442
1431
|
"http://localhost:4000/graphql"
|
|
@@ -1445,43 +1434,43 @@ function registerCreateExtensionCommand(program2, globalOpts) {
|
|
|
1445
1434
|
globalOpts,
|
|
1446
1435
|
async (name, cmdOpts) => {
|
|
1447
1436
|
console.log();
|
|
1448
|
-
console.log(chalk4.bold(" Create Foir
|
|
1449
|
-
console.log(chalk4.gray("
|
|
1437
|
+
console.log(chalk4.bold(" Create Foir Config"));
|
|
1438
|
+
console.log(chalk4.gray(" ------------------"));
|
|
1450
1439
|
console.log();
|
|
1451
|
-
let
|
|
1452
|
-
if (!
|
|
1440
|
+
let configName = name;
|
|
1441
|
+
if (!configName) {
|
|
1453
1442
|
const { inputName } = await inquirer2.prompt([
|
|
1454
1443
|
{
|
|
1455
1444
|
type: "input",
|
|
1456
1445
|
name: "inputName",
|
|
1457
|
-
message: "
|
|
1458
|
-
default: "my-
|
|
1446
|
+
message: "Config name:",
|
|
1447
|
+
default: "my-config"
|
|
1459
1448
|
}
|
|
1460
1449
|
]);
|
|
1461
|
-
|
|
1450
|
+
configName = inputName;
|
|
1462
1451
|
}
|
|
1463
|
-
let
|
|
1464
|
-
if (cmdOpts?.type &&
|
|
1465
|
-
|
|
1452
|
+
let configType;
|
|
1453
|
+
if (cmdOpts?.type && isValidConfigType(cmdOpts.type)) {
|
|
1454
|
+
configType = cmdOpts.type;
|
|
1466
1455
|
} else {
|
|
1467
1456
|
const { selectedType } = await inquirer2.prompt([
|
|
1468
1457
|
{
|
|
1469
1458
|
type: "list",
|
|
1470
1459
|
name: "selectedType",
|
|
1471
|
-
message: "
|
|
1472
|
-
choices:
|
|
1460
|
+
message: "Config type:",
|
|
1461
|
+
choices: CONFIG_TYPES,
|
|
1473
1462
|
default: "custom-editor"
|
|
1474
1463
|
}
|
|
1475
1464
|
]);
|
|
1476
|
-
|
|
1465
|
+
configType = selectedType;
|
|
1477
1466
|
}
|
|
1478
1467
|
const apiUrl = cmdOpts?.apiUrl ?? "http://localhost:4000/graphql";
|
|
1479
1468
|
console.log();
|
|
1480
1469
|
console.log(
|
|
1481
|
-
` Scaffolding ${chalk4.cyan(`"${
|
|
1470
|
+
` Scaffolding ${chalk4.cyan(`"${configName}"`)} (${configType})...`
|
|
1482
1471
|
);
|
|
1483
1472
|
console.log();
|
|
1484
|
-
await scaffold(
|
|
1473
|
+
await scaffold(configName, configType, apiUrl);
|
|
1485
1474
|
}
|
|
1486
1475
|
)
|
|
1487
1476
|
);
|
|
@@ -1916,11 +1905,10 @@ Edit the files, then run:
|
|
|
1916
1905
|
);
|
|
1917
1906
|
}
|
|
1918
1907
|
|
|
1919
|
-
// src/commands/
|
|
1908
|
+
// src/commands/push.ts
|
|
1920
1909
|
import chalk6 from "chalk";
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
import inquirer4 from "inquirer";
|
|
1910
|
+
import { existsSync as existsSync4 } from "fs";
|
|
1911
|
+
import { resolve as resolve4 } from "path";
|
|
1924
1912
|
|
|
1925
1913
|
// src/lib/config-loader.ts
|
|
1926
1914
|
import { readFile } from "fs/promises";
|
|
@@ -1945,7 +1933,194 @@ async function loadConfig(filePath) {
|
|
|
1945
1933
|
);
|
|
1946
1934
|
}
|
|
1947
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
|
+
|
|
1948
2122
|
// src/lib/input.ts
|
|
2123
|
+
import inquirer5 from "inquirer";
|
|
1949
2124
|
async function parseInputData(opts) {
|
|
1950
2125
|
if (opts.data) {
|
|
1951
2126
|
return JSON.parse(opts.data);
|
|
@@ -1974,7 +2149,7 @@ function isUUID(value) {
|
|
|
1974
2149
|
}
|
|
1975
2150
|
async function confirmAction(message, opts) {
|
|
1976
2151
|
if (opts?.confirm) return true;
|
|
1977
|
-
const { confirmed } = await
|
|
2152
|
+
const { confirmed } = await inquirer5.prompt([
|
|
1978
2153
|
{
|
|
1979
2154
|
type: "confirm",
|
|
1980
2155
|
name: "confirmed",
|
|
@@ -2145,7 +2320,7 @@ function registerProfilesCommand(program2, globalOpts) {
|
|
|
2145
2320
|
if (opts.json || opts.jsonl) {
|
|
2146
2321
|
formatOutput({ deleted: name }, opts);
|
|
2147
2322
|
} else {
|
|
2148
|
-
console.log(
|
|
2323
|
+
console.log(chalk8.green(`Deleted profile "${name}".`));
|
|
2149
2324
|
}
|
|
2150
2325
|
}
|
|
2151
2326
|
)
|
|
@@ -2154,9 +2329,9 @@ function registerProfilesCommand(program2, globalOpts) {
|
|
|
2154
2329
|
|
|
2155
2330
|
// src/commands/register-commands.ts
|
|
2156
2331
|
import { readFileSync, readdirSync } from "fs";
|
|
2157
|
-
import { resolve as
|
|
2332
|
+
import { resolve as resolve5, dirname as dirname4 } from "path";
|
|
2158
2333
|
import { fileURLToPath } from "url";
|
|
2159
|
-
import
|
|
2334
|
+
import chalk9 from "chalk";
|
|
2160
2335
|
|
|
2161
2336
|
// ../command-registry/src/command-map.ts
|
|
2162
2337
|
var COMMANDS = [
|
|
@@ -2782,65 +2957,65 @@ var COMMANDS = [
|
|
|
2782
2957
|
// EXTENSIONS
|
|
2783
2958
|
// =========================================================================
|
|
2784
2959
|
{
|
|
2785
|
-
group: "
|
|
2960
|
+
group: "configs",
|
|
2786
2961
|
name: "list",
|
|
2787
|
-
description: "List
|
|
2788
|
-
operation: "
|
|
2962
|
+
description: "List configs",
|
|
2963
|
+
operation: "configs",
|
|
2789
2964
|
operationType: "query",
|
|
2790
2965
|
columns: [
|
|
2791
2966
|
{ key: "id", header: "ID", width: 28 },
|
|
2792
2967
|
{ key: "key", header: "Key", width: 20 },
|
|
2793
2968
|
{ key: "name", header: "Name", width: 20 },
|
|
2794
|
-
{ key: "
|
|
2969
|
+
{ key: "configType", header: "Type", width: 12 },
|
|
2795
2970
|
{ key: "enabled", header: "Enabled", width: 8, format: "boolean" },
|
|
2796
2971
|
{ key: "syncStatus", header: "Sync", width: 10 }
|
|
2797
2972
|
]
|
|
2798
2973
|
},
|
|
2799
2974
|
{
|
|
2800
|
-
group: "
|
|
2975
|
+
group: "configs",
|
|
2801
2976
|
name: "get",
|
|
2802
|
-
description: "Get
|
|
2803
|
-
operation: "
|
|
2977
|
+
description: "Get a config",
|
|
2978
|
+
operation: "config",
|
|
2804
2979
|
operationType: "query",
|
|
2805
2980
|
positionalArgs: [{ name: "id", graphqlArg: "id" }],
|
|
2806
|
-
alternateGet: { operation: "
|
|
2981
|
+
alternateGet: { operation: "configByKey", argName: "key" }
|
|
2807
2982
|
},
|
|
2808
2983
|
{
|
|
2809
|
-
group: "
|
|
2984
|
+
group: "configs",
|
|
2810
2985
|
name: "register",
|
|
2811
|
-
description: "Register
|
|
2812
|
-
operation: "
|
|
2986
|
+
description: "Register a config",
|
|
2987
|
+
operation: "registerConfig",
|
|
2813
2988
|
operationType: "mutation",
|
|
2814
2989
|
acceptsInput: true,
|
|
2815
|
-
successMessage: "Registered
|
|
2990
|
+
successMessage: "Registered config"
|
|
2816
2991
|
},
|
|
2817
2992
|
{
|
|
2818
|
-
group: "
|
|
2819
|
-
name: "
|
|
2820
|
-
description: "
|
|
2821
|
-
operation: "
|
|
2993
|
+
group: "configs",
|
|
2994
|
+
name: "apply",
|
|
2995
|
+
description: "Apply a config",
|
|
2996
|
+
operation: "applyConfig",
|
|
2822
2997
|
operationType: "mutation",
|
|
2823
2998
|
acceptsInput: true,
|
|
2824
|
-
successMessage: "
|
|
2999
|
+
successMessage: "Applied config"
|
|
2825
3000
|
},
|
|
2826
3001
|
{
|
|
2827
|
-
group: "
|
|
3002
|
+
group: "configs",
|
|
2828
3003
|
name: "uninstall",
|
|
2829
|
-
description: "Unregister
|
|
2830
|
-
operation: "
|
|
3004
|
+
description: "Unregister a config",
|
|
3005
|
+
operation: "unregisterConfig",
|
|
2831
3006
|
operationType: "mutation",
|
|
2832
3007
|
positionalArgs: [{ name: "id", graphqlArg: "id" }],
|
|
2833
3008
|
requiresConfirmation: true,
|
|
2834
3009
|
scalarResult: true,
|
|
2835
|
-
successMessage: "Unregistered
|
|
3010
|
+
successMessage: "Unregistered config"
|
|
2836
3011
|
},
|
|
2837
3012
|
{
|
|
2838
|
-
group: "
|
|
3013
|
+
group: "configs",
|
|
2839
3014
|
name: "sync",
|
|
2840
|
-
description: "Trigger
|
|
2841
|
-
operation: "
|
|
3015
|
+
description: "Trigger config sync",
|
|
3016
|
+
operation: "triggerConfigSync",
|
|
2842
3017
|
operationType: "mutation",
|
|
2843
|
-
positionalArgs: [{ name: "
|
|
3018
|
+
positionalArgs: [{ name: "configId", graphqlArg: "configId" }],
|
|
2844
3019
|
successMessage: "Triggered sync"
|
|
2845
3020
|
},
|
|
2846
3021
|
// =========================================================================
|
|
@@ -3757,11 +3932,11 @@ function createSchemaEngine(sdl) {
|
|
|
3757
3932
|
var __filename = fileURLToPath(import.meta.url);
|
|
3758
3933
|
var __dirname = dirname4(__filename);
|
|
3759
3934
|
function loadSchemaSDL() {
|
|
3760
|
-
const bundledPath =
|
|
3935
|
+
const bundledPath = resolve5(__dirname, "schema.graphql");
|
|
3761
3936
|
try {
|
|
3762
3937
|
return readFileSync(bundledPath, "utf-8");
|
|
3763
3938
|
} catch {
|
|
3764
|
-
const monorepoPath =
|
|
3939
|
+
const monorepoPath = resolve5(
|
|
3765
3940
|
__dirname,
|
|
3766
3941
|
"../../../graphql-core/schema.graphql"
|
|
3767
3942
|
);
|
|
@@ -3917,11 +4092,11 @@ function registerDynamicCommands(program2, globalOpts) {
|
|
|
3917
4092
|
);
|
|
3918
4093
|
Object.assign(variables, coerced);
|
|
3919
4094
|
if (flags.dir && entry.acceptsInput) {
|
|
3920
|
-
const dirPath =
|
|
4095
|
+
const dirPath = resolve5(String(flags.dir));
|
|
3921
4096
|
const files = readdirSync(dirPath).filter((f) => /\.(json|ts|js|mjs)$/.test(f)).sort();
|
|
3922
4097
|
if (files.length === 0) {
|
|
3923
4098
|
console.error(
|
|
3924
|
-
|
|
4099
|
+
chalk9.yellow(`\u26A0 No .json/.ts/.js files found in ${dirPath}`)
|
|
3925
4100
|
);
|
|
3926
4101
|
return;
|
|
3927
4102
|
}
|
|
@@ -3929,7 +4104,7 @@ function registerDynamicCommands(program2, globalOpts) {
|
|
|
3929
4104
|
let updated = 0;
|
|
3930
4105
|
let failed = 0;
|
|
3931
4106
|
for (const file of files) {
|
|
3932
|
-
const filePath =
|
|
4107
|
+
const filePath = resolve5(dirPath, file);
|
|
3933
4108
|
const fileData = await parseInputData({ file: filePath });
|
|
3934
4109
|
const argName = entry.inputArgName ?? "input";
|
|
3935
4110
|
const fileVars = { ...variables, [argName]: fileData };
|
|
@@ -3966,19 +4141,19 @@ function registerDynamicCommands(program2, globalOpts) {
|
|
|
3966
4141
|
} catch (updateErr) {
|
|
3967
4142
|
failed++;
|
|
3968
4143
|
const msg2 = updateErr instanceof Error ? updateErr.message : String(updateErr);
|
|
3969
|
-
console.error(
|
|
4144
|
+
console.error(chalk9.red(`\u2717 ${label}:`), msg2);
|
|
3970
4145
|
continue;
|
|
3971
4146
|
}
|
|
3972
4147
|
}
|
|
3973
4148
|
failed++;
|
|
3974
4149
|
const msg = err instanceof Error ? err.message : String(err);
|
|
3975
|
-
console.error(
|
|
4150
|
+
console.error(chalk9.red(`\u2717 ${label}:`), msg);
|
|
3976
4151
|
}
|
|
3977
4152
|
}
|
|
3978
4153
|
if (!(opts.json || opts.jsonl || opts.quiet)) {
|
|
3979
4154
|
console.log("");
|
|
3980
4155
|
console.log(
|
|
3981
|
-
|
|
4156
|
+
chalk9.bold(
|
|
3982
4157
|
`Done: ${created} created${updated ? `, ${updated} updated` : ""}${failed ? `, ${failed} failed` : ""}`
|
|
3983
4158
|
)
|
|
3984
4159
|
);
|
|
@@ -4154,7 +4329,7 @@ function registerDynamicCommands(program2, globalOpts) {
|
|
|
4154
4329
|
}
|
|
4155
4330
|
} else if (!(opts.json || opts.jsonl || opts.quiet)) {
|
|
4156
4331
|
console.error(
|
|
4157
|
-
|
|
4332
|
+
chalk9.yellow(
|
|
4158
4333
|
"\u26A0 Could not auto-publish: no version found in response"
|
|
4159
4334
|
)
|
|
4160
4335
|
);
|
|
@@ -4178,7 +4353,7 @@ function autoColumns(items) {
|
|
|
4178
4353
|
// src/cli.ts
|
|
4179
4354
|
var __filename2 = fileURLToPath2(import.meta.url);
|
|
4180
4355
|
var __dirname2 = dirname5(__filename2);
|
|
4181
|
-
config({ path:
|
|
4356
|
+
config({ path: resolve6(__dirname2, "../.env.local") });
|
|
4182
4357
|
var require2 = createRequire(import.meta.url);
|
|
4183
4358
|
var { version } = require2("../package.json");
|
|
4184
4359
|
var program = new Command();
|
|
@@ -4200,7 +4375,9 @@ registerWhoamiCommand(program, getGlobalOpts);
|
|
|
4200
4375
|
registerProfilesCommand(program, getGlobalOpts);
|
|
4201
4376
|
registerMediaCommands(program, getGlobalOpts);
|
|
4202
4377
|
registerSearchCommands(program, getGlobalOpts);
|
|
4203
|
-
|
|
4378
|
+
registerPushCommand(program, getGlobalOpts);
|
|
4379
|
+
registerRemoveCommand(program, getGlobalOpts);
|
|
4380
|
+
registerCreateConfigCommand(program, getGlobalOpts);
|
|
4204
4381
|
registerInitCommands(program, getGlobalOpts);
|
|
4205
4382
|
registerDynamicCommands(program, getGlobalOpts);
|
|
4206
4383
|
program.parse();
|