mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-18 18:23:14 +00:00
Encode canonical self-hosted feature metadata
This commit is contained in:
parent
8b6a429186
commit
b4692ef7f0
13 changed files with 1244 additions and 262 deletions
269
scripts/generate-self-hosted-feature-catalog.go
Normal file
269
scripts/generate-self-hosted-feature-catalog.go
Normal file
|
|
@ -0,0 +1,269 @@
|
|||
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)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue