@ahmadubaidillah/cli 1.1.2 → 1.1.4

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 (46) hide show
  1. package/dist/bin.js +59 -17
  2. package/dist/plugins/admin-panel/files/src/modules/admin/components/OverviewChart.tsx +56 -0
  3. package/dist/plugins/admin-panel/files/src/modules/admin/services/admin.service.ts +40 -0
  4. package/dist/plugins/admin-panel/files/src/modules/admin/views/AdminDashboard.tsx +106 -0
  5. package/dist/plugins/admin-panel/plugin.config.json +10 -0
  6. package/dist/plugins/admin-panel/tsconfig.json +12 -0
  7. package/dist/plugins/analytics/files/src/modules/analytics/services/analytics.service.ts +71 -0
  8. package/dist/plugins/analytics/plugin.config.json +8 -0
  9. package/dist/plugins/auth/files/src/modules/auth/auth.schema.ts +21 -0
  10. package/dist/plugins/auth/files/src/modules/auth/db/rls.ts +31 -0
  11. package/dist/plugins/auth/files/src/modules/auth/db/schema.ts +103 -0
  12. package/dist/plugins/auth/files/src/modules/auth/middleware/auth.middleware.ts +12 -0
  13. package/dist/plugins/auth/files/src/modules/auth/middleware/tenant.middleware.ts +50 -0
  14. package/dist/plugins/auth/files/src/modules/auth/routes/auth.routes.ts +40 -0
  15. package/dist/plugins/auth/files/src/modules/auth/services/auth.service.ts +113 -0
  16. package/dist/plugins/auth/plugin.config.json +9 -0
  17. package/dist/plugins/cms/files/src/modules/cms/cms.schema.ts +24 -0
  18. package/dist/plugins/cms/files/src/modules/cms/db/schema.ts +88 -0
  19. package/dist/plugins/cms/files/src/modules/cms/routes/cms.routes.ts +67 -0
  20. package/dist/plugins/cms/files/src/modules/cms/services/cms.service.ts +99 -0
  21. package/dist/plugins/cms/plugin.config.json +9 -0
  22. package/dist/plugins/deployment/files/Dockerfile +33 -0
  23. package/dist/plugins/deployment/files/docker-compose.yml +27 -0
  24. package/dist/plugins/deployment/files/vercel.json +14 -0
  25. package/dist/plugins/deployment/plugin.config.json +5 -0
  26. package/dist/plugins/email/files/src/modules/email/services/email.service.ts +30 -0
  27. package/dist/plugins/email/plugin.config.json +9 -0
  28. package/dist/plugins/file_upload/files/src/modules/storage/services/storage.service.ts +39 -0
  29. package/dist/plugins/file_upload/plugin.config.json +10 -0
  30. package/dist/plugins/github-actions/files/.github/workflows/ci.yml +34 -0
  31. package/dist/plugins/github-actions/plugin.config.json +14 -0
  32. package/dist/plugins/openapi/files/src/modules/openapi/openapi.routes.ts +17 -0
  33. package/dist/plugins/openapi/files/src/modules/openapi/openapi.schema.ts +10 -0
  34. package/dist/plugins/openapi/plugin.config.json +10 -0
  35. package/dist/plugins/payments/files/src/modules/billing/billing.schema.ts +14 -0
  36. package/dist/plugins/payments/files/src/modules/billing/routes/billing.routes.ts +57 -0
  37. package/dist/plugins/payments/files/src/modules/billing/services/stripe.service.ts +47 -0
  38. package/dist/plugins/payments/plugin.config.json +10 -0
  39. package/dist/plugins/queue/files/src/modules/queue/services/queue.service.ts +61 -0
  40. package/dist/plugins/queue/plugin.config.json +10 -0
  41. package/dist/plugins/search/files/src/modules/search/services/search.service.ts +98 -0
  42. package/dist/plugins/search/plugin.config.json +9 -0
  43. package/dist/plugins/websocket/files/src/modules/websocket/services/ws.service.ts +51 -0
  44. package/dist/plugins/websocket/plugin.config.json +7 -0
  45. package/dist/templates/templates/saas/files/package.json +1 -1
  46. package/package.json +2 -2
