#!/usr/bin/env node /** * Scaffolds Torbat Pages (Experience) frontend module FE-11.0–11.5 */ 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 MOD = path.join(ROOT, "modules", "experience"); const APP = path.join(ROOT, "app", "experience"); const FEATURES = path.join(MOD, "features"); const PAGE_REGISTRY = [ ["", "ExecutiveDashboard", "dashboard"], ["hub", "ExperienceHub", "hub"], ["workspaces", "WorkspacesPage", "workspaces"], ["sites", "SitesPage", "sites"], ["pages", "PagesPage", "pages"], ["site-domains", "SiteDomainsPage", "siteDomains"], ["templates", "TemplatesPage", "templates"], ["template-versions", "TemplateVersionsPage", "templateVersions"], ["template-instantiations", "TemplateInstantiationsPage", "templateInstantiations"], ["publish-targets", "PublishTargetsPage", "publishTargets"], ["components", "ComponentsPage", "components"], ["component-versions", "ComponentVersionsPage", "componentVersions"], ["themes", "ThemesPage", "themes"], ["theme-versions", "ThemeVersionsPage", "themeVersions"], ["layouts", "LayoutsPage", "layouts"], ["layout-versions", "LayoutVersionsPage", "layoutVersions"], ["site-theme-bindings", "SiteThemeBindingsPage", "siteThemeBindings"], ["page-layout-bindings", "PageLayoutBindingsPage", "pageLayoutBindings"], ["localizations", "LocalizationsPage", "localizations"], ["media-refs", "MediaRefsPage", "mediaRefs"], ["forms", "FormsPage", "forms"], ["form-submissions", "FormSubmissionsPage", "formSubmissions"], ["surveys", "SurveysPage", "surveys"], ["survey-responses", "SurveyResponsesPage", "surveyResponses"], ["publish-releases", "PublishReleasesPage", "publishReleases"], ["domain-edge-bindings", "DomainEdgeBindingsPage", "domainEdgeBindings"], ["seo-metadata", "SeoMetadataPage", "seoMetadata"], ["sitemap-shells", "SitemapShellsPage", "sitemapShells"], ["robots-shells", "RobotsShellsPage", "robotsShells"], ["pwa-manifests", "PwaManifestsPage", "pwaManifests"], ["analytics", "AnalyticsDashboardPage", "analyticsDashboard"], ["analytics-report-definitions", "AnalyticsReportDefinitionsPage", "analyticsReportDefinitions"], ["analytics-snapshots", "AnalyticsSnapshotsPage", "analyticsSnapshots"], ["embed", "EmbedPreviewPage", "embedPreview"], ["widget-definitions", "WidgetDefinitionsPage", "widgetDefinitions"], ["widget-instances", "WidgetInstancesPage", "widgetInstances"], ["published-actions", "PublishedActionsPage", "publishedActions"], ["locale-profiles", "LocaleProfilesPage", "localeProfiles"], ["external-providers", "ExternalProvidersPage", "externalProviders"], ["render-engines", "RenderEnginesPage", "renderEngines"], ["configurations", "ConfigurationsPage", "configurations"], ["settings", "SettingsPage", "settings"], ["audit", "AuditLogsPage", "audit"], ["health", "HealthPage", "health"], ["capabilities", "CapabilitiesPage", "capabilities"], ]; const DYNAMIC_ROUTES = [ ["pages/[id]/builder", "PageBuilderPage", "pageBuilder"], ["forms/[id]/builder", "FormBuilderPage", "formBuilder"], ["surveys/[id]/builder", "SurveyBuilderPage", "surveyBuilder"], ["preview/[type]/[id]", "PublicPreviewPage", "publicPreview"], ]; function ensureDir(d) { fs.mkdirSync(d, { recursive: true }); } function w(filePath, content) { ensureDir(path.dirname(filePath)); fs.writeFileSync(filePath, content); } const listPage = (exportName, apiKey, title, perm, extra = "") => `"use client"; import { createExperienceListPage } from "@/modules/experience/components/ExperienceListCrudPage"; import { experienceApi } from "@/modules/experience/services/experience-api"; export const ${exportName} = createExperienceListPage({ title: "${title}", breadcrumb: "${title}", resourceKey: "${apiKey}", permission: "${perm}", api: experienceApi.${apiKey}, columns: [ { key: "code", header: "کد" }, { key: "name", header: "نام" }, { key: "status", header: "وضعیت", type: "status" }, { key: "created_at", header: "ایجاد", type: "datetime" }, ], createFields: [ { name: "code", label: "کد", required: true }, { name: "name", label: "نام", required: true }, { name: "status", label: "وضعیت", type: "select", options: [ { value: "draft", label: "پیش‌نویس" }, { value: "active", label: "فعال" }, ]}, ], ${extra} }); export default ${exportName}; `; // Generate list feature pages const listPages = { workspaces: listPage("WorkspacesPage", "workspaces", "فضاهای کاری", "experience.workspaces.view"), sites: listPage("SitesPage", "sites", "سایت‌ها", "experience.sites.view"), pages: `"use client"; import { createExperienceListPage } from "@/modules/experience/components/ExperienceListCrudPage"; import { experienceApi } from "@/modules/experience/services/experience-api"; export const PagesPage = createExperienceListPage({ title: "صفحات", breadcrumb: "صفحات", resourceKey: "pages", permission: "experience.pages.view", api: experienceApi.pages, columns: [ { key: "title", header: "عنوان" }, { key: "slug", header: "اسلاگ" }, { key: "page_type", header: "نوع" }, { key: "publish_status", header: "انتشار", type: "status" }, { key: "created_at", header: "ایجاد", type: "datetime" }, ], createFields: [ { name: "code", label: "کد", required: true }, { name: "title", label: "عنوان", required: true }, { name: "slug", label: "اسلاگ", required: true }, { name: "page_type", label: "نوع", type: "select", options: [ { value: "landing", label: "لندینگ" }, { value: "content", label: "محتوا" }, ]}, ], detailHref: (row) => row.id ? \`/experience/pages/\${row.id}/builder\` : null, }); export default PagesPage; `, siteDomains: listPage("SiteDomainsPage", "siteDomains", "دامنه‌های سایت", "experience.domains.view"), templates: listPage("TemplatesPage", "templates", "قالب‌ها", "experience.templates.view"), templateVersions: listPage("TemplateVersionsPage", "templateVersions", "نسخه‌های قالب", "experience.templates.view"), templateInstantiations: listPage("TemplateInstantiationsPage", "templateInstantiations", "نمونه‌سازی قالب", "experience.templates.view", ` readOnly: true,\n`), components: listPage("ComponentsPage", "components", "کامپوننت‌ها", "experience.components.view"), componentVersions: listPage("ComponentVersionsPage", "componentVersions", "نسخه‌های کامپوننت", "experience.components.view"), themes: listPage("ThemesPage", "themes", "تم‌ها", "experience.themes.view"), themeVersions: listPage("ThemeVersionsPage", "themeVersions", "نسخه‌های تم", "experience.themes.view"), layouts: listPage("LayoutsPage", "layouts", "چیدمان‌ها", "experience.layouts.view"), layoutVersions: listPage("LayoutVersionsPage", "layoutVersions", "نسخه‌های چیدمان", "experience.layouts.view"), siteThemeBindings: listPage("SiteThemeBindingsPage", "siteThemeBindings", "اتصال تم سایت", "experience.themes.view", ` readOnly: true,\n`), pageLayoutBindings: listPage("PageLayoutBindingsPage", "pageLayoutBindings", "اتصال چیدمان صفحه", "experience.layouts.view", ` readOnly: true,\n`), localizations: listPage("LocalizationsPage", "localizations", "بومی‌سازی", "experience.localization.view"), mediaRefs: listPage("MediaRefsPage", "mediaRefs", "رسانه‌ها", "experience.media.view"), forms: `"use client"; import { createExperienceListPage } from "@/modules/experience/components/ExperienceListCrudPage"; import { experienceApi } from "@/modules/experience/services/experience-api"; export const FormsPage = createExperienceListPage({ title: "فرم‌ها", breadcrumb: "فرم‌ها", resourceKey: "forms", permission: "experience.forms.view", api: experienceApi.forms, columns: [ { key: "code", header: "کد" }, { key: "name", header: "نام" }, { key: "status", header: "وضعیت", type: "status" }, { key: "created_at", header: "ایجاد", type: "datetime" }, ], createFields: [ { name: "code", label: "کد", required: true }, { name: "name", label: "نام", required: true }, ], detailHref: (row) => row.id ? \`/experience/forms/\${row.id}/builder\` : null, }); export default FormsPage; `, formSubmissions: listPage("FormSubmissionsPage", "formSubmissions", "ارسال‌های فرم", "experience.forms.view", ` readOnly: true,\n`), surveys: `"use client"; import { createExperienceListPage } from "@/modules/experience/components/ExperienceListCrudPage"; import { experienceApi } from "@/modules/experience/services/experience-api"; export const SurveysPage = createExperienceListPage({ title: "نظرسنجی‌ها", breadcrumb: "نظرسنجی‌ها", resourceKey: "surveys", permission: "experience.surveys.view", api: experienceApi.surveys, columns: [ { key: "code", header: "کد" }, { key: "name", header: "نام" }, { key: "status", header: "وضعیت", type: "status" }, { key: "created_at", header: "ایجاد", type: "datetime" }, ], createFields: [ { name: "code", label: "کد", required: true }, { name: "name", label: "نام", required: true }, ], detailHref: (row) => row.id ? \`/experience/surveys/\${row.id}/builder\` : null, }); export default SurveysPage; `, surveyResponses: listPage("SurveyResponsesPage", "surveyResponses", "پاسخ‌های نظرسنجی", "experience.surveys.view", ` readOnly: true,\n`), publishReleases: listPage("PublishReleasesPage", "publishReleases", "انتشارها", "experience.publishing.view"), domainEdgeBindings: listPage("DomainEdgeBindingsPage", "domainEdgeBindings", "اتصال Edge دامنه", "experience.publishing.view"), seoMetadata: listPage("SeoMetadataPage", "seoMetadata", "متاداده SEO", "experience.seo.view"), sitemapShells: listPage("SitemapShellsPage", "sitemapShells", "Sitemap", "experience.seo.view"), robotsShells: listPage("RobotsShellsPage", "robotsShells", "Robots.txt", "experience.seo.view"), pwaManifests: listPage("PwaManifestsPage", "pwaManifests", "PWA Manifest", "experience.publishing.view"), analyticsReportDefinitions: listPage("AnalyticsReportDefinitionsPage", "analyticsReportDefinitions", "تعریف گزارش", "experience.analytics.view"), analyticsSnapshots: listPage("AnalyticsSnapshotsPage", "analyticsSnapshots", "اسنپ‌شات آنالیتیکس", "experience.analytics.view"), widgetDefinitions: listPage("WidgetDefinitionsPage", "widgetDefinitions", "تعریف ویجت", "experience.widgets.view"), widgetInstances: listPage("WidgetInstancesPage", "widgetInstances", "نمونه ویجت", "experience.widgets.view"), localeProfiles: listPage("LocaleProfilesPage", "localeProfiles", "پروفایل زبان", "experience.locale_profiles.view"), externalProviders: listPage("ExternalProvidersPage", "externalProviders", "ارائه‌دهندگان خارجی", "experience.external_providers.view"), renderEngines: listPage("RenderEnginesPage", "renderEngines", "موتورهای رندر", "experience.render_engines.view"), configurations: listPage("ConfigurationsPage", "configurations", "پیکربندی", "experience.configurations.view"), }; for (const [name, content] of Object.entries(listPages)) { w(path.join(FEATURES, `${name}.tsx`), content); } // Thin app routes function writeAppRoute(routePath, exportName, featureFile) { const dir = path.join(APP, routePath); ensureDir(dir); w( path.join(dir, "page.tsx"), `"use client"; export { ${exportName} as default } from "@/modules/experience/features/${featureFile}"; ` ); w( path.join(dir, "loading.tsx"), `import { LoadingState } from "@/components/ds"; export default function Loading() { return ; } ` ); w( path.join(dir, "error.tsx"), `"use client"; import { ErrorState } from "@/components/ds"; export default function Error({ error, reset }: { error: Error; reset: () => void }) { return ; } ` ); } for (const [routePath, exportName, featureFile] of PAGE_REGISTRY) { writeAppRoute(routePath, exportName, featureFile); } for (const [routePath, exportName, featureFile] of DYNAMIC_ROUTES) { writeAppRoute(routePath, exportName, featureFile); } // Layout files w( path.join(APP, "layout.tsx"), `export { ExperienceRootLayout as default } from "@/modules/experience/components/createPortalLayout"; ` ); w( path.join(APP, "not-found.tsx"), `import { EmptyState } from "@/components/ds"; export default function NotFound() { return ; } ` ); // BFF w( path.join(ROOT, "app", "api", "experience", "[...path]", "route.ts"), `import { NextRequest, NextResponse } from "next/server"; const UPSTREAM = process.env.EXPERIENCE_SERVICE_URL || process.env.NEXT_PUBLIC_EXPERIENCE_API_URL || "http://localhost:8008"; async function proxy(req: NextRequest, pathSegments: string[]) { const p = pathSegments.join("/"); const url = new URL(req.url); const target = \`\${UPSTREAM.replace(/\\/$/, "")}/\${p}\${url.search}\`; const headers = new Headers(); for (const h of ["authorization", "content-type", "x-tenant-id", "accept"]) { const v = req.headers.get(h); if (v) headers.set(h, v); } let body: ArrayBuffer | undefined; if (req.method !== "GET" && req.method !== "HEAD") body = await req.arrayBuffer(); try { const res = await fetch(target, { method: req.method, headers, body, cache: "no-store" }); const out = new Headers(); const ct = res.headers.get("content-type"); if (ct) out.set("content-type", ct); return new NextResponse(await res.arrayBuffer(), { status: res.status, headers: out }); } catch (err) { const detail = err instanceof Error ? err.message : "upstream_unreachable"; return NextResponse.json( { error: { code: "experience_proxy_error", message: \`پروکسی تربت پیجز — \${detail}\` } }, { status: 502 } ); } } export async function GET(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) { const { path: p } = await ctx.params; return proxy(req, p); } export async function POST(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) { const { path: p } = await ctx.params; return proxy(req, p); } export async function PATCH(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) { const { path: p } = await ctx.params; return proxy(req, p); } export async function PUT(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) { const { path: p } = await ctx.params; return proxy(req, p); } export async function DELETE(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) { const { path: p } = await ctx.params; return proxy(req, p); } ` ); console.log(`Scaffolded ${PAGE_REGISTRY.length + DYNAMIC_ROUTES.length} routes + BFF`);