@eide/foir-cli 0.1.16 → 0.1.18

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.
Files changed (59) hide show
  1. package/dist/cli.js +3 -0
  2. package/dist/codegen/field-mapping.d.ts +1 -0
  3. package/dist/codegen/field-mapping.d.ts.map +1 -1
  4. package/dist/codegen/field-mapping.js +8 -0
  5. package/dist/codegen/generators/customer-profile-hooks.d.ts +5 -0
  6. package/dist/codegen/generators/customer-profile-hooks.d.ts.map +1 -0
  7. package/dist/codegen/generators/customer-profile-hooks.js +78 -0
  8. package/dist/codegen/generators/customer-profile-loaders.d.ts +5 -0
  9. package/dist/codegen/generators/customer-profile-loaders.d.ts.map +1 -0
  10. package/dist/codegen/generators/customer-profile-loaders.js +67 -0
  11. package/dist/codegen/generators/customer-profile-operations.d.ts +5 -0
  12. package/dist/codegen/generators/customer-profile-operations.d.ts.map +1 -0
  13. package/dist/codegen/generators/customer-profile-operations.js +126 -0
  14. package/dist/codegen/generators/documents.d.ts.map +1 -1
  15. package/dist/codegen/generators/documents.js +4 -0
  16. package/dist/codegen/generators/public-schema-content.d.ts +14 -0
  17. package/dist/codegen/generators/public-schema-content.d.ts.map +1 -0
  18. package/dist/codegen/generators/public-schema-content.js +22 -0
  19. package/dist/codegen/generators/react-hooks-index.d.ts +6 -0
  20. package/dist/codegen/generators/react-hooks-index.d.ts.map +1 -0
  21. package/dist/codegen/generators/react-hooks-index.js +20 -0
  22. package/dist/codegen/generators/react-hooks.d.ts +7 -0
  23. package/dist/codegen/generators/react-hooks.d.ts.map +1 -0
  24. package/dist/codegen/generators/react-hooks.js +139 -0
  25. package/dist/codegen/generators/remix-loaders-index.d.ts +6 -0
  26. package/dist/codegen/generators/remix-loaders-index.d.ts.map +1 -0
  27. package/dist/codegen/generators/remix-loaders-index.js +20 -0
  28. package/dist/codegen/generators/remix-loaders.d.ts +7 -0
  29. package/dist/codegen/generators/remix-loaders.d.ts.map +1 -0
  30. package/dist/codegen/generators/remix-loaders.js +107 -0
  31. package/dist/codegen/generators/static-documents.d.ts +14 -0
  32. package/dist/codegen/generators/static-documents.d.ts.map +1 -0
  33. package/dist/codegen/generators/static-documents.js +766 -0
  34. package/dist/codegen/generators/typed-operations-common.d.ts +6 -0
  35. package/dist/codegen/generators/typed-operations-common.d.ts.map +1 -0
  36. package/dist/codegen/generators/typed-operations-common.js +74 -0
  37. package/dist/codegen/generators/typed-operations-index.d.ts +6 -0
  38. package/dist/codegen/generators/typed-operations-index.d.ts.map +1 -0
  39. package/dist/codegen/generators/typed-operations-index.js +22 -0
  40. package/dist/codegen/generators/typed-operations.d.ts +11 -0
  41. package/dist/codegen/generators/typed-operations.d.ts.map +1 -0
  42. package/dist/codegen/generators/typed-operations.js +251 -0
  43. package/dist/commands/create-extension.d.ts +4 -0
  44. package/dist/commands/create-extension.d.ts.map +1 -0
  45. package/dist/commands/create-extension.js +60 -0
  46. package/dist/commands/pull.d.ts.map +1 -1
  47. package/dist/commands/pull.js +135 -25
  48. package/dist/config/pull-config.d.ts +6 -0
  49. package/dist/config/pull-config.d.ts.map +1 -1
  50. package/dist/config/pull-config.js +50 -1
  51. package/dist/config/types.d.ts +23 -0
  52. package/dist/config/types.d.ts.map +1 -1
  53. package/dist/scaffold/package-manager.d.ts +12 -0
  54. package/dist/scaffold/package-manager.d.ts.map +1 -0
  55. package/dist/scaffold/package-manager.js +51 -0
  56. package/dist/scaffold/scaffold.d.ts +4 -0
  57. package/dist/scaffold/scaffold.d.ts.map +1 -0
  58. package/dist/scaffold/scaffold.js +462 -0
  59. package/package.json +1 -1
