mirror of
https://github.com/supabase/supabase.git
synced 2026-10-11 12:25:05 +03:00
refactor(www): extract shared markdown table builder, tighten pricing catalog types
This commit is contained in:
1 parent
af435025ac
commit
b522f31060
4 files changed
+21
-46
No files matched your search
+15
-31
@@ -31,6 +31,16 @@ function pad(str: string, len: number): string {
|
||||
return str.padEnd(len)
|
||||
}
|
||||
|
||||
function buildMarkdownTable(headers: string[], dataRows: string[][]): string[] {
|
||||
const widths = headers.map((h, i) => Math.max(h.length, ...dataRows.map((r) => r[i].length)))
|
||||
const headerRow = `| ${headers.map((h, i) => pad(h, widths[i])).join(' | ')} |`
|
||||
const separator = `| ${widths.map((w) => '-'.repeat(w)).join(' | ')} |`
|
||||
const bodyRows = dataRows.map(
|
||||
(cells) => `| ${cells.map((c, i) => pad(c, widths[i])).join(' | ')} |`
|
||||
)
|
||||
return [headerRow, separator, ...bodyRows]
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Plan Tiers
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -97,22 +107,12 @@ function buildComputeSection(): string {
|
||||
})
|
||||
)
|
||||
|
||||
const widths = headers.map((h, i) => Math.max(h.length, ...dataRows.map((r) => r[i].length)))
|
||||
|
||||
const headerRow = `| ${headers.map((h, i) => pad(h, widths[i])).join(' | ')} |`
|
||||
const separator = `| ${widths.map((w) => '-'.repeat(w)).join(' | ')} |`
|
||||
const bodyRows = dataRows.map(
|
||||
(cells) => `| ${cells.map((c, i) => pad(c, widths[i])).join(' | ')} |`
|
||||
)
|
||||
|
||||
return [
|
||||
'## Compute Add-Ons',
|
||||
'',
|
||||
'All projects run on a compute instance. Pro and Team plans include Micro compute in the base price.',
|
||||
'',
|
||||
headerRow,
|
||||
separator,
|
||||
...bodyRows,
|
||||
...buildMarkdownTable(headers, dataRows),
|
||||
'',
|
||||
`Compute is billed hourly. Each project runs its own instance. Pro and Team plans include ${usd(PLAN_BILLING.pro.computeCreditsMonthly)}/month in compute credits (covers one Micro instance). Additional projects add their full compute cost.`,
|
||||
'',
|
||||
@@ -126,7 +126,7 @@ function buildComputeSection(): string {
|
||||
function buildDiskSection(): string {
|
||||
const headers = ['Disk type', 'Max size', 'Size', 'IOPS', 'Throughput', 'Durability']
|
||||
|
||||
const dataRows = Object.values(DISK_PRICING).map((disk) => {
|
||||
const dataRows = Object.entries(DISK_PRICING).map(([type, disk]) => {
|
||||
const size = disk.includedPerProject
|
||||
? `${disk.includedPerProject.sizeGb} GB included, then ${usd(disk.perUnitMonth.sizeGb)} per GB`
|
||||
: `${usd(disk.perUnitMonth.sizeGb)} per GB`
|
||||
@@ -139,7 +139,7 @@ function buildDiskSection(): string {
|
||||
: (disk.throughputNote ?? '')
|
||||
|
||||
return [
|
||||
`${disk.displayName} (${disk.type})`,
|
||||
`${disk.displayName} (${type})`,
|
||||
`${disk.maxSizeTb} TB`,
|
||||
size,
|
||||
iops,
|
||||
@@ -148,14 +148,7 @@ function buildDiskSection(): string {
|
||||
]
|
||||
})
|
||||
|
||||
const widths = headers.map((h, i) => Math.max(h.length, ...dataRows.map((r) => r[i].length)))
|
||||
const headerRow = `| ${headers.map((h, i) => pad(h, widths[i])).join(' | ')} |`
|
||||
const separator = `| ${widths.map((w) => '-'.repeat(w)).join(' | ')} |`
|
||||
const bodyRows = dataRows.map(
|
||||
(cells) => `| ${cells.map((c, i) => pad(c, widths[i])).join(' | ')} |`
|
||||
)
|
||||
|
||||
return ['## Disk Storage', '', headerRow, separator, ...bodyRows, ''].join('\n')
|
||||
return ['## Disk Storage', '', ...buildMarkdownTable(headers, dataRows), ''].join('\n')
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -192,16 +185,7 @@ function buildAddOnsSection(): string {
|
||||
],
|
||||
]
|
||||
|
||||
const nameWidth = Math.max('Add-on'.length, ...addOns.map(([n]) => n.length))
|
||||
const priceWidth = Math.max('Price'.length, ...addOns.map(([, p]) => p.length))
|
||||
|
||||
const header = `| ${pad('Add-on', nameWidth)} | ${pad('Price', priceWidth)} |`
|
||||
const separator = `| ${'-'.repeat(nameWidth)} | ${'-'.repeat(priceWidth)} |`
|
||||
const rows = addOns.map(
|
||||
([name, price]) => `| ${pad(name, nameWidth)} | ${pad(price, priceWidth)} |`
|
||||
)
|
||||
|
||||
return ['## Add-Ons', '', header, separator, ...rows, ''].join('\n')
|
||||
return ['## Add-Ons', '', ...buildMarkdownTable(['Add-on', 'Price'], addOns), ''].join('\n')
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -87,8 +87,8 @@ export function buildPricingJson() {
|
||||
},
|
||||
disk: {
|
||||
scope: 'per-project',
|
||||
types: Object.values(DISK_PRICING).map((disk) => ({
|
||||
type: disk.type,
|
||||
types: Object.entries(DISK_PRICING).map(([type, disk]) => ({
|
||||
type,
|
||||
name: disk.displayName,
|
||||
maxSizeTb: disk.maxSizeTb,
|
||||
durabilityPercent: disk.durabilityPercent,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ADDONS, gbDisplay, METERS, PLAN_BILLING, qty, usd, withUnit } from './pricing-catalog'
|
||||
import type { PlanKey } from './pricing-catalog'
|
||||
|
||||
export type PlanId = 'free' | 'pro' | 'team' | 'enterprise'
|
||||
export type PlanId = PlanKey
|
||||
|
||||
export interface PricingInformation {
|
||||
id: 'tier_free' | 'tier_pro' | 'tier_team' | 'tier_enterprise'
|
||||
|
||||
@@ -26,7 +26,6 @@ export interface DiskAllowance {
|
||||
}
|
||||
|
||||
export interface DiskTypeEntry {
|
||||
type: 'gp3' | 'io2'
|
||||
displayName: string
|
||||
tagline: string
|
||||
maxSizeTb: number
|
||||
@@ -36,13 +35,6 @@ export interface DiskTypeEntry {
|
||||
throughputNote?: string
|
||||
}
|
||||
|
||||
export interface AddonAvailability {
|
||||
free: boolean
|
||||
pro: boolean
|
||||
team: boolean
|
||||
enterprise: boolean
|
||||
}
|
||||
|
||||
export function usd(amount: number, decimals?: number): string {
|
||||
return `$${decimals === undefined ? String(amount) : amount.toFixed(decimals)}`
|
||||
}
|
||||
@@ -72,9 +64,8 @@ export const PLAN_BILLING = {
|
||||
enterprise: { priceMonthly: null, computeCreditsMonthly: null, spendCapAvailable: false },
|
||||
} as const satisfies Record<PlanKey, PlanBillingEntry>
|
||||
|
||||
export const DISK_PRICING: Record<'gp3' | 'io2', DiskTypeEntry> = {
|
||||
export const DISK_PRICING = {
|
||||
gp3: {
|
||||
type: 'gp3',
|
||||
displayName: 'General Purpose',
|
||||
tagline: 'Balance between price and performance',
|
||||
maxSizeTb: 16,
|
||||
@@ -83,7 +74,6 @@ export const DISK_PRICING: Record<'gp3' | 'io2', DiskTypeEntry> = {
|
||||
perUnitMonth: { sizeGb: GP3_PER_GB_MONTH, iops: 0.024, throughputMBps: 0.095 },
|
||||
},
|
||||
io2: {
|
||||
type: 'io2',
|
||||
displayName: 'High Performance',
|
||||
tagline: 'For mission critical applications',
|
||||
maxSizeTb: 60,
|
||||
@@ -92,7 +82,7 @@ export const DISK_PRICING: Record<'gp3' | 'io2', DiskTypeEntry> = {
|
||||
perUnitMonth: { sizeGb: 0.195, iops: 0.119, throughputMBps: null },
|
||||
throughputNote: 'Scales automatically with IOPS',
|
||||
},
|
||||
}
|
||||
} as const satisfies Record<'gp3' | 'io2', DiskTypeEntry>
|
||||
|
||||
export const METERS = {
|
||||
egress: {
|
||||
|
||||
Reference in new issue
Block a user