//go:build ignore package main import ( "bytes" "encoding/json" "fmt" "go/format" "os" "os/exec" "path/filepath" "sort" "strings" "github.com/rcourtman/pulse-go-rewrite/pkg/licensing" ) type generatedFeatureEntry struct { Key string `json:"key"` DisplayName string `json:"displayName"` ComparisonName string `json:"comparisonName"` ShowInComparisonTable bool `json:"showInComparisonTable"` DisplayableInSelfHostedPlan bool `json:"displayableInSelfHostedPlan"` Roles generatedFeatureRoles `json:"roles"` IncludedIn generatedFeatureTierInclusion `json:"includedIn"` } type generatedFeatureRoles struct { Community licensing.SelfHostedFeatureRole `json:"community"` Relay licensing.SelfHostedFeatureRole `json:"relay"` Pro licensing.SelfHostedFeatureRole `json:"pro"` } type generatedFeatureTierInclusion struct { Community bool `json:"community"` Relay bool `json:"relay"` Pro bool `json:"pro"` } func main() { repoRoot, err := os.Getwd() if err != nil { fail(err) } entries := buildGeneratedEntries() tierFeatures := map[string][]string{ "community": append([]string(nil), licensing.TierFeatures[licensing.TierFree]...), "relay": append([]string(nil), licensing.TierFeatures[licensing.TierRelay]...), "pro": append([]string(nil), licensing.TierFeatures[licensing.TierPro]...), } if err := writeFrontendModule(filepath.Join(repoRoot, "frontend-modern"), entries); err != nil { fail(err) } pulseProRoot := filepath.Clean(filepath.Join(repoRoot, "..", "pulse-pro")) if _, err := os.Stat(pulseProRoot); err == nil { if err := writePulseProModule(filepath.Join(pulseProRoot, "license-server/self_hosted_feature_catalog.generated.go"), entries, tierFeatures); err != nil { fail(err) } } } func buildGeneratedEntries() []generatedFeatureEntry { metadata := licensing.AllFeatureMetadata() entries := make([]generatedFeatureEntry, 0, len(metadata)) for _, entry := range metadata { roles := generatedFeatureRoles{ Community: entry.SelfHostedRoles.Community, Relay: entry.SelfHostedRoles.Relay, Pro: entry.SelfHostedRoles.Pro, } included := generatedFeatureTierInclusion{ Community: licensing.TierHasFeature(licensing.TierFree, entry.Key), Relay: licensing.TierHasFeature(licensing.TierRelay, entry.Key), Pro: licensing.TierHasFeature(licensing.TierPro, entry.Key), } entries = append(entries, generatedFeatureEntry{ Key: entry.Key, DisplayName: entry.DisplayName, ComparisonName: entry.ComparisonName, ShowInComparisonTable: entry.ShowInComparisonTable, DisplayableInSelfHostedPlan: entry.DisplayableInPlanUI, Roles: roles, IncludedIn: included, }) } return entries } func writeFrontendModule(frontendDir string, entries []generatedFeatureEntry) error { path := filepath.Join(frontendDir, "src/utils/selfHostedFeatureCatalog.generated.ts") payload, err := json.MarshalIndent(entries, "", " ") if err != nil { return err } var buf bytes.Buffer buf.WriteString("// This file is generated by scripts/generate-self-hosted-feature-catalog.go.\n") buf.WriteString("// Do not edit it manually.\n\n") buf.WriteString("export type SelfHostedFeatureRole =\n") buf.WriteString(" | 'hidden'\n") buf.WriteString(" | 'included'\n") buf.WriteString(" | 'primary_pillar'\n") buf.WriteString(" | 'included_extra'\n") buf.WriteString(" | 'compatibility_only';\n\n") buf.WriteString("export interface GeneratedSelfHostedFeatureCatalogEntry {\n") buf.WriteString(" key: string;\n") buf.WriteString(" displayName: string;\n") buf.WriteString(" comparisonName: string;\n") buf.WriteString(" showInComparisonTable: boolean;\n") buf.WriteString(" displayableInSelfHostedPlan: boolean;\n") buf.WriteString(" roles: {\n") buf.WriteString(" community: SelfHostedFeatureRole;\n") buf.WriteString(" relay: SelfHostedFeatureRole;\n") buf.WriteString(" pro: SelfHostedFeatureRole;\n") buf.WriteString(" };\n") buf.WriteString(" includedIn: {\n") buf.WriteString(" community: boolean;\n") buf.WriteString(" relay: boolean;\n") buf.WriteString(" pro: boolean;\n") buf.WriteString(" };\n") buf.WriteString("}\n\n") buf.WriteString("export const SELF_HOSTED_FEATURE_CATALOG: readonly GeneratedSelfHostedFeatureCatalogEntry[] = ") buf.Write(payload) buf.WriteString(" as const;\n\n") buf.WriteString("export const SELF_HOSTED_FEATURE_CATALOG_BY_KEY: Record = Object.fromEntries(\n") buf.WriteString(" SELF_HOSTED_FEATURE_CATALOG.map((entry) => [entry.key, entry]),\n") buf.WriteString(") as Record;\n\n") buf.WriteString("export function getSelfHostedFeatureCatalogEntry(\n") buf.WriteString(" key?: string | null,\n") buf.WriteString("): GeneratedSelfHostedFeatureCatalogEntry | null {\n") buf.WriteString(" const normalized = (key || '').trim();\n") buf.WriteString(" if (!normalized) {\n") buf.WriteString(" return null;\n") buf.WriteString(" }\n") buf.WriteString(" return SELF_HOSTED_FEATURE_CATALOG_BY_KEY[normalized] || null;\n") buf.WriteString("}\n\n") buf.WriteString("export function isDisplayableSelfHostedFeatureKey(key?: string | null): boolean {\n") buf.WriteString(" const entry = getSelfHostedFeatureCatalogEntry(key);\n") buf.WriteString(" if (!entry) {\n") buf.WriteString(" return true;\n") buf.WriteString(" }\n") buf.WriteString(" return entry.displayableInSelfHostedPlan;\n") buf.WriteString("}\n\n") buf.WriteString("export function getSelfHostedFeaturesForRole(\n") buf.WriteString(" tier: 'community' | 'relay' | 'pro',\n") buf.WriteString(" role: SelfHostedFeatureRole,\n") buf.WriteString("): readonly GeneratedSelfHostedFeatureCatalogEntry[] {\n") buf.WriteString(" return SELF_HOSTED_FEATURE_CATALOG.filter((entry) => entry.roles[tier] === role);\n") buf.WriteString("}\n") if err := os.WriteFile(path, buf.Bytes(), 0o644); err != nil { return err } return formatWithPrettier(frontendDir, path) } // formatWithPrettier rewrites the generated module with the frontend's own // prettier setup so generator runs stay byte-identical to the committed, // prettier-formatted file. func formatWithPrettier(frontendDir, path string) error { prettier := filepath.Join(frontendDir, "node_modules", ".bin", "prettier") var cmd *exec.Cmd if _, err := os.Stat(prettier); err == nil { cmd = exec.Command(prettier, "--write", path) } else { cmd = exec.Command("npx", "--yes", "prettier", "--write", path) } cmd.Dir = frontendDir cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { return fmt.Errorf("prettier %s: %w", path, err) } return nil } func writePulseProModule(path string, entries []generatedFeatureEntry, tierFeatures map[string][]string) error { var buf bytes.Buffer buf.WriteString("// Code generated by ../pulse/scripts/generate-self-hosted-feature-catalog.go; DO NOT EDIT.\n") buf.WriteString("package main\n\n") buf.WriteString("import \"strings\"\n\n") buf.WriteString("type selfHostedFeatureRole string\n\n") buf.WriteString("const (\n") buf.WriteString("\tselfHostedFeatureRoleHidden selfHostedFeatureRole = \"hidden\"\n") buf.WriteString("\tselfHostedFeatureRoleIncluded selfHostedFeatureRole = \"included\"\n") buf.WriteString("\tselfHostedFeatureRolePrimaryPillar selfHostedFeatureRole = \"primary_pillar\"\n") buf.WriteString("\tselfHostedFeatureRoleIncludedExtra selfHostedFeatureRole = \"included_extra\"\n") buf.WriteString("\tselfHostedFeatureRoleCompatibilityOnly selfHostedFeatureRole = \"compatibility_only\"\n") buf.WriteString(")\n\n") buf.WriteString("type selfHostedFeatureCatalogEntry struct {\n") buf.WriteString("\tKey string\n") buf.WriteString("\tDisplayName string\n") buf.WriteString("\tComparisonName string\n") buf.WriteString("\tShowInComparisonTable bool\n") buf.WriteString("\tDisplayableInSelfHostedPlan bool\n") buf.WriteString("\tCommunityRole selfHostedFeatureRole\n") buf.WriteString("\tRelayRole selfHostedFeatureRole\n") buf.WriteString("\tProRole selfHostedFeatureRole\n") buf.WriteString("\tCommunity bool\n") buf.WriteString("\tRelay bool\n") buf.WriteString("\tPro bool\n") buf.WriteString("}\n\n") buf.WriteString("var selfHostedFeatureCatalog = []selfHostedFeatureCatalogEntry{\n") for _, entry := range entries { buf.WriteString("\t{\n") buf.WriteString(fmt.Sprintf("\t\tKey: %q,\n", entry.Key)) buf.WriteString(fmt.Sprintf("\t\tDisplayName: %q,\n", entry.DisplayName)) buf.WriteString(fmt.Sprintf("\t\tComparisonName: %q,\n", entry.ComparisonName)) buf.WriteString(fmt.Sprintf("\t\tShowInComparisonTable: %t,\n", entry.ShowInComparisonTable)) buf.WriteString(fmt.Sprintf("\t\tDisplayableInSelfHostedPlan: %t,\n", entry.DisplayableInSelfHostedPlan)) buf.WriteString(fmt.Sprintf("\t\tCommunityRole: %q,\n", entry.Roles.Community)) buf.WriteString(fmt.Sprintf("\t\tRelayRole: %q,\n", entry.Roles.Relay)) buf.WriteString(fmt.Sprintf("\t\tProRole: %q,\n", entry.Roles.Pro)) buf.WriteString(fmt.Sprintf("\t\tCommunity: %t,\n", entry.IncludedIn.Community)) buf.WriteString(fmt.Sprintf("\t\tRelay: %t,\n", entry.IncludedIn.Relay)) buf.WriteString(fmt.Sprintf("\t\tPro: %t,\n", entry.IncludedIn.Pro)) buf.WriteString("\t},\n") } buf.WriteString("}\n\n") buf.WriteString("var selfHostedFeatureCatalogByKey = map[string]selfHostedFeatureCatalogEntry{\n") for _, entry := range entries { buf.WriteString(fmt.Sprintf("\t%q: {\n", entry.Key)) buf.WriteString(fmt.Sprintf("\t\tKey: %q,\n", entry.Key)) buf.WriteString(fmt.Sprintf("\t\tDisplayName: %q,\n", entry.DisplayName)) buf.WriteString(fmt.Sprintf("\t\tComparisonName: %q,\n", entry.ComparisonName)) buf.WriteString(fmt.Sprintf("\t\tShowInComparisonTable: %t,\n", entry.ShowInComparisonTable)) buf.WriteString(fmt.Sprintf("\t\tDisplayableInSelfHostedPlan: %t,\n", entry.DisplayableInSelfHostedPlan)) buf.WriteString(fmt.Sprintf("\t\tCommunityRole: %q,\n", entry.Roles.Community)) buf.WriteString(fmt.Sprintf("\t\tRelayRole: %q,\n", entry.Roles.Relay)) buf.WriteString(fmt.Sprintf("\t\tProRole: %q,\n", entry.Roles.Pro)) buf.WriteString(fmt.Sprintf("\t\tCommunity: %t,\n", entry.IncludedIn.Community)) buf.WriteString(fmt.Sprintf("\t\tRelay: %t,\n", entry.IncludedIn.Relay)) buf.WriteString(fmt.Sprintf("\t\tPro: %t,\n", entry.IncludedIn.Pro)) buf.WriteString("\t},\n") } buf.WriteString("}\n\n") buf.WriteString("var canonicalSelfHostedTierFeatures = map[string][]string{\n") tiers := make([]string, 0, len(tierFeatures)) for tier := range tierFeatures { tiers = append(tiers, tier) } sort.Strings(tiers) for _, tier := range tiers { features := append([]string(nil), tierFeatures[tier]...) sort.Strings(features) buf.WriteString(fmt.Sprintf("\t%q: []string{%s},\n", tier, quoteJoin(features))) } buf.WriteString("}\n\n") buf.WriteString("func selfHostedFeaturesForRole(tier string, role selfHostedFeatureRole) []selfHostedFeatureCatalogEntry {\n") buf.WriteString("\tentries := make([]selfHostedFeatureCatalogEntry, 0)\n") buf.WriteString("\tfor _, entry := range selfHostedFeatureCatalog {\n") buf.WriteString("\t\tvar current selfHostedFeatureRole\n") buf.WriteString("\t\tswitch strings.ToLower(strings.TrimSpace(tier)) {\n") buf.WriteString("\t\tcase \"community\":\n") buf.WriteString("\t\t\tcurrent = entry.CommunityRole\n") buf.WriteString("\t\tcase \"relay\":\n") buf.WriteString("\t\t\tcurrent = entry.RelayRole\n") buf.WriteString("\t\tcase \"pro\":\n") buf.WriteString("\t\t\tcurrent = entry.ProRole\n") buf.WriteString("\t\tdefault:\n") buf.WriteString("\t\t\tcurrent = selfHostedFeatureRoleHidden\n") buf.WriteString("\t\t}\n") buf.WriteString("\t\tif current == role {\n") buf.WriteString("\t\t\tentries = append(entries, entry)\n") buf.WriteString("\t\t}\n") buf.WriteString("\t}\n") buf.WriteString("\treturn entries\n") buf.WriteString("}\n") formatted, err := format.Source(buf.Bytes()) if err != nil { return fmt.Errorf("format pulse-pro module: %w", err) } return os.WriteFile(path, formatted, 0o644) } func quoteJoin(values []string) string { quoted := make([]string, 0, len(values)) for _, value := range values { quoted = append(quoted, fmt.Sprintf("%q", value)) } return strings.Join(quoted, ", ") } func fail(err error) { fmt.Fprintln(os.Stderr, err) os.Exit(1) }