@@ -0,0 +1,462 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import { detectPackageManager } from './package-manager.js';
4
+ export async function scaffold(projectName, extensionType, apiUrl) {
5
+ const projectDir = path.resolve(process.cwd(), projectName);
6
+ if (fs.existsSync(projectDir)) {
7
+ throw new Error(`Directory "${projectName}" already exists. Choose a different name or remove the existing directory.`);
8
+ }
9
+ // Create directory structure
10
+ const files = getFiles(projectName, extensionType, apiUrl);
11
+ for (const [filePath, content] of Object.entries(files)) {
12
+ const fullPath = path.join(projectDir, filePath);
13
+ const dir = path.dirname(fullPath);
14
+ if (!fs.existsSync(dir)) {
15
+ fs.mkdirSync(dir, { recursive: true });
16
+ }
17
+ fs.writeFileSync(fullPath, content, 'utf-8');
18
+ }
19
+ // Detect package manager and print instructions
20
+ const pm = detectPackageManager();
21
+ console.log(' Files created:');
22
+ console.log();
23
+ for (const filePath of Object.keys(files)) {
24
+ console.log(` ${filePath}`);
25
+ }
26
+ console.log();
27
+ console.log(' Done! Next steps:');
28
+ console.log();
29
+ console.log(` cd ${projectName}`);
30
+ console.log(` ${pm.installCommand}`);
31
+ console.log(` cp ui/.env.example ui/.env.local`);
32
+ console.log(` cp api/.env.example api/.env.local`);
33
+ console.log(` ${pm.name === 'npm' ? 'npm run' : pm.name} dev`);
34
+ console.log();
35
+ }
36
+ function getFiles(projectName, extensionType, apiUrl) {
37
+ return {
38
+ // Root
39
+ 'package.json': getRootPackageJson(projectName),
40
+ 'extension.manifest.json': getManifest(projectName, extensionType),
41
+ // UI (Vite SPA)
42
+ 'ui/package.json': getUiPackageJson(projectName),
43
+ 'ui/tsconfig.json': getUiTsconfig(),
44
+ 'ui/vite.config.ts': getUiViteConfig(),
45
+ 'ui/index.html': getUiIndexHtml(projectName),
46
+ 'ui/.env.example': getUiEnvExample(apiUrl),
47
+ 'ui/.gitignore': getUiGitignore(),
48
+ 'ui/src/main.tsx': getUiMain(),
49
+ 'ui/src/App.tsx': getUiApp(extensionType),
50
+ 'ui/src/index.css': getUiCss(),
51
+ 'ui/src/vite-env.d.ts': '/// <reference types="vite/client" />\n',
52
+ // API (Hono)
53
+ 'api/package.json': getApiPackageJson(projectName),
54
+ 'api/tsconfig.json': getApiTsconfig(),
55
+ 'api/.env.example': getApiEnvExample(apiUrl),
56
+ 'api/.gitignore': 'node_modules\ndist\n.env\n.env.local\n',
57
+ 'api/src/index.ts': getApiIndex(),
58
+ 'api/src/routes/webhooks.ts': getApiWebhooks(),
59
+ 'api/src/routes/health.ts': getApiHealth(),
60
+ 'api/src/lib/platform.ts': getApiPlatform(),
61
+ };
62
+ }
63
+ // ---------------------------------------------------------------------------
64
+ // Root
65
+ // ---------------------------------------------------------------------------
66
+ function getRootPackageJson(name) {
67
+ const pkg = {
68
+ name,
69
+ version: '0.1.0',
70
+ private: true,
71
+ scripts: {
72
+ dev: 'concurrently "pnpm --filter ./ui dev" "pnpm --filter ./api dev"',
73
+ build: 'pnpm --filter ./ui build && pnpm --filter ./api build',
74
+ },
75
+ devDependencies: {
76
+ concurrently: '^9.0.0',
77
+ },
78
+ };
79
+ return JSON.stringify(pkg, null, 2) + '\n';
80
+ }
81
+ function getManifest(name, extensionType) {
82
+ const manifest = {
83
+ name: name.replace(/-/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()),
84
+ version: '0.1.0',
85
+ type: extensionType,
86
+ description: `${extensionType} extension`,
87
+ entityTypes: [],
88
+ };
89
+ return JSON.stringify(manifest, null, 2) + '\n';
90
+ }
91
+ // ---------------------------------------------------------------------------
92
+ // UI (Vite SPA)
93
+ // ---------------------------------------------------------------------------
94
+ function getUiPackageJson(name) {
95
+ const pkg = {
96
+ name: `${name}-ui`,
97
+ version: '0.1.0',
98
+ private: true,
99
+ type: 'module',
100
+ scripts: {
101
+ dev: 'vite',
102
+ build: 'tsc && vite build',
103
+ preview: 'vite preview',
104
+ },
105
+ dependencies: {
106
+ '@eide/extension-sdk': '^0.1.0',
107
+ react: '^19.0.0',
108
+ 'react-dom': '^19.0.0',
109
+ },
110
+ devDependencies: {
111
+ '@tailwindcss/vite': '^4.0.6',
112
+ '@types/react': '^19.0.0',
113
+ '@types/react-dom': '^19.0.0',
114
+ '@vitejs/plugin-react': '^4.3.4',
115
+ tailwindcss: '^4.0.0',
116
+ typescript: '^5.0.0',
117
+ vite: '^6.0.7',
118
+ },
119
+ };
120
+ return JSON.stringify(pkg, null, 2) + '\n';
121
+ }
122
+ function getUiTsconfig() {
123
+ const config = {
124
+ compilerOptions: {
125
+ target: 'ES2020',
126
+ lib: ['ES2020', 'DOM', 'DOM.Iterable'],
127
+ module: 'ESNext',
128
+ skipLibCheck: true,
129
+ moduleResolution: 'bundler',
130
+ allowImportingTsExtensions: true,
131
+ resolveJsonModule: true,
132
+ isolatedModules: true,
133
+ noEmit: true,
134
+ jsx: 'react-jsx',
135
+ strict: true,
136
+ baseUrl: '.',
137
+ paths: { '@/*': ['./src/*'] },
138
+ },
139
+ include: ['src'],
140
+ };
141
+ return JSON.stringify(config, null, 2) + '\n';
142
+ }
143
+ function getUiViteConfig() {
144
+ return `import { defineConfig } from 'vite';
145
+ import react from '@vitejs/plugin-react';
146
+ import tailwindcss from '@tailwindcss/vite';
147
+ import path from 'path';
148
+
149
+ export default defineConfig({
150
+ plugins: [react(), tailwindcss()],
151
+ resolve: {
152
+ alias: {
153
+ '@': path.resolve(__dirname, './src'),
154
+ },
155
+ },
156
+ server: {
157
+ port: 3001,
158
+ host: '127.0.0.1',
159
+ cors: true,
160
+ headers: {
161
+ 'X-Frame-Options': 'ALLOWALL',
162
+ 'Content-Security-Policy': 'frame-ancestors *',
163
+ },
164
+ },
165
+ build: {
166
+ outDir: 'dist',
167
+ sourcemap: true,
168
+ },
169
+ });
170
+ `;
171
+ }
172
+ function getUiIndexHtml(name) {
173
+ const title = name
174
+ .replace(/-/g, ' ')
175
+ .replace(/\b\w/g, (c) => c.toUpperCase());
176
+ return `<!doctype html>
177
+ <html lang="en">
178
+ <head>
179
+ <meta charset="UTF-8" />
180
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
181
+ <title>${title}</title>
182
+ </head>
183
+ <body>
184
+ <div id="root"></div>
185
+ <script type="module" src="/src/main.tsx"></script>
186
+ </body>
187
+ </html>
188
+ `;
189
+ }
190
+ function getUiEnvExample(apiUrl) {
191
+ return `VITE_PARENT_ORIGIN=http://localhost:3000
192
+ VITE_API_URL=${apiUrl}
193
+ `;
194
+ }
195
+ function getUiGitignore() {
196
+ return `node_modules
197
+ dist
198
+ .env
199
+ .env.local
200
+ `;
201
+ }
202
+ function getUiMain() {
203
+ return `import { StrictMode } from 'react';
204
+ import { createRoot } from 'react-dom/client';
205
+ import { ExtensionProvider, useExtension } from '@eide/extension-sdk';
206
+ import { useEffect } from 'react';
207
+ import { App } from './App';
208
+ import './index.css';
209
+
210
+ function ThemeSync({ children }: { children: React.ReactNode }) {
211
+ const { theme } = useExtension();
212
+
213
+ useEffect(() => {
214
+ const root = document.documentElement;
215
+ root.classList.remove('light', 'dark');
216
+ root.classList.add(theme);
217
+ }, [theme]);
218
+
219
+ return <>{children}</>;
220
+ }
221
+
222
+ createRoot(document.getElementById('root')!).render(
223
+ <StrictMode>
224
+ <ExtensionProvider>
225
+ <ThemeSync>
226
+ <App />
227
+ </ThemeSync>
228
+ </ExtensionProvider>
229
+ </StrictMode>
230
+ );
231
+ `;
232
+ }
233
+ function getUiApp(extensionType) {
234
+ switch (extensionType) {
235
+ case 'custom-editor':
236
+ return getCustomEditorApp();
237
+ case 'widget':
238
+ return getWidgetApp();
239
+ case 'workflow':
240
+ return getWorkflowApp();
241
+ }
242
+ }
243
+ function getCustomEditorApp() {
244
+ return `import { useExtension, useAutoResize } from '@eide/extension-sdk';
245
+
246
+ export function App() {
247
+ const { isReady, init, updateField, setDirty } = useExtension();
248
+ const containerRef = useAutoResize({ minHeight: 600 });
249
+
250
+ if (!isReady) return null;
251
+
252
+ return (
253
+ <div ref={containerRef} className="p-6 space-y-4">
254
+ <h1 className="text-lg font-semibold">
255
+ Editing: {init?.entityModelKey}
256
+ </h1>
257
+ <p className="text-sm text-gray-500">
258
+ Record: {init?.recordId}
259
+ </p>
260
+ {/* Add your editor form fields here */}
261
+ </div>
262
+ );
263
+ }
264
+ `;
265
+ }
266
+ function getWidgetApp() {
267
+ return `import { useExtension, useAutoResize } from '@eide/extension-sdk';
268
+
269
+ export function App() {
270
+ const { isReady, init, client } = useExtension();
271
+ const containerRef = useAutoResize({ minHeight: 300 });
272
+
273
+ if (!isReady) return null;
274
+
275
+ return (
276
+ <div ref={containerRef} className="p-6 space-y-4">
277
+ <h2 className="text-lg font-semibold">Dashboard Widget</h2>
278
+ <p className="text-sm text-gray-500">
279
+ Connected to: {init?.entityModelKey}
280
+ </p>
281
+ {/* Add your widget content here */}
282
+ </div>
283
+ );
284
+ }
285
+ `;
286
+ }
287
+ function getWorkflowApp() {
288
+ return `import { useExtension, useAutoResize } from '@eide/extension-sdk';
289
+
290
+ export function App() {
291
+ const { isReady, init, client, requestSave } = useExtension();
292
+ const containerRef = useAutoResize({ minHeight: 400 });
293
+
294
+ if (!isReady) return null;
295
+
296
+ return (
297
+ <div ref={containerRef} className="p-6 space-y-4">
298
+ <h1 className="text-lg font-semibold">Workflow Extension</h1>
299
+ <p className="text-sm text-gray-500">
300
+ Processing: {init?.entityModelKey} / {init?.recordId}
301
+ </p>
302
+ {/* Add your workflow steps here */}
303
+ </div>
304
+ );
305
+ }
306
+ `;
307
+ }
308
+ function getUiCss() {
309
+ return `@import 'tailwindcss';
310
+
311
+ :root {
312
+ --background: #ffffff;
313
+ --foreground: #171717;
314
+ }
315
+
316
+ .dark {
317
+ --background: #0a0a0a;
318
+ --foreground: #ededed;
319
+ }
320
+
321
+ body {
322
+ background: var(--background);
323
+ color: var(--foreground);
324
+ font-family: system-ui, sans-serif;
325
+ }
326
+ `;
327
+ }
328
+ // ---------------------------------------------------------------------------
329
+ // API (Hono)
330
+ // ---------------------------------------------------------------------------
331
+ function getApiPackageJson(name) {
332
+ const pkg = {
333
+ name: `${name}-api`,
334
+ version: '0.1.0',
335
+ private: true,
336
+ type: 'module',
337
+ scripts: {
338
+ dev: 'tsx watch src/index.ts',
339
+ build: 'tsx src/index.ts',
340
+ start: 'node dist/index.js',
341
+ },
342
+ dependencies: {
343
+ '@eide/extension-sdk': '^0.1.0',
344
+ hono: '^4.0.0',
345
+ '@hono/node-server': '^1.0.0',
346
+ },
347
+ devDependencies: {
348
+ tsx: '^4.0.0',
349
+ typescript: '^5.0.0',
350
+ },
351
+ };
352
+ return JSON.stringify(pkg, null, 2) + '\n';
353
+ }
354
+ function getApiTsconfig() {
355
+ const config = {
356
+ compilerOptions: {
357
+ target: 'ES2020',
358
+ module: 'ESNext',
359
+ moduleResolution: 'bundler',
360
+ outDir: './dist',
361
+ rootDir: './src',
362
+ strict: true,
363
+ esModuleInterop: true,
364
+ skipLibCheck: true,
365
+ resolveJsonModule: true,
366
+ isolatedModules: true,
367
+ },
368
+ include: ['src'],
369
+ exclude: ['dist', 'node_modules'],
370
+ };
371
+ return JSON.stringify(config, null, 2) + '\n';
372
+ }
373
+ function getApiEnvExample(apiUrl) {
374
+ const baseUrl = apiUrl.replace(/\/graphql$/, '');
375
+ return `# Platform API
376
+ PLATFORM_BASE_URL=${baseUrl}
377
+ PLATFORM_API_KEY=sk_your_api_key_here
378
+
379
+ # Extension
380
+ EXTENSION_KEY=${'{your-extension-key}'}
381
+ WEBHOOK_SECRET=your_webhook_secret_here
382
+
383
+ # Server
384
+ PORT=3002
385
+ `;
386
+ }
387
+ function getApiIndex() {
388
+ return `import { Hono } from 'hono';
389
+ import { serve } from '@hono/node-server';
390
+ import { webhooks } from './routes/webhooks';
391
+ import { health } from './routes/health';
392
+
393
+ const app = new Hono();
394
+
395
+ // Routes
396
+ app.route('/webhooks', webhooks);
397
+ app.route('/', health);
398
+
399
+ const port = parseInt(process.env.PORT || '3002', 10);
400
+
401
+ console.log(\`Extension API running on http://localhost:\${port}\`);
402
+
403
+ serve({ fetch: app.fetch, port });
404
+ `;
405
+ }
406
+ function getApiWebhooks() {
407
+ return `import { Hono } from 'hono';
408
+ import { verifyWebhookSignature } from '@eide/extension-sdk/server';
409
+
410
+ const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET || '';
411
+
412
+ export const webhooks = new Hono();
413
+
414
+ /**
415
+ * Receive entity lifecycle events from the platform.
416
+ */
417
+ webhooks.post('/entity-changed', async (c) => {
418
+ const body = await c.req.text();
419
+ const signature = c.req.header('x-eide-signature') ?? '';
420
+
421
+ const valid = await verifyWebhookSignature(body, signature, WEBHOOK_SECRET);
422
+ if (!valid) {
423
+ return c.json({ error: 'Invalid signature' }, 401);
424
+ }
425
+
426
+ const payload = JSON.parse(body);
427
+ console.log('[Webhook] Entity changed:', payload.event, payload.entityId);
428
+
429
+ // TODO: Handle the event (sync, transform, notify, etc.)
430
+
431
+ return c.json({ received: true });
432
+ });
433
+ `;
434
+ }
435
+ function getApiHealth() {
436
+ return `import { Hono } from 'hono';
437
+
438
+ export const health = new Hono();
439
+
440
+ health.get('/health', (c) => {
441
+ return c.json({ status: 'ok', timestamp: new Date().toISOString() });
442
+ });
443
+ `;
444
+ }
445
+ function getApiPlatform() {
446
+ return `import { createExtensionClient } from '@eide/extension-sdk/server';
447
+
448
+ /**
449
+ * Pre-configured platform client for this extension.
450
+ *
451
+ * Uses env vars for configuration:
452
+ * - PLATFORM_BASE_URL: Platform API base URL
453
+ * - PLATFORM_API_KEY: Project-scoped API key (sk_*)
454
+ * - EXTENSION_KEY: This extension's extension key
455
+ */
456
+ export const platform = createExtensionClient({
457
+ baseUrl: process.env.PLATFORM_BASE_URL || 'http://localhost:4000',
458
+ apiKey: process.env.PLATFORM_API_KEY || '',
459
+ extensionKey: process.env.EXTENSION_KEY || '',
460
+ });
461
+ `;
462
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eide/foir-cli",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
4
4
  "description": "Universal platform CLI for EIDE — scriptable, composable resource management",
5
5
  "type": "module",
6
6
  "publishConfig": {