Pulse/scripts/generate-self-hosted-feature-catalog.go
2026-05-01 21:36:28 +01:00

271 lines
12 KiB
Go

//go:build ignore
package main
import (
"bytes"
"encoding/json"
"fmt"
"go/format"
"os"
"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/src/utils/selfHostedFeatureCatalog.generated.ts"), 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(path string, entries []generatedFeatureEntry) error {
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<string, GeneratedSelfHostedFeatureCatalogEntry> = Object.fromEntries(\n")
buf.WriteString(" SELF_HOSTED_FEATURE_CATALOG.map((entry) => [entry.key, entry]),\n")
buf.WriteString(") as Record<string, GeneratedSelfHostedFeatureCatalogEntry>;\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")
return os.WriteFile(path, buf.Bytes(), 0o644)
}
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)
}