TorbatYar/frontend/scripts/scaffold-experience-module.mjs
Mortezakoohjani 0eec9f729b feat(experience): deploy Experience frontend FE-11.0-11.5 portal
Commit completed Torbat Pages admin frontend module, BFF proxy, routes, and validation artifacts for official platform deploy.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-27 21:01:50 +03:30

344 lines
15 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
/**
* Scaffolds Torbat Pages (Experience) frontend module FE-11.011.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 <LoadingState label="در حال بارگذاری تربت پیجز…" />;
}
`
);
w(
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} />;
}
`
);
}
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 <EmptyState title="صفحه یافت نشد" description="مسیر تربت پیجز نامعتبر است." />;
}
`
);
// 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`);