@@ -0,0 +1,17 @@
1
+ import { OpenAPIHono } from '@hono/zod-openapi';
2
+ import { swaggerUI } from '@hono/swagger-ui';
3
+
4
+ export const openApiApp = new OpenAPIHono();
5
+
6
+ // Generates the OpenAPI JSON schema
7
+ openApiApp.doc('/openapi.json', {
8
+ openapi: '3.0.0',
9
+ info: {
10
+ version: '1.0.0',
11
+ title: 'DevForge Scaffolded API',
12
+ description: 'Auto-generated documentation by the DevForge openapi plugin.',
13
+ },
14
+ });
15
+
16
+ // Swagger UI Route
17
+ openApiApp.get('/docs', swaggerUI({ url: '/openapi.json' }));
@@ -0,0 +1,10 @@
1
+ import { z } from 'zod';
2
+
3
+ // For the plugin's own registry service if we expand it
4
+ export const pluginRegistryItemSchema = z.object({
5
+ name: z.string(),
6
+ version: z.string().regex(/^\d+\.\d+\.\d+$/),
7
+ description: z.string().optional(),
8
+ });
9
+
10
+ export type PluginRegistryItem = z.infer<typeof pluginRegistryItemSchema>;
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "openapi",
3
+ "description": "Auto-Generated OpenAPI / Swagger Documentation plugin.",
4
+ "compatibleTemplates": ["saas", "marketplace", "crm"],
5
+ "packageDependencies": {
6
+ "@hono/swagger-ui": "^0.4.0",
7
+ "@hono/zod-openapi": "^0.15.1"
8
+ },
9
+ "packageDevDependencies": {}
10
+ }
@@ -0,0 +1,14 @@
1
+ import { z } from 'zod';
2
+
3
+ export const checkoutSchema = z.object({
4
+ priceId: z.string().startsWith('price_'),
5
+ success_url: z.string().url().optional(),
6
+ cancel_url: z.string().url().optional(),
7
+ });
8
+
9
+ export const webhookSchema = z.object({
10
+ signature: z.string().min(1),
11
+ });
12
+
13
+ export type CheckoutInput = z.infer<typeof checkoutSchema>;
14
+ export type WebhookInput = z.infer<typeof webhookSchema>;
@@ -0,0 +1,57 @@
1
+ import { Hono } from 'hono';
2
+ import { StripeService } from '../services/stripe.service';
3
+ import { checkoutSchema } from '../billing.schema';
4
+
5
+ export const billingRoutes = new Hono<{
6
+ Variables: {
7
+ organizationId: string;
8
+ }
9
+ }>();
10
+
11
+ // Ensure STIPE_SECRET_KEY is defined in .env
12
+ const stripeService = new StripeService(process.env.STRIPE_SECRET_KEY || 'sk_test_placeholder');
13
+
14
+ billingRoutes.post('/webhook', async (c) => {
15
+ const body = await c.req.text();
16
+ const signature = c.req.header('stripe-signature');
17
+
18
+ if (!signature) {
19
+ return c.json({ error: 'Missing signature' }, 400);
20
+ }
21
+
22
+ // Webhook handling logic here using StripeService
23
+ const whSecret = process.env.STRIPE_WEBHOOK_SECRET || 'whsec_placeholder';
24
+ try {
25
+ const event = await stripeService.handleWebhook(body, signature, whSecret);
26
+ return c.json({ received: true });
27
+ } catch (err: any) {
28
+ return c.json({ error: `Webhook Error: ${err.message}` }, 400);
29
+ }
30
+ });
31
+
32
+ billingRoutes.post('/checkout', async (c) => {
33
+ const body = await c.req.json();
34
+ const result = checkoutSchema.safeParse(body);
35
+ if (!result.success) return c.json({ error: result.error.format() }, 400);
36
+
37
+ const { priceId } = body;
38
+ const organizationId = c.get('organizationId');
39
+ if (!organizationId) {
40
+ return c.json({ error: 'Unauthorized: organizationId is required' }, 401);
41
+ }
42
+
43
+ // Call StripeService to create checkout session linked to the organization
44
+ const session = await stripeService.createCheckoutSession(organizationId, priceId);
45
+ return c.json({ url: session.url });
46
+ });
47
+
48
+ billingRoutes.get('/portal', async (c) => {
49
+ const organizationId = c.get('organizationId');
50
+ if (!organizationId) {
51
+ return c.json({ error: 'Unauthorized: organizationId is required' }, 401);
52
+ }
53
+
54
+ // Call StripeService to create billing portal linked to the organization
55
+ const url = await stripeService.createBillingPortal(organizationId);
56
+ return c.json({ url });
57
+ });
@@ -0,0 +1,47 @@
1
+ import Stripe from 'stripe';
2
+
3
+ export class StripeService {
4
+ private stripe: Stripe;
5
+
6
+ constructor(apiKey: string) {
7
+ this.stripe = new Stripe(apiKey, {
8
+ apiVersion: '2023-10-16' as any,
9
+ });
10
+ }
11
+
12
+ async createCheckoutSession(customerId: string, priceId: string) {
13
+ try {
14
+ return await this.stripe.checkout.sessions.create({
15
+ customer: customerId,
16
+ line_items: [{ price: priceId, quantity: 1 }],
17
+ mode: 'subscription',
18
+ success_url: '{{SUCCESS_URL}}',
19
+ cancel_url: '{{CANCEL_URL}}',
20
+ });
21
+ } catch (error: any) {
22
+ console.error(`[StripeService] Checkout session failed: ${error.message}`);
23
+ throw error;
24
+ }
25
+ }
26
+
27
+ async createBillingPortal(customerId: string) {
28
+ try {
29
+ return await this.stripe.billingPortal.sessions.create({
30
+ customer: customerId,
31
+ return_url: '{{RETURN_URL}}',
32
+ });
33
+ } catch (error: any) {
34
+ console.error(`[StripeService] Billing portal creation failed: ${error.message}`);
35
+ throw error;
36
+ }
37
+ }
38
+
39
+ async handleWebhook(body: string, signature: string, secret: string) {
40
+ try {
41
+ return this.stripe.webhooks.constructEvent(body, signature, secret);
42
+ } catch (error: any) {
43
+ console.error(`[StripeService] Webhook construction failed: ${error.message}`);
44
+ throw error;
45
+ }
46
+ }
47
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "payments",
3
+ "description": "Stripe integration for subscriptions and one-time payments.",
4
+ "compatibleTemplates": ["saas", "marketplace", "booking", "crm"],
5
+ "dependencies": ["auth"],
6
+ "packageDependencies": {
7
+ "stripe": "latest",
8
+ "zod": "^3.22.4"
9
+ }
10
+ }
@@ -0,0 +1,61 @@
1
+ import { Queue, Worker, Job } from 'bullmq';
2
+ import Redis from 'ioredis';
3
+ import { EventBus } from '../../../../../../../core/src/hooks/event-bus';
4
+
5
+ export class QueueService {
6
+ private redis: Redis;
7
+ private queue: Queue;
8
+ private worker: Worker;
9
+
10
+ constructor(private eventBus: EventBus) {
11
+ this.redis = new Redis(process.env.REDIS_URL || 'redis://localhost:6379');
12
+
13
+ // Define the default queue for background tasks
14
+ this.queue = new Queue('default-queue', { connection: this.redis as any });
15
+
16
+ // Initialize the background worker
17
+ this.worker = new Worker('default-queue', async (job: Job) => {
18
+ console.log(`Processing job ${job.id} of type ${job.name} with data`, job.data);
19
+
20
+ // If the job type is an event bridge trigger
21
+ if (job.name === 'trigger_event') {
22
+ this.eventBus.dispatch(job.data.eventName, job.data.payload);
23
+ }
24
+
25
+ // Add more specific job processors here as needed
26
+ }, { connection: this.redis as any });
27
+
28
+ this.worker.on('completed', (job: Job) => {
29
+ console.log(`Job ${job.id} has completed successfully.`);
30
+ });
31
+
32
+ this.worker.on('failed', (job: Job | undefined, err: Error) => {
33
+ console.log(`Job ${job?.id} has failed with error: ${err.message}`);
34
+ });
35
+ }
36
+
37
+ /**
38
+ * Enqueue a job to be processed in the background
39
+ */
40
+ async addJob(name: string, data: any, opts?: any) {
41
+ try {
42
+ return await this.queue.add(name, data, opts);
43
+ } catch (error: any) {
44
+ console.error(`[QueueService] Failed to add job ${name}: ${error.message}`);
45
+ throw error;
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Gracefully close connections
51
+ */
52
+ async close() {
53
+ try {
54
+ await this.worker.close();
55
+ await this.queue.close();
56
+ await this.redis.quit();
57
+ } catch (error: any) {
58
+ console.error(`[QueueService] Failed to close connections: ${error.message}`);
59
+ }
60
+ }
61
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "queue",
3
+ "description": "Background Jobs and Task Queue powered by BullMQ and Redis.",
4
+ "compatibleTemplates": ["saas", "marketplace", "crm"],
5
+ "packageDependencies": {
6
+ "bullmq": "^5.7.0",
7
+ "ioredis": "^5.3.0"
8
+ },
9
+ "packageDevDependencies": {}
10
+ }
@@ -0,0 +1,98 @@
1
+ import { MeiliSearch } from 'meilisearch';
2
+
3
+ export interface SearchDriver {
4
+ search(index: string, query: string, options?: any): Promise<any>;
5
+ addDocuments(index: string, documents: any[]): Promise<any>;
6
+ deleteDocuments(index: string, documentIds: string[] | number[]): Promise<any>;
7
+ }
8
+
9
+ export class MeiliSearchDriver implements SearchDriver {
10
+ private client: MeiliSearch;
11
+
12
+ constructor(config: { host: string; apiKey: string }) {
13
+ this.client = new MeiliSearch(config);
14
+ }
15
+
16
+ async search(index: string, query: string, options: any = {}) {
17
+ try {
18
+ return await this.client.index(index).search(query, options);
19
+ } catch (error: any) {
20
+ console.error(`[MeiliSearchDriver] Search failed for index ${index}: ${error.message}`);
21
+ throw error;
22
+ }
23
+ }
24
+
25
+ async addDocuments(index: string, documents: any[]) {
26
+ try {
27
+ return await this.client.index(index).addDocuments(documents);
28
+ } catch (error: any) {
29
+ console.error(`[MeiliSearchDriver] Add documents failed for index ${index}: ${error.message}`);
30
+ throw error;
31
+ }
32
+ }
33
+
34
+ async deleteDocuments(index: string, documentIds: string[] | number[]) {
35
+ try {
36
+ return await this.client.index(index).deleteDocuments(documentIds);
37
+ } catch (error: any) {
38
+ console.error(`[MeiliSearchDriver] Delete documents failed for index ${index}: ${error.message}`);
39
+ throw error;
40
+ }
41
+ }
42
+ }
43
+
44
+ /**
45
+ * PostgresSearchDriver
46
+ * Simple fallback using basic SQL pattern matching.
47
+ */
48
+ export class PostgresSearchDriver implements SearchDriver {
49
+ constructor(private db: any) {}
50
+
51
+ async search(index: string, query: string, _options: any = {}) {
52
+ // In a real implementation, this would use Drizzle's ilike or tsvector
53
+ // For the template, we provide the architectural skeleton
54
+ console.warn(`Postgres Search fallback for index: ${index}`);
55
+ return {
56
+ hits: [],
57
+ query,
58
+ fallback: true
59
+ };
60
+ }
61
+
62
+ async addDocuments(_index: string, _documents: any[]) {
63
+ // No-op for the simple driver, or sync to table if needed
64
+ return { success: true };
65
+ }
66
+
67
+ async deleteDocuments(_index: string, _documentIds: any[]) {
68
+ return { success: true };
69
+ }
70
+ }
71
+
72
+ export class SearchService {
73
+ private driver: SearchDriver;
74
+
75
+ constructor(config: {
76
+ type: 'meilisearch' | 'postgres',
77
+ meiliConfig?: { host: string; apiKey: string },
78
+ db?: any
79
+ }) {
80
+ if (config.type === 'meilisearch' && config.meiliConfig) {
81
+ this.driver = new MeiliSearchDriver(config.meiliConfig);
82
+ } else {
83
+ this.driver = new PostgresSearchDriver(config.db);
84
+ }
85
+ }
86
+
87
+ async search(index: string, query: string, options: any = {}) {
88
+ return await this.driver.search(index, query, options);
89
+ }
90
+
91
+ async addDocuments(index: string, documents: any[]) {
92
+ return await this.driver.addDocuments(index, documents);
93
+ }
94
+
95
+ async deleteDocuments(index: string, documentIds: any[]) {
96
+ return await this.driver.deleteDocuments(index, documentIds);
97
+ }
98
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "search",
3
+ "description": "Full-text search abstraction with support for PostgreSQL and MeiliSearch.",
4
+ "compatibleTemplates": ["saas", "marketplace", "cms"],
5
+ "packageDependencies": {
6
+ "meilisearch": "latest",
7
+ "zod": "^3.22.4"
8
+ }
9
+ }
@@ -0,0 +1,51 @@
1
+ import { EventBus } from '../../../../../../../core/src/hooks/event-bus';
2
+
3
+ export class WebSocketService {
4
+ private clients: Set<any>;
5
+
6
+ constructor(private eventBus: EventBus) {
7
+ this.clients = new Set();
8
+
9
+ // Listen to all public events that should trigger a frontend notification
10
+ this.eventBus.on('notification.broadcast', (payload: any) => {
11
+ this.broadcast(payload);
12
+ });
13
+
14
+ // Listen to tenant-specific events
15
+ this.eventBus.on('tenant.event', (payload: any) => {
16
+ // Logic to filter clients by orgId can be implemented here
17
+ this.broadcast(payload);
18
+ });
19
+ }
20
+
21
+ /**
22
+ * Registers a connected socket
23
+ */
24
+ addClient(ws: any) {
25
+ this.clients.add(ws);
26
+ }
27
+
28
+ /**
29
+ * Unregisters a disconnected socket
30
+ */
31
+ removeClient(ws: any) {
32
+ this.clients.delete(ws);
33
+ }
34
+
35
+ /**
36
+ * Broadcasts a message to all connected clients
37
+ */
38
+ broadcast(message: any) {
39
+ const data = JSON.stringify(message);
40
+ for (const client of this.clients) {
41
+ // Verify the connection is still open before sending
42
+ if (typeof client.send === 'function') {
43
+ try {
44
+ client.send(data);
45
+ } catch (err) {
46
+ console.error('Failed to send message to client', err);
47
+ }
48
+ }
49
+ }
50
+ }
51
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "websocket",
3
+ "description": "Real-time WebSockets and Notifications.",
4
+ "compatibleTemplates": ["saas", "marketplace", "crm"],
5
+ "packageDependencies": {},
6
+ "packageDevDependencies": {}
7
+ }
@@ -15,7 +15,7 @@
15
15
  "zod": "latest",
16
16
  "drizzle-orm": "latest",
17
17
  "postgres": "latest",
18
- "@better-auth/hono": "latest"
18
+ "better-auth": "latest"
19
19
  },
20
20
  "devDependencies": {
21
21
  "drizzle-kit": "latest",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ahmadubaidillah/cli",
3
- "version": "1.1.2",
3
+ "version": "1.1.4",
4
4
  "description": "The elite modular boilerplate engine for agentic applications.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -16,7 +16,7 @@
16
16
  "devforge-cli": "dist/bin.js"
17
17
  },
18
18
  "dependencies": {
19
- "@ahmadubaidillah/core": "^1.1.2",
19
+ "@ahmadubaidillah/core": "^1.1.4",
20
20
  "@inquirer/prompts": "latest",
21
21
  "chalk": "latest",
22
22
  "commander": "latest",