#!/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`);