TorbatYar/frontend/scripts/scaffold-communication-module.mjs
Mortezakoohjani 10c3c43a75 feat(communication): add SMS MVP frontend with governance artifacts.
Ship the full Communication portal (real SMS API, feature-locked future channels), BFF proxy, docs, snapshot, and phase handover.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-27 11:11:25 +03:30

161 lines
5.9 KiB
JavaScript

#!/usr/bin/env node
/** Scaffolds communication app routes + locked feature pages */
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), "..");
const APP = path.join(ROOT, "app", "communication");
const FEATURES = path.join(ROOT, "modules", "communication", "features");
const lockKeyMap = {
email: "email",
whatsapp: "whatsapp",
telegram: "telegram",
rubika: "rubika",
push: "push",
voice: "voice",
video: "video",
liveChat: "live_chat",
omnichannelInbox: "omnichannel_inbox",
marketingAutomation: "marketing_automation",
automationTriggers: "automation_triggers",
journeyBuilder: "journey_builder",
aiCampaigns: "ai_campaigns",
webhookCenter: "webhook_center",
connectors: "connectors",
conversations: "conversations",
campaignOrchestrator: "campaign_orchestrator",
notificationCenter: "notification_center",
templateCategories: "template_categories",
roles: "roles",
audit: "audit",
apiKeys: "api_keys",
integrationsHub: "integrations_hub",
};
const PAGES = [
["", "ExecutiveDashboard", "executiveDashboard"],
["dashboard", "CommunicationDashboard", "dashboard"],
["providers/dashboard", "ProviderDashboard", "providerDashboard"],
["sms", "SmsDashboard", "smsDashboard"],
["sms/providers", "ProvidersPage", "providers"],
["sms/templates", "TemplatesPage", "templates"],
["sms/template-categories", "TemplateCategoriesPage", "templateCategories"],
["sms/senders", "SendersPage", "senders"],
["sms/campaigns", "CampaignsPage", "campaigns"],
["sms/send", "SendMessagePage", "sendMessage"],
["sms/queue", "QueuePage", "queue"],
["sms/delivery-reports", "DeliveryReportsPage", "deliveryReports"],
["sms/messages", "MessagesPage", "messages"],
["contacts/recipients", "RecipientsPage", "recipients"],
["contacts/sources", "ContactSourcesPage", "contactSources"],
["contacts/address-books", "AddressBooksPage", "recipients"],
["email", "EmailPage", "email"],
["whatsapp", "WhatsappPage", "whatsapp"],
["telegram", "TelegramPage", "telegram"],
["rubika", "RubikaPage", "rubika"],
["push", "PushPage", "push"],
["voice", "VoicePage", "voice"],
["video", "VideoPage", "video"],
["live-chat", "LiveChatPage", "liveChat"],
["inbox", "OmnichannelInboxPage", "omnichannelInbox"],
["automation", "MarketingAutomationPage", "marketingAutomation"],
["automation/triggers", "AutomationTriggersPage", "automationTriggers"],
["journey-builder", "JourneyBuilderPage", "journeyBuilder"],
["ai-campaigns", "AiCampaignsPage", "aiCampaigns"],
["webhooks", "WebhookCenterPage", "webhookCenter"],
["connectors", "ConnectorsPage", "connectors"],
["conversations", "ConversationsPage", "conversations"],
["campaign-orchestrator", "CampaignOrchestratorPage", "campaignOrchestrator"],
["notifications", "NotificationCenterPage", "notificationCenter"],
["monitoring", "MonitoringPage", "monitoring"],
["analytics", "AnalyticsPage", "monitoring"],
["reports", "ReportsPage", "monitoring"],
["health", "HealthPage", "health"],
["capabilities", "CapabilitiesPage", "capabilities"],
["settings", "SettingsPage", "settings"],
["permissions", "PermissionsPage", "permissions"],
["roles", "RolesPage", "roles"],
["audit", "AuditPage", "audit"],
["api-keys", "ApiKeysPage", "apiKeys"],
["integrations", "IntegrationsHubPage", "integrationsHub"],
["otp", "OtpPage", "otp"],
];
function ensureDir(d) {
fs.mkdirSync(d, { recursive: true });
}
function writeLockedFeature(featureFile, lockKey, exportName) {
const file = path.join(FEATURES, `${featureFile}.tsx`);
if (fs.existsSync(file)) return;
fs.writeFileSync(
file,
`import { createCommunicationLockedPage } from "@/modules/communication/components/CommunicationFeatureLock";
import { FUTURE_MODULE_REGISTRY } from "@/modules/communication/constants/featureRegistry";
const config = FUTURE_MODULE_REGISTRY.${lockKey}!;
export const ${exportName} = createCommunicationLockedPage(config);
export default ${exportName};
`
);
}
function writeRoute(routePath, exportName, featureFile) {
const dir = path.join(APP, routePath);
ensureDir(dir);
fs.writeFileSync(
path.join(dir, "page.tsx"),
`"use client";
export { ${exportName} as default } from "@/modules/communication/features/${featureFile}";
`
);
fs.writeFileSync(
path.join(dir, "loading.tsx"),
`import { LoadingState } from "@/components/ds";
export default function Loading() { return <LoadingState label="بارگذاری…" />; }
`
);
fs.writeFileSync(
path.join(dir, "error.tsx"),
`"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}
`
);
}
ensureDir(APP);
fs.writeFileSync(
path.join(APP, "layout.tsx"),
`"use client";
import { CommunicationRootLayout } from "@/modules/communication/components/createPortalLayout";
export default CommunicationRootLayout;
`
);
fs.writeFileSync(
path.join(APP, "not-found.tsx"),
`import Link from "next/link";
import { EmptyState, Button } from "@/components/ds";
export default function NotFound() {
return (
<EmptyState title="صفحه یافت نشد" description="مسیر ارتباطات نامعتبر است." action={<Link href="/communication"><Button>داشبورد</Button></Link>} />
);
}
`
);
for (const [file, key] of Object.entries(lockKeyMap)) {
const exportName = file.charAt(0).toUpperCase() + file.slice(1) + "Page";
writeLockedFeature(file, key, exportName);
}
for (const [route, exportName, featureFile] of PAGES) {
writeRoute(route, exportName, featureFile);
}
console.log(`Scaffolded ${PAGES.length} communication routes`);