Centralize launch readiness and expand public manual surfaces
This commit is contained in:
parent
2967c6ee44
commit
4c5ceea100
15 changed files with 730 additions and 245 deletions
|
|
@ -136,6 +136,12 @@ marketing copy does not imply launch readiness when checkout/download config
|
|||
exists but the live billing webhook or public-origin runtime posture is still
|
||||
incomplete.
|
||||
|
||||
That same lane is now additionally server-owned through an authoritative
|
||||
`launch` summary on `GET /api/auth/health`, carrying blocker labels, billing
|
||||
product/price-map posture, and current operator/studio checkout targets so the
|
||||
public banner and protected dashboard no longer recompute launch blockers from
|
||||
separate client-side sources.
|
||||
|
||||
The login and register pages now also surface those shared-auth runtime warnings
|
||||
instead of silently behaving like production auth when the lane is still in
|
||||
local fallback or mixed deployment posture.
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -305,6 +305,26 @@ describe('website/server bootstrap', { timeout: 15_000 }, () => {
|
|||
warnings: [],
|
||||
errors: [],
|
||||
},
|
||||
launch: {
|
||||
posture: 'launch-ready',
|
||||
ready: true,
|
||||
missingLabels: [],
|
||||
targets: {
|
||||
operatorCheckoutTarget: 'https://buy.paddle.com/operator-live',
|
||||
studioCheckoutTarget: 'https://buy.paddle.com/studio-live',
|
||||
},
|
||||
readiness: {
|
||||
windowsDownloadConfigured: true,
|
||||
operatorCheckoutConfigured: true,
|
||||
studioCheckoutConfigured: true,
|
||||
mplSourceConfigured: true,
|
||||
openSourceRepoConfigured: true,
|
||||
billingProductPlanMapConfigured: true,
|
||||
billingPricePlanMapConfigured: true,
|
||||
billingWebhookSecretConfigured: true,
|
||||
publicAuthRuntimeReady: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const releaseManifestResponse = await fetch(`${baseUrl}/api/releases/manifest`)
|
||||
|
|
@ -482,6 +502,11 @@ describe('website/server bootstrap', { timeout: 15_000 }, () => {
|
|||
fallback: {
|
||||
active: false,
|
||||
},
|
||||
launch: {
|
||||
posture: 'launch-ready',
|
||||
ready: true,
|
||||
missingLabels: [],
|
||||
},
|
||||
})
|
||||
|
||||
const stateFile = JSON.parse(readFileSync(billingStatePath, 'utf8'))
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import Session from 'supertokens-node/recipe/session'
|
|||
import ThirdParty from 'supertokens-node/recipe/thirdparty'
|
||||
import GithubProvider from 'supertokens-node/lib/build/recipe/thirdparty/providers/github'
|
||||
import GoogleProvider from 'supertokens-node/lib/build/recipe/thirdparty/providers/google'
|
||||
import { resolvePublicLaunchStatusSummary } from '../../src/shared/public-launch'
|
||||
import { probeSuperTokensCoreHealth } from './auth-health'
|
||||
import { createBillingStateStore, type BillingPlan, type BillingRole } from './billing-state'
|
||||
import { verifyPaddleWebhookSignature } from './paddle-webhook'
|
||||
|
|
@ -315,6 +316,31 @@ app.get('/api/auth/health', async (_req, res) => {
|
|||
timeoutMs: SUPERTOKENS_HEALTH_TIMEOUT_MS,
|
||||
})
|
||||
|
||||
const billing = {
|
||||
statePath: billingStateStore.getStatePath(),
|
||||
processedEventCount: billingStateStore.getProcessedEventCount(),
|
||||
pricePlanMapConfigured: Object.keys(PADDLE_PRICE_PLAN_MAP).length > 0,
|
||||
productPlanMapConfigured: Object.keys(PADDLE_PRODUCT_PLAN_MAP).length > 0,
|
||||
webhookSecretConfigured: Boolean(PADDLE_WEBHOOK_SECRET),
|
||||
}
|
||||
const runtime = {
|
||||
mode: runtimeConfigDiagnostics.mode,
|
||||
public_origin_ready: runtimeConfigDiagnostics.publicOriginReady,
|
||||
cookie_secure: runtimeConfigDiagnostics.cookieSecure,
|
||||
api_domain: runtimeConfigDiagnostics.apiDomain,
|
||||
website_domain: runtimeConfigDiagnostics.websiteDomain,
|
||||
warnings: runtimeConfigDiagnostics.warnings,
|
||||
errors: runtimeConfigDiagnostics.errors,
|
||||
}
|
||||
const publicManifest = createReleaseManifestResponse(createAnonymousReleaseManifestViewer()).manifest
|
||||
const launch = resolvePublicLaunchStatusSummary({
|
||||
manifest: publicManifest,
|
||||
health: {
|
||||
billing,
|
||||
runtime,
|
||||
},
|
||||
})
|
||||
|
||||
res.json({
|
||||
ok: true,
|
||||
service: 'hypertwist-auth-server',
|
||||
|
|
@ -334,22 +360,9 @@ app.get('/api/auth/health', async (_req, res) => {
|
|||
active: !superTokensHealth.ready,
|
||||
reason: superTokensHealth.ready ? null : (superTokensHealth.error || 'not_ready'),
|
||||
},
|
||||
billing: {
|
||||
statePath: billingStateStore.getStatePath(),
|
||||
processedEventCount: billingStateStore.getProcessedEventCount(),
|
||||
pricePlanMapConfigured: Object.keys(PADDLE_PRICE_PLAN_MAP).length > 0,
|
||||
productPlanMapConfigured: Object.keys(PADDLE_PRODUCT_PLAN_MAP).length > 0,
|
||||
webhookSecretConfigured: Boolean(PADDLE_WEBHOOK_SECRET),
|
||||
},
|
||||
runtime: {
|
||||
mode: runtimeConfigDiagnostics.mode,
|
||||
public_origin_ready: runtimeConfigDiagnostics.publicOriginReady,
|
||||
cookie_secure: runtimeConfigDiagnostics.cookieSecure,
|
||||
api_domain: runtimeConfigDiagnostics.apiDomain,
|
||||
website_domain: runtimeConfigDiagnostics.websiteDomain,
|
||||
warnings: runtimeConfigDiagnostics.warnings,
|
||||
errors: runtimeConfigDiagnostics.errors,
|
||||
},
|
||||
billing,
|
||||
runtime,
|
||||
launch,
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -181,8 +181,10 @@ describe('DashboardOverviewPage', () => {
|
|||
expect(screen.getByText(/This session is currently using local fallback posture/i)).toBeTruthy()
|
||||
expect(screen.getByText(/Shared auth core is not fully ready right now/i)).toBeTruthy()
|
||||
expect(screen.getByText(/Public launch is not fully configured yet:/i)).toBeTruthy()
|
||||
expect(screen.getByText(/Paddle webhook secret missing/i)).toBeTruthy()
|
||||
expect(screen.getByText(/Public auth runtime still uses local or mixed deployment posture/i)).toBeTruthy()
|
||||
expect(screen.getByText('Billing product-plan map: missing')).toBeTruthy()
|
||||
expect(screen.getByText('Billing price-plan map: missing')).toBeTruthy()
|
||||
expect(screen.getByText('Paddle webhook secret: missing')).toBeTruthy()
|
||||
expect(screen.getByText('Public auth runtime posture: local-or-mixed')).toBeTruthy()
|
||||
expect(screen.getByText('Packaged validation passed')).toBeTruthy()
|
||||
expect(screen.getByText(/Magic120Cell dedicated-family training map: passed/i)).toBeTruthy()
|
||||
expect(screen.getByText('Desktop rollout follow-through')).toBeTruthy()
|
||||
|
|
|
|||
|
|
@ -13,12 +13,18 @@ describe('public launch readiness helpers', () => {
|
|||
studioCheckoutConfigured: false,
|
||||
mplSourceConfigured: false,
|
||||
openSourceRepoConfigured: true,
|
||||
}, {
|
||||
requiredOnly: true,
|
||||
})
|
||||
|
||||
expect(missingItems.map((item) => item.label)).toEqual([
|
||||
'Operator checkout',
|
||||
'Studio checkout',
|
||||
'Corresponding-source URL',
|
||||
'Operator checkout URL',
|
||||
'Studio checkout URL',
|
||||
'MPL corresponding-source URL',
|
||||
'Billing product-plan map',
|
||||
'Billing price-plan map',
|
||||
'Paddle webhook secret',
|
||||
'Public auth runtime posture',
|
||||
])
|
||||
expect(isPublicLaunchReady({
|
||||
windowsDownloadConfigured: true,
|
||||
|
|
@ -32,19 +38,31 @@ describe('public launch readiness helpers', () => {
|
|||
it('treats the bounded website lane as launch-ready only when the full checklist is configured', () => {
|
||||
const checklist = getPublicLaunchChecklist({
|
||||
windowsDownloadConfigured: true,
|
||||
macosDownloadConfigured: true,
|
||||
linuxDownloadConfigured: true,
|
||||
operatorCheckoutConfigured: true,
|
||||
studioCheckoutConfigured: true,
|
||||
mplSourceConfigured: true,
|
||||
openSourceRepoConfigured: true,
|
||||
billingProductPlanMapConfigured: true,
|
||||
billingPricePlanMapConfigured: true,
|
||||
billingWebhookSecretConfigured: true,
|
||||
publicAuthRuntimeReady: true,
|
||||
})
|
||||
|
||||
expect(checklist.every((item) => item.configured)).toBe(true)
|
||||
expect(isPublicLaunchReady({
|
||||
windowsDownloadConfigured: true,
|
||||
macosDownloadConfigured: false,
|
||||
linuxDownloadConfigured: false,
|
||||
operatorCheckoutConfigured: true,
|
||||
studioCheckoutConfigured: true,
|
||||
mplSourceConfigured: true,
|
||||
openSourceRepoConfigured: true,
|
||||
billingProductPlanMapConfigured: true,
|
||||
billingPricePlanMapConfigured: true,
|
||||
billingWebhookSecretConfigured: true,
|
||||
publicAuthRuntimeReady: true,
|
||||
})).toBe(true)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -102,6 +102,7 @@ vi.mock('../site-config', () => ({
|
|||
}))
|
||||
|
||||
import {
|
||||
AboutPage,
|
||||
DocsPage,
|
||||
DownloadPage,
|
||||
HomeLanding,
|
||||
|
|
@ -236,7 +237,7 @@ describe('public marketing pages', () => {
|
|||
|
||||
expect(screen.getByRole('link', { name: /sign in for windows access/i }).getAttribute('href')).toBe('/app/downloads?platform=windows')
|
||||
expect(screen.getAllByText('Preview posture').length).toBeGreaterThan(0)
|
||||
expect(screen.getByText('Operator checkout: missing')).toBeTruthy()
|
||||
expect(screen.getByText('Operator checkout URL: missing')).toBeTruthy()
|
||||
expect(screen.getByText('How the release lane works')).toBeTruthy()
|
||||
expect(screen.getByText('First launch and desktop setup')).toBeTruthy()
|
||||
expect(screen.getByText('Pair desktop access to the browser account')).toBeTruthy()
|
||||
|
|
@ -347,12 +348,83 @@ describe('public marketing pages', () => {
|
|||
expect(screen.getByText('Current public site status')).toBeTruthy()
|
||||
expect(await screen.findByText('Public website and release posture')).toBeTruthy()
|
||||
expect(screen.getAllByText('Preview posture').length).toBeGreaterThan(0)
|
||||
expect(screen.getByText('Paddle webhook secret: missing')).toBeTruthy()
|
||||
expect(screen.getAllByText('Paddle webhook secret: missing').length).toBeGreaterThan(0)
|
||||
expect(screen.getByText('Public auth runtime posture: production-ready')).toBeTruthy()
|
||||
expect(screen.getByText('How a real first session flows')).toBeTruthy()
|
||||
expect(screen.getByText('Why both browser and desktop stay')).toBeTruthy()
|
||||
expect(screen.getAllByText('Native Unreal desktop runtime').length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it('surfaces about-page workflow and current XR truth without pretending the web replaces the simulator', () => {
|
||||
mockGetAuthHealth.mockResolvedValue({
|
||||
ok: true,
|
||||
service: 'hypertwist-auth-server',
|
||||
supertokens: {
|
||||
configured: true,
|
||||
reachable: true,
|
||||
ready: true,
|
||||
apiVersion: '5.4',
|
||||
error: null,
|
||||
oauth: {
|
||||
github: false,
|
||||
google: false,
|
||||
},
|
||||
},
|
||||
fallback: {
|
||||
enabled: true,
|
||||
active: false,
|
||||
reason: null,
|
||||
},
|
||||
billing: {
|
||||
statePath: '/var/lib/hypertwist/auth/hypertwist-billing-state.json',
|
||||
processedEventCount: 0,
|
||||
pricePlanMapConfigured: true,
|
||||
productPlanMapConfigured: true,
|
||||
webhookSecretConfigured: true,
|
||||
},
|
||||
runtime: {
|
||||
mode: 'public',
|
||||
public_origin_ready: true,
|
||||
cookie_secure: true,
|
||||
api_domain: 'https://hypertwist.app',
|
||||
website_domain: 'https://hypertwist.app',
|
||||
warnings: [],
|
||||
errors: [],
|
||||
},
|
||||
})
|
||||
mockGetReleaseManifest.mockResolvedValue({
|
||||
ok: true,
|
||||
manifest: {
|
||||
generated_at: '2026-06-22T12:00:00.000Z',
|
||||
support_email: 'hello@hypertwist.app',
|
||||
public_docs_url: 'https://docs.hypertwist.app',
|
||||
release_notes_url: 'https://notes.hypertwist.app',
|
||||
corresponding_source_url: 'https://hypertwist.app/open-source/source.zip',
|
||||
open_source_repo_url: 'https://github.com/hypertwist/hypertwist',
|
||||
commerce: {
|
||||
operator_checkout_url: 'https://buy.paddle.com/operator-live',
|
||||
studio_checkout_url: 'https://buy.paddle.com/studio-live',
|
||||
plan_price_operator: '$19 / month',
|
||||
plan_price_studio: '$99 / month',
|
||||
},
|
||||
viewer: {
|
||||
authenticated: false,
|
||||
canDownload: false,
|
||||
plan: null,
|
||||
role: null,
|
||||
accessStatus: null,
|
||||
},
|
||||
platforms: [],
|
||||
},
|
||||
})
|
||||
|
||||
renderWithProviders(<AboutPage />, ['/about'])
|
||||
|
||||
expect(screen.getByText('How a real HyperTwist session unfolds')).toBeTruthy()
|
||||
expect(screen.getByText('Current desktop control and XR truth')).toBeTruthy()
|
||||
expect(screen.getByText('Release and deployment maturity')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('uses useful support fallback routes for plans that do not yet have live checkouts', () => {
|
||||
mockGetAuthHealth.mockResolvedValue({
|
||||
ok: true,
|
||||
|
|
@ -482,6 +554,7 @@ describe('public marketing pages', () => {
|
|||
|
||||
expect(await screen.findByText('$19 / month')).toBeTruthy()
|
||||
expect(screen.getByText('$99 / month')).toBeTruthy()
|
||||
expect(screen.getAllByText('What happens after access is granted').length).toBeGreaterThan(0)
|
||||
const checkoutLinks = screen.getAllByRole('link', { name: /open paddle checkout/i })
|
||||
expect(checkoutLinks).toHaveLength(2)
|
||||
expect(checkoutLinks[0].getAttribute('href')).toBe('https://buy.paddle.com/operator-live')
|
||||
|
|
@ -664,7 +737,7 @@ describe('public marketing pages', () => {
|
|||
expect(screen.getByText('Higher-dimensional runtime guide')).toBeTruthy()
|
||||
expect(screen.getByText('Current control and device posture')).toBeTruthy()
|
||||
expect(screen.getByText('Deployment readiness snapshot')).toBeTruthy()
|
||||
expect(screen.getByText('XR groundwork exists, but the full VR lane is not finished')).toBeTruthy()
|
||||
expect(screen.getAllByText('XR groundwork exists, but the full VR lane is not finished').length).toBeGreaterThan(0)
|
||||
expect(screen.getByText('Higher-dimensional runtime ownership')).toBeTruthy()
|
||||
})
|
||||
|
||||
|
|
@ -728,7 +801,7 @@ describe('public marketing pages', () => {
|
|||
renderWithProviders(<DocsPage />, ['/docs'])
|
||||
|
||||
expect(screen.getByText('Operator manual')).toBeTruthy()
|
||||
expect(screen.getByText('1. Start in the browser shell')).toBeTruthy()
|
||||
expect(screen.getAllByText('1. Start in the browser shell').length).toBeGreaterThan(0)
|
||||
expect(screen.getByText('Simulator manual')).toBeTruthy()
|
||||
expect(screen.getByText('Higher-dimensional family guide')).toBeTruthy()
|
||||
expect(screen.getByText('Deployment readiness manual')).toBeTruthy()
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { getAuthRuntimeConfig } from './auth-env'
|
||||
import type { PublicLaunchChecklistItem, PublicLaunchReadiness, PublicLaunchTargets } from '../shared/public-launch'
|
||||
|
||||
const authRuntime = getAuthRuntimeConfig()
|
||||
const DEFAULT_AUTH_API_TIMEOUT_MS = authRuntime.authApiTimeoutMs
|
||||
|
|
@ -90,6 +91,14 @@ export interface AuthHealthPayload {
|
|||
warnings: string[]
|
||||
errors: string[]
|
||||
}
|
||||
launch?: {
|
||||
posture: 'launch-ready' | 'preview'
|
||||
ready: boolean
|
||||
readiness: PublicLaunchReadiness
|
||||
checklist: PublicLaunchChecklistItem[]
|
||||
missingLabels: string[]
|
||||
targets: PublicLaunchTargets
|
||||
}
|
||||
}
|
||||
|
||||
export interface ReleaseManifestPlatformPayload {
|
||||
|
|
|
|||
|
|
@ -1,14 +1,8 @@
|
|||
import { useMemo } from 'react'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { getAuthHealth, getReleaseManifest } from '../../auth/auth-api'
|
||||
import {
|
||||
getMissingPublicLaunchChecklistItems,
|
||||
getPublicLaunchChecklist,
|
||||
isPublicLaunchReady,
|
||||
resolvePublicLaunchReadiness,
|
||||
type PublicLaunchReadiness,
|
||||
} from '../../public-launch'
|
||||
import { getAuthHealth } from '../../auth/auth-api'
|
||||
import { resolvePublicLaunchStatusSummary } from '../../public-launch'
|
||||
import { launchReadiness } from '../../site-config'
|
||||
import { buildSupportPath } from '../../site-routes'
|
||||
|
||||
|
|
@ -18,48 +12,29 @@ function usePublicLaunchStatus() {
|
|||
queryFn: getAuthHealth,
|
||||
retry: false,
|
||||
})
|
||||
const releaseManifestQuery = useQuery({
|
||||
queryKey: ['release-manifest', 'public'],
|
||||
queryFn: getReleaseManifest,
|
||||
retry: false,
|
||||
})
|
||||
const launchStatus = useMemo(() => (
|
||||
resolvePublicLaunchStatusSummary(authHealthQuery.data, undefined, launchReadiness)
|
||||
), [authHealthQuery.data])
|
||||
|
||||
const readiness = useMemo<PublicLaunchReadiness>(() => {
|
||||
return resolvePublicLaunchReadiness(releaseManifestQuery.data?.manifest, launchReadiness)
|
||||
}, [releaseManifestQuery.data?.manifest])
|
||||
|
||||
const checklist = getPublicLaunchChecklist(readiness)
|
||||
const missingItems = getMissingPublicLaunchChecklistItems(readiness)
|
||||
const webhookReady = authHealthQuery.data?.billing.webhookSecretConfigured === true
|
||||
const runtimeReady = authHealthQuery.data?.runtime.public_origin_ready === true
|
||||
const ready = isPublicLaunchReady(readiness) && webhookReady && runtimeReady
|
||||
const checklist = launchStatus.checklist
|
||||
const ready = launchStatus.ready
|
||||
const missingLabels = useMemo(() => {
|
||||
const labels = missingItems.map((item) => item.label.toLowerCase())
|
||||
const labels = launchStatus.missingLabels.map((label) => label.toLowerCase())
|
||||
|
||||
if (authHealthQuery.isLoading || authHealthQuery.isError) {
|
||||
labels.push('live auth runtime verification')
|
||||
return labels
|
||||
}
|
||||
|
||||
if (!webhookReady) {
|
||||
labels.push('billing webhook secret')
|
||||
}
|
||||
|
||||
if (!runtimeReady) {
|
||||
labels.push('public auth runtime posture')
|
||||
labels.push('release-manifest authority confirmation')
|
||||
}
|
||||
|
||||
return labels
|
||||
}, [authHealthQuery.isError, authHealthQuery.isLoading, missingItems, runtimeReady, webhookReady])
|
||||
}, [authHealthQuery.isError, authHealthQuery.isLoading, launchStatus.missingLabels])
|
||||
|
||||
return {
|
||||
authHealthQuery,
|
||||
checklist,
|
||||
missingLabels,
|
||||
ready,
|
||||
releaseManifestQuery,
|
||||
runtimeReady,
|
||||
webhookReady,
|
||||
launchStatus,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -96,7 +71,7 @@ export function PublicLaunchStatus({
|
|||
}: {
|
||||
title?: string
|
||||
}) {
|
||||
const { authHealthQuery, checklist, missingLabels, ready, releaseManifestQuery, runtimeReady, webhookReady } = usePublicLaunchStatus()
|
||||
const { authHealthQuery, checklist, launchStatus, missingLabels, ready } = usePublicLaunchStatus()
|
||||
|
||||
return (
|
||||
<article className="callout">
|
||||
|
|
@ -112,44 +87,20 @@ export function PublicLaunchStatus({
|
|||
<ul className="list">
|
||||
{checklist.map((item) => (
|
||||
<li key={item.id}>
|
||||
{item.label}: {item.configured ? 'configured' : 'missing'}
|
||||
{item.label}: {item.configured
|
||||
? (item.id === 'public-auth-runtime' ? 'production-ready' : 'configured')
|
||||
: (item.id === 'public-auth-runtime' ? 'local-or-mixed' : 'missing')}
|
||||
</li>
|
||||
))}
|
||||
<li>
|
||||
Paddle webhook secret:{' '}
|
||||
{authHealthQuery.isLoading
|
||||
? 'checking'
|
||||
: authHealthQuery.isError
|
||||
? 'unknown'
|
||||
: webhookReady
|
||||
? 'configured'
|
||||
: 'missing'}
|
||||
</li>
|
||||
<li>
|
||||
Public auth runtime posture:{' '}
|
||||
{authHealthQuery.isLoading
|
||||
? 'checking'
|
||||
: authHealthQuery.isError
|
||||
? 'unknown'
|
||||
: runtimeReady
|
||||
? 'production-ready'
|
||||
: 'local-or-mixed'}
|
||||
</li>
|
||||
<li>Operator checkout target: {launchStatus.targets.operatorCheckoutTarget || 'support fallback active'}</li>
|
||||
<li>Studio checkout target: {launchStatus.targets.studioCheckoutTarget || 'support fallback active'}</li>
|
||||
</ul>
|
||||
{authHealthQuery.isLoading ? (
|
||||
<p>Checking live auth-runtime and billing-webhook posture from the deployed auth server.</p>
|
||||
<p>Checking live launch-readiness posture from the deployed auth server.</p>
|
||||
) : null}
|
||||
{authHealthQuery.isError ? (
|
||||
<p className="form-error">
|
||||
Live auth-health lookup failed. Launch posture cannot confirm runtime or billing-webhook readiness right now.
|
||||
</p>
|
||||
) : null}
|
||||
{releaseManifestQuery.isLoading ? (
|
||||
<p>Refreshing Windows release readiness from the live auth-server manifest.</p>
|
||||
) : null}
|
||||
{releaseManifestQuery.isError ? (
|
||||
<p className="form-error">
|
||||
Live release-manifest lookup failed. This launch checklist is currently using a bounded frontend fallback and intentionally withholds direct download-lane authority until the auth server returns.
|
||||
Live auth-health lookup failed. Launch posture cannot confirm runtime, billing, or release-authority readiness right now.
|
||||
</p>
|
||||
) : null}
|
||||
{!ready ? (
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ import { buildAuthApiBaseUrls, createDesktopLinkToken, getAuthHealth, getRelease
|
|||
import { usePlatformAuth, type PlatformUser } from '../auth/platform-auth'
|
||||
import { SiteMetadata } from '../components/seo/SiteMetadata'
|
||||
import { ReleaseValidationSummary } from '../components/ui/ReleaseValidationSummary'
|
||||
import { resolvePublicLaunchReadiness } from '../public-launch'
|
||||
import { downloadTargets, launchReadiness, mplSourceUrl, openSourceRepoUrl, planCatalog, publicDocsUrl, releaseNotesUrl } from '../site-config'
|
||||
import { buildReleaseMetadataItems, resolveReleaseCommerceView, resolveReleaseManifestView, type ReleaseManifestView } from '../release-manifest'
|
||||
import { resolvePublicLaunchStatusSummary } from '../public-launch'
|
||||
import { downloadTargets, launchReadiness, mplSourceUrl, openSourceRepoUrl, publicDocsUrl, releaseNotesUrl } from '../site-config'
|
||||
import { buildReleaseMetadataItems, resolveReleaseManifestView, type ReleaseManifestView } from '../release-manifest'
|
||||
import { buildSupportPath, getDownloadPlatformLabel, normalizeDownloadPlatform } from '../site-routes'
|
||||
import { desktopDownloadSteps, desktopFirstLaunchCards, roadmapHonestyCards, supportEscalationCards } from '../site-data'
|
||||
|
||||
|
|
@ -29,15 +29,6 @@ function Panel({
|
|||
)
|
||||
}
|
||||
|
||||
const operatorFallbackPlan = planCatalog.find((plan) => plan.key === 'operator') ?? planCatalog[1]
|
||||
const studioFallbackPlan = planCatalog.find((plan) => plan.key === 'studio') ?? planCatalog[2]
|
||||
const releaseCommerceFallback = {
|
||||
operatorCheckoutUrl: /^https?:/i.test(operatorFallbackPlan.ctaHref) ? operatorFallbackPlan.ctaHref : '',
|
||||
studioCheckoutUrl: /^https?:/i.test(studioFallbackPlan.ctaHref) ? studioFallbackPlan.ctaHref : '',
|
||||
planPriceOperator: operatorFallbackPlan.price,
|
||||
planPriceStudio: studioFallbackPlan.price,
|
||||
}
|
||||
|
||||
function buildProtectedReleaseManifestFallback(user: PlatformUser | null | undefined, supportEmail: string) {
|
||||
return {
|
||||
downloadTargets,
|
||||
|
|
@ -283,27 +274,14 @@ export function DashboardOverviewPage() {
|
|||
},
|
||||
})
|
||||
|
||||
const releaseCommerce = useMemo(
|
||||
() => resolveReleaseCommerceView(releaseManifestQuery.data?.manifest, releaseCommerceFallback),
|
||||
[releaseManifestQuery.data?.manifest],
|
||||
)
|
||||
const publicLaunchReadiness = useMemo(
|
||||
() => resolvePublicLaunchReadiness(releaseManifestQuery.data?.manifest, launchReadiness),
|
||||
[releaseManifestQuery.data?.manifest],
|
||||
)
|
||||
const launchStatus = useMemo(() => (
|
||||
resolvePublicLaunchStatusSummary(
|
||||
healthQuery.data,
|
||||
releaseManifestQuery.data?.manifest,
|
||||
launchReadiness,
|
||||
)
|
||||
), [healthQuery.data, releaseManifestQuery.data?.manifest])
|
||||
|
||||
const manifestWindowsConfigured = useMemo(
|
||||
() => releaseManifest.platforms.some((platform) => platform.platform_key === 'windows' && platform.configured),
|
||||
[releaseManifest.platforms],
|
||||
)
|
||||
const manifestMacConfigured = useMemo(
|
||||
() => releaseManifest.platforms.some((platform) => platform.platform_key === 'macos' && platform.configured),
|
||||
[releaseManifest.platforms],
|
||||
)
|
||||
const manifestLinuxConfigured = useMemo(
|
||||
() => releaseManifest.platforms.some((platform) => platform.platform_key === 'linux' && platform.configured),
|
||||
[releaseManifest.platforms],
|
||||
)
|
||||
const manifestWindowsTarget = useMemo(
|
||||
() => releaseManifest.platforms.find((platform) => platform.platform_key === 'windows') || null,
|
||||
[releaseManifest.platforms],
|
||||
|
|
@ -317,17 +295,7 @@ export function DashboardOverviewPage() {
|
|||
return `${baseUrl}/api/auth/desktop-link/verify?token=${encodeURIComponent(token)}`
|
||||
}, [desktopLinkMutation.data?.token])
|
||||
|
||||
const launchReadinessIssues = useMemo(() => {
|
||||
const issues: string[] = []
|
||||
if (!manifestWindowsConfigured) issues.push('Windows download URL missing')
|
||||
if (!publicLaunchReadiness.operatorCheckoutConfigured) issues.push('Operator checkout URL missing')
|
||||
if (!publicLaunchReadiness.studioCheckoutConfigured) issues.push('Studio checkout URL missing')
|
||||
if (!publicLaunchReadiness.mplSourceConfigured) issues.push('MPL corresponding-source URL missing')
|
||||
if (!publicLaunchReadiness.openSourceRepoConfigured) issues.push('Open-source repository/notices URL missing')
|
||||
if (healthQuery.data && !healthQuery.data.billing.webhookSecretConfigured) issues.push('Paddle webhook secret missing')
|
||||
if (healthQuery.data && !healthQuery.data.runtime.public_origin_ready) issues.push('Public auth runtime still uses local or mixed deployment posture')
|
||||
return issues
|
||||
}, [healthQuery.data, manifestWindowsConfigured, publicLaunchReadiness])
|
||||
const launchReadinessIssues = launchStatus.missingLabels
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -339,30 +307,30 @@ export function DashboardOverviewPage() {
|
|||
/>
|
||||
<div className="panel-grid">
|
||||
<Panel title="Account state" kicker="Live session">
|
||||
<p><strong>{user?.name}</strong></p>
|
||||
<p>{user?.email}</p>
|
||||
<p>Plan: {user?.plan}</p>
|
||||
<p>Role: {user?.role || 'operator'}</p>
|
||||
<p>Desktop downloads: {user?.canDownload ? 'enabled' : 'not yet entitled'}</p>
|
||||
{!superTokensConfigured || user?.billing?.source === 'local-fallback' ? (
|
||||
<p className="form-error">
|
||||
This session is currently using local fallback posture, not fully shared production auth.
|
||||
</p>
|
||||
) : null}
|
||||
{releaseManifestQuery.data?.manifest && releaseAuthoritySyncItems.length > 0 ? (
|
||||
<div className="callout top-gap">
|
||||
<p className="status-pill status-pill--info">Live authority sync</p>
|
||||
<p>
|
||||
The protected browser session was refreshed from live release authority so the local dashboard view catches up to current account-access truth.
|
||||
<p><strong>{user?.name}</strong></p>
|
||||
<p>{user?.email}</p>
|
||||
<p>Plan: {user?.plan}</p>
|
||||
<p>Role: {user?.role || 'operator'}</p>
|
||||
<p>Desktop downloads: {user?.canDownload ? 'enabled' : 'not yet entitled'}</p>
|
||||
{!superTokensConfigured || user?.billing?.source === 'local-fallback' ? (
|
||||
<p className="form-error">
|
||||
This session is currently using local fallback posture, not fully shared production auth.
|
||||
</p>
|
||||
<ul className="list top-gap">
|
||||
{releaseAuthoritySyncItems.map((item) => (
|
||||
<li key={item}>{item}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
) : null}
|
||||
</Panel>
|
||||
) : null}
|
||||
{releaseManifestQuery.data?.manifest && releaseAuthoritySyncItems.length > 0 ? (
|
||||
<div className="callout top-gap">
|
||||
<p className="status-pill status-pill--info">Live authority sync</p>
|
||||
<p>
|
||||
The protected browser session was refreshed from live release authority so the local dashboard view catches up to current account-access truth.
|
||||
</p>
|
||||
<ul className="list top-gap">
|
||||
{releaseAuthoritySyncItems.map((item) => (
|
||||
<li key={item}>{item}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
) : null}
|
||||
</Panel>
|
||||
|
||||
<Panel title="Auth and server health" kicker="Browser shell">
|
||||
{healthQuery.isLoading ? <p>Checking auth server health...</p> : null}
|
||||
|
|
@ -440,20 +408,19 @@ export function DashboardOverviewPage() {
|
|||
</Panel>
|
||||
|
||||
<Panel title="Launch readiness" kicker="Public release configuration">
|
||||
<p className={`status-pill${launchStatus.ready ? ' status-pill--success' : ''}`}>
|
||||
{launchStatus.ready ? 'Launch-ready posture' : 'Preview posture'}
|
||||
</p>
|
||||
<ul className="list">
|
||||
<li>Windows download URL: {manifestWindowsConfigured ? 'configured' : 'missing'}</li>
|
||||
<li>macOS download URL: {manifestMacConfigured ? 'configured' : 'missing'}</li>
|
||||
<li>Linux download URL: {manifestLinuxConfigured ? 'configured' : 'missing'}</li>
|
||||
<li>Operator checkout URL: {publicLaunchReadiness.operatorCheckoutConfigured ? 'configured' : 'missing'}</li>
|
||||
<li>Studio checkout URL: {publicLaunchReadiness.studioCheckoutConfigured ? 'configured' : 'missing'}</li>
|
||||
<li>MPL corresponding-source URL: {publicLaunchReadiness.mplSourceConfigured ? 'configured' : 'missing'}</li>
|
||||
<li>Open-source repo/notices URL: {publicLaunchReadiness.openSourceRepoConfigured ? 'configured' : 'missing'}</li>
|
||||
<li>Paddle webhook secret: {!healthQuery.data ? 'checking' : (healthQuery.data.billing.webhookSecretConfigured ? 'configured' : 'missing')}</li>
|
||||
<li>Billing product map: {!healthQuery.data ? 'checking' : (healthQuery.data.billing.productPlanMapConfigured ? 'configured' : 'missing')}</li>
|
||||
<li>Billing price map: {!healthQuery.data ? 'checking' : (healthQuery.data.billing.pricePlanMapConfigured ? 'configured' : 'missing')}</li>
|
||||
<li>Public auth runtime posture: {!healthQuery.data ? 'checking' : (healthQuery.data.runtime.public_origin_ready ? 'production-ready' : 'local-or-mixed')}</li>
|
||||
<li>Operator checkout target: {releaseCommerce.operator_checkout_url || 'support fallback active'}</li>
|
||||
<li>Studio checkout target: {releaseCommerce.studio_checkout_url || 'support fallback active'}</li>
|
||||
{launchStatus.checklist.map((item) => (
|
||||
<li key={item.id}>
|
||||
{item.label}: {item.configured
|
||||
? (item.id === 'public-auth-runtime' ? 'production-ready' : 'configured')
|
||||
: (item.id === 'public-auth-runtime' ? 'local-or-mixed' : 'missing')}
|
||||
</li>
|
||||
))}
|
||||
<li>Operator checkout target: {launchStatus.targets.operatorCheckoutTarget || 'support fallback active'}</li>
|
||||
<li>Studio checkout target: {launchStatus.targets.studioCheckoutTarget || 'support fallback active'}</li>
|
||||
</ul>
|
||||
{releaseManifestQuery.isLoading ? (
|
||||
<p>Refreshing release-manifest download readiness from the auth server.</p>
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import {
|
|||
digitalDeliveryCards,
|
||||
distributionDoctrineCards,
|
||||
openSourceNotices,
|
||||
operatorManualTracks,
|
||||
privacyBoundaryCards,
|
||||
termsBoundaryCards,
|
||||
} from '../site-data'
|
||||
|
|
@ -94,6 +95,25 @@ export function PricingPage() {
|
|||
<PublicLaunchStatus />
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
title="What happens after access is granted"
|
||||
description="Pricing only stays professional when it explains the real path from browser entitlement into the packaged simulator instead of stopping at the checkout button."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{operatorManualTracks.slice(0, 3).map((track) => (
|
||||
<article key={track.title} className="card">
|
||||
<h3>{track.title}</h3>
|
||||
<p>{track.description}</p>
|
||||
<ul className="list top-gap">
|
||||
{track.steps.map((step) => (
|
||||
<li key={step}>{step}</li>
|
||||
))}
|
||||
</ul>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
title="Why plans live in the browser while training stays native"
|
||||
description="Commercial access, entitlement, and launch-readiness posture belong to the browser shell so the simulator can stay focused on training quality."
|
||||
|
|
|
|||
|
|
@ -167,6 +167,25 @@ export function HomeLanding() {
|
|||
</div>
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
title="How a real first session flows"
|
||||
description="This is the public-facing operator path from curiosity into the actual simulator lane, without pretending the browser already replaced the desktop runtime."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{operatorManualTracks.slice(0, 3).map((track) => (
|
||||
<article key={track.title} className="card">
|
||||
<h3>{track.title}</h3>
|
||||
<p>{track.description}</p>
|
||||
<ul className="list top-gap">
|
||||
{track.steps.map((step) => (
|
||||
<li key={step}>{step}</li>
|
||||
))}
|
||||
</ul>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
title="Why both browser and desktop stay"
|
||||
description="The public site owns operator and distribution work the simulator should not dilute, while the simulator stays native for the runtime-heavy training job."
|
||||
|
|
@ -244,6 +263,62 @@ export function AboutPage() {
|
|||
</article>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
title="How a real HyperTwist session unfolds"
|
||||
description="The product story is strongest when the public site explains the genuine operator path instead of flattening browser access and simulator use into one vague promise."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{operatorManualTracks.map((track) => (
|
||||
<article key={track.title} className="card">
|
||||
<h3>{track.title}</h3>
|
||||
<p>{track.description}</p>
|
||||
<ul className="list top-gap">
|
||||
{track.steps.map((step) => (
|
||||
<li key={step}>{step}</li>
|
||||
))}
|
||||
</ul>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
title="Current desktop control and XR truth"
|
||||
description="The product can be ambitious without overclaiming. This page now makes the current input, controller, and XR posture explicit."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{inputAndDevicePostureCards.map((card) => (
|
||||
<article key={card.title} className="card">
|
||||
<h3>{card.title}</h3>
|
||||
<p>{card.description}</p>
|
||||
<ul className="list top-gap">
|
||||
{card.bullets.map((bullet) => (
|
||||
<li key={bullet}>{bullet}</li>
|
||||
))}
|
||||
</ul>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
title="Release and deployment maturity"
|
||||
description="Production seriousness comes from clean separation between identity, package proof, legal posture, and runtime execution."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{deploymentReadinessTracks.map((track) => (
|
||||
<article key={track.title} className="card">
|
||||
<h3>{track.title}</h3>
|
||||
<ul className="list top-gap">
|
||||
{track.steps.map((step) => (
|
||||
<li key={step}>{step}</li>
|
||||
))}
|
||||
</ul>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
</MarketingShell>
|
||||
</>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,92 +1,89 @@
|
|||
import type { AuthHealthPayload } from './auth/auth-api'
|
||||
import type { ReleaseManifestView } from './release-manifest'
|
||||
import { launchReadiness } from './site-config'
|
||||
import {
|
||||
buildPublicLaunchStatusSummary,
|
||||
getMissingPublicLaunchChecklistItems as getSharedMissingPublicLaunchChecklistItems,
|
||||
getPublicLaunchChecklist as getSharedPublicLaunchChecklist,
|
||||
isPublicLaunchReady as isSharedPublicLaunchReady,
|
||||
normalizePublicLaunchReadiness,
|
||||
resolveHealthPublicLaunchReadiness,
|
||||
resolveManifestPublicLaunchReadiness,
|
||||
resolvePublicLaunchStatusSummary as resolveSharedPublicLaunchStatusSummary,
|
||||
type PublicLaunchChecklistId,
|
||||
type PublicLaunchChecklistItem,
|
||||
type PublicLaunchReadiness,
|
||||
type PublicLaunchStatusSummary,
|
||||
type PublicLaunchTargets,
|
||||
} from './shared/public-launch'
|
||||
|
||||
type PublicLaunchChecklistId =
|
||||
| 'windows-download'
|
||||
| 'operator-checkout'
|
||||
| 'studio-checkout'
|
||||
| 'mpl-source'
|
||||
| 'open-source-notices'
|
||||
const defaultLaunchReadiness = normalizePublicLaunchReadiness(launchReadiness)
|
||||
|
||||
export type PublicLaunchChecklistItem = {
|
||||
id: PublicLaunchChecklistId
|
||||
label: string
|
||||
configured: boolean
|
||||
}
|
||||
|
||||
export type PublicLaunchReadiness = {
|
||||
windowsDownloadConfigured: boolean
|
||||
operatorCheckoutConfigured: boolean
|
||||
studioCheckoutConfigured: boolean
|
||||
mplSourceConfigured: boolean
|
||||
openSourceRepoConfigured: boolean
|
||||
export type {
|
||||
PublicLaunchChecklistId,
|
||||
PublicLaunchChecklistItem,
|
||||
PublicLaunchReadiness,
|
||||
PublicLaunchStatusSummary,
|
||||
PublicLaunchTargets,
|
||||
}
|
||||
|
||||
export function getPublicLaunchChecklist(
|
||||
readiness: PublicLaunchReadiness = launchReadiness,
|
||||
readiness: Partial<PublicLaunchReadiness> = defaultLaunchReadiness,
|
||||
): PublicLaunchChecklistItem[] {
|
||||
return [
|
||||
{
|
||||
id: 'windows-download',
|
||||
label: 'Windows download URL',
|
||||
configured: readiness.windowsDownloadConfigured,
|
||||
},
|
||||
{
|
||||
id: 'operator-checkout',
|
||||
label: 'Operator checkout',
|
||||
configured: readiness.operatorCheckoutConfigured,
|
||||
},
|
||||
{
|
||||
id: 'studio-checkout',
|
||||
label: 'Studio checkout',
|
||||
configured: readiness.studioCheckoutConfigured,
|
||||
},
|
||||
{
|
||||
id: 'mpl-source',
|
||||
label: 'Corresponding-source URL',
|
||||
configured: readiness.mplSourceConfigured,
|
||||
},
|
||||
{
|
||||
id: 'open-source-notices',
|
||||
label: 'Open-source notices/repository URL',
|
||||
configured: readiness.openSourceRepoConfigured,
|
||||
},
|
||||
]
|
||||
return getSharedPublicLaunchChecklist(readiness)
|
||||
}
|
||||
|
||||
export function getMissingPublicLaunchChecklistItems(
|
||||
readiness: PublicLaunchReadiness = launchReadiness,
|
||||
readiness: Partial<PublicLaunchReadiness> = defaultLaunchReadiness,
|
||||
options?: {
|
||||
requiredOnly?: boolean
|
||||
},
|
||||
): PublicLaunchChecklistItem[] {
|
||||
return getPublicLaunchChecklist(readiness).filter((item) => !item.configured)
|
||||
return getSharedMissingPublicLaunchChecklistItems(readiness, options)
|
||||
}
|
||||
|
||||
export function isPublicLaunchReady(readiness: PublicLaunchReadiness = launchReadiness): boolean {
|
||||
return getMissingPublicLaunchChecklistItems(readiness).length === 0
|
||||
export function isPublicLaunchReady(
|
||||
readiness: Partial<PublicLaunchReadiness> = defaultLaunchReadiness,
|
||||
): boolean {
|
||||
return isSharedPublicLaunchReady(readiness)
|
||||
}
|
||||
|
||||
export function resolvePublicLaunchReadiness(
|
||||
manifest: ReleaseManifestView | null | undefined,
|
||||
fallback: PublicLaunchReadiness = launchReadiness,
|
||||
fallback: Partial<PublicLaunchReadiness> = defaultLaunchReadiness,
|
||||
): PublicLaunchReadiness {
|
||||
return {
|
||||
windowsDownloadConfigured: manifest?.platforms.some(
|
||||
(platform) => platform.platform_key === 'windows' && platform.configured,
|
||||
) ?? fallback.windowsDownloadConfigured,
|
||||
operatorCheckoutConfigured:
|
||||
manifest?.commerce?.operator_checkout_url != null
|
||||
? Boolean(manifest.commerce.operator_checkout_url)
|
||||
: fallback.operatorCheckoutConfigured,
|
||||
studioCheckoutConfigured:
|
||||
manifest?.commerce?.studio_checkout_url != null
|
||||
? Boolean(manifest.commerce.studio_checkout_url)
|
||||
: fallback.studioCheckoutConfigured,
|
||||
mplSourceConfigured:
|
||||
manifest?.corresponding_source_url != null
|
||||
? Boolean(manifest.corresponding_source_url)
|
||||
: fallback.mplSourceConfigured,
|
||||
openSourceRepoConfigured:
|
||||
manifest?.open_source_repo_url != null
|
||||
? Boolean(manifest.open_source_repo_url)
|
||||
: fallback.openSourceRepoConfigured,
|
||||
}
|
||||
return resolveManifestPublicLaunchReadiness(manifest, fallback)
|
||||
}
|
||||
|
||||
export function resolvePublicLaunchReadinessFromHealth({
|
||||
manifest,
|
||||
health,
|
||||
fallback = defaultLaunchReadiness,
|
||||
}: {
|
||||
manifest?: ReleaseManifestView | null
|
||||
health?: AuthHealthPayload | null
|
||||
fallback?: Partial<PublicLaunchReadiness>
|
||||
}): PublicLaunchReadiness {
|
||||
return resolveHealthPublicLaunchReadiness({
|
||||
manifest,
|
||||
health,
|
||||
fallback,
|
||||
})
|
||||
}
|
||||
|
||||
export function resolvePublicLaunchStatusSummary(
|
||||
health?: AuthHealthPayload | null,
|
||||
manifest?: ReleaseManifestView | null,
|
||||
fallback: Partial<PublicLaunchReadiness> = defaultLaunchReadiness,
|
||||
): PublicLaunchStatusSummary {
|
||||
return resolveSharedPublicLaunchStatusSummary({
|
||||
health,
|
||||
manifest,
|
||||
fallback,
|
||||
})
|
||||
}
|
||||
|
||||
export {
|
||||
buildPublicLaunchStatusSummary,
|
||||
normalizePublicLaunchReadiness,
|
||||
}
|
||||
|
|
|
|||
324
website/src/shared/public-launch.ts
Normal file
324
website/src/shared/public-launch.ts
Normal file
|
|
@ -0,0 +1,324 @@
|
|||
type ReleaseManifestLike = {
|
||||
commerce?: {
|
||||
operator_checkout_url?: string | null
|
||||
studio_checkout_url?: string | null
|
||||
} | null
|
||||
corresponding_source_url?: string | null
|
||||
open_source_repo_url?: string | null
|
||||
platforms?: Array<{
|
||||
platform_key: string
|
||||
configured: boolean
|
||||
}>
|
||||
}
|
||||
|
||||
type AuthHealthLike = {
|
||||
billing?: {
|
||||
productPlanMapConfigured?: boolean
|
||||
pricePlanMapConfigured?: boolean
|
||||
webhookSecretConfigured?: boolean
|
||||
}
|
||||
runtime?: {
|
||||
public_origin_ready?: boolean
|
||||
}
|
||||
launch?: {
|
||||
posture?: 'launch-ready' | 'preview'
|
||||
ready?: boolean
|
||||
readiness?: Partial<PublicLaunchReadiness> | null
|
||||
checklist?: PublicLaunchChecklistItem[]
|
||||
missingLabels?: string[]
|
||||
targets?: PublicLaunchTargets
|
||||
} | null
|
||||
}
|
||||
|
||||
export type PublicLaunchChecklistId =
|
||||
| 'windows-download'
|
||||
| 'macos-download'
|
||||
| 'linux-download'
|
||||
| 'operator-checkout'
|
||||
| 'studio-checkout'
|
||||
| 'mpl-source'
|
||||
| 'open-source-notices'
|
||||
| 'billing-product-map'
|
||||
| 'billing-price-map'
|
||||
| 'billing-webhook-secret'
|
||||
| 'public-auth-runtime'
|
||||
|
||||
export type PublicLaunchChecklistItem = {
|
||||
id: PublicLaunchChecklistId
|
||||
label: string
|
||||
configured: boolean
|
||||
requiredForPublicLaunch: boolean
|
||||
}
|
||||
|
||||
export type PublicLaunchReadiness = {
|
||||
windowsDownloadConfigured: boolean
|
||||
macosDownloadConfigured: boolean
|
||||
linuxDownloadConfigured: boolean
|
||||
operatorCheckoutConfigured: boolean
|
||||
studioCheckoutConfigured: boolean
|
||||
mplSourceConfigured: boolean
|
||||
openSourceRepoConfigured: boolean
|
||||
billingProductPlanMapConfigured: boolean
|
||||
billingPricePlanMapConfigured: boolean
|
||||
billingWebhookSecretConfigured: boolean
|
||||
publicAuthRuntimeReady: boolean
|
||||
}
|
||||
|
||||
export type PublicLaunchTargets = {
|
||||
operatorCheckoutTarget: string | null
|
||||
studioCheckoutTarget: string | null
|
||||
}
|
||||
|
||||
export type PublicLaunchStatusSummary = {
|
||||
posture: 'launch-ready' | 'preview'
|
||||
ready: boolean
|
||||
readiness: PublicLaunchReadiness
|
||||
checklist: PublicLaunchChecklistItem[]
|
||||
missingLabels: string[]
|
||||
targets: PublicLaunchTargets
|
||||
}
|
||||
|
||||
type PublicLaunchReadinessInput = Partial<PublicLaunchReadiness> & {
|
||||
macDownloadConfigured?: boolean
|
||||
}
|
||||
|
||||
function hasConfiguredPlatform(manifest: ReleaseManifestLike | null | undefined, platformKey: string) {
|
||||
if (!Array.isArray(manifest?.platforms)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return manifest.platforms.some((platform) => platform.platform_key === platformKey && platform.configured)
|
||||
}
|
||||
|
||||
function normalizeConfiguredValue(value: unknown) {
|
||||
return value === true
|
||||
}
|
||||
|
||||
export function normalizePublicLaunchReadiness(
|
||||
input?: PublicLaunchReadinessInput | null,
|
||||
): PublicLaunchReadiness {
|
||||
return {
|
||||
windowsDownloadConfigured: normalizeConfiguredValue(input?.windowsDownloadConfigured),
|
||||
macosDownloadConfigured:
|
||||
normalizeConfiguredValue(input?.macosDownloadConfigured)
|
||||
|| normalizeConfiguredValue(input?.macDownloadConfigured),
|
||||
linuxDownloadConfigured: normalizeConfiguredValue(input?.linuxDownloadConfigured),
|
||||
operatorCheckoutConfigured: normalizeConfiguredValue(input?.operatorCheckoutConfigured),
|
||||
studioCheckoutConfigured: normalizeConfiguredValue(input?.studioCheckoutConfigured),
|
||||
mplSourceConfigured: normalizeConfiguredValue(input?.mplSourceConfigured),
|
||||
openSourceRepoConfigured: normalizeConfiguredValue(input?.openSourceRepoConfigured),
|
||||
billingProductPlanMapConfigured: normalizeConfiguredValue(input?.billingProductPlanMapConfigured),
|
||||
billingPricePlanMapConfigured: normalizeConfiguredValue(input?.billingPricePlanMapConfigured),
|
||||
billingWebhookSecretConfigured: normalizeConfiguredValue(input?.billingWebhookSecretConfigured),
|
||||
publicAuthRuntimeReady: normalizeConfiguredValue(input?.publicAuthRuntimeReady),
|
||||
}
|
||||
}
|
||||
|
||||
export function getPublicLaunchChecklist(
|
||||
readinessInput: PublicLaunchReadinessInput,
|
||||
): PublicLaunchChecklistItem[] {
|
||||
const readiness = normalizePublicLaunchReadiness(readinessInput)
|
||||
|
||||
return [
|
||||
{
|
||||
id: 'windows-download',
|
||||
label: 'Windows download URL',
|
||||
configured: readiness.windowsDownloadConfigured,
|
||||
requiredForPublicLaunch: true,
|
||||
},
|
||||
{
|
||||
id: 'macos-download',
|
||||
label: 'macOS download URL',
|
||||
configured: readiness.macosDownloadConfigured,
|
||||
requiredForPublicLaunch: false,
|
||||
},
|
||||
{
|
||||
id: 'linux-download',
|
||||
label: 'Linux download URL',
|
||||
configured: readiness.linuxDownloadConfigured,
|
||||
requiredForPublicLaunch: false,
|
||||
},
|
||||
{
|
||||
id: 'operator-checkout',
|
||||
label: 'Operator checkout URL',
|
||||
configured: readiness.operatorCheckoutConfigured,
|
||||
requiredForPublicLaunch: true,
|
||||
},
|
||||
{
|
||||
id: 'studio-checkout',
|
||||
label: 'Studio checkout URL',
|
||||
configured: readiness.studioCheckoutConfigured,
|
||||
requiredForPublicLaunch: true,
|
||||
},
|
||||
{
|
||||
id: 'mpl-source',
|
||||
label: 'MPL corresponding-source URL',
|
||||
configured: readiness.mplSourceConfigured,
|
||||
requiredForPublicLaunch: true,
|
||||
},
|
||||
{
|
||||
id: 'open-source-notices',
|
||||
label: 'Open-source repo/notices URL',
|
||||
configured: readiness.openSourceRepoConfigured,
|
||||
requiredForPublicLaunch: true,
|
||||
},
|
||||
{
|
||||
id: 'billing-product-map',
|
||||
label: 'Billing product-plan map',
|
||||
configured: readiness.billingProductPlanMapConfigured,
|
||||
requiredForPublicLaunch: true,
|
||||
},
|
||||
{
|
||||
id: 'billing-price-map',
|
||||
label: 'Billing price-plan map',
|
||||
configured: readiness.billingPricePlanMapConfigured,
|
||||
requiredForPublicLaunch: true,
|
||||
},
|
||||
{
|
||||
id: 'billing-webhook-secret',
|
||||
label: 'Paddle webhook secret',
|
||||
configured: readiness.billingWebhookSecretConfigured,
|
||||
requiredForPublicLaunch: true,
|
||||
},
|
||||
{
|
||||
id: 'public-auth-runtime',
|
||||
label: 'Public auth runtime posture',
|
||||
configured: readiness.publicAuthRuntimeReady,
|
||||
requiredForPublicLaunch: true,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
export function getMissingPublicLaunchChecklistItems(
|
||||
readinessInput: PublicLaunchReadinessInput,
|
||||
options?: {
|
||||
requiredOnly?: boolean
|
||||
},
|
||||
): PublicLaunchChecklistItem[] {
|
||||
return getPublicLaunchChecklist(readinessInput).filter((item) => (
|
||||
!item.configured && (!options?.requiredOnly || item.requiredForPublicLaunch)
|
||||
))
|
||||
}
|
||||
|
||||
export function isPublicLaunchReady(readinessInput: PublicLaunchReadinessInput): boolean {
|
||||
return getMissingPublicLaunchChecklistItems(readinessInput, { requiredOnly: true }).length === 0
|
||||
}
|
||||
|
||||
export function resolveManifestPublicLaunchReadiness(
|
||||
manifest: ReleaseManifestLike | null | undefined,
|
||||
fallback?: PublicLaunchReadinessInput | null,
|
||||
): PublicLaunchReadiness {
|
||||
const normalizedFallback = normalizePublicLaunchReadiness(fallback)
|
||||
|
||||
const windowsDownloadConfigured = hasConfiguredPlatform(manifest, 'windows')
|
||||
const macosDownloadConfigured = hasConfiguredPlatform(manifest, 'macos')
|
||||
const linuxDownloadConfigured = hasConfiguredPlatform(manifest, 'linux')
|
||||
|
||||
return {
|
||||
windowsDownloadConfigured:
|
||||
windowsDownloadConfigured ?? normalizedFallback.windowsDownloadConfigured,
|
||||
macosDownloadConfigured:
|
||||
macosDownloadConfigured ?? normalizedFallback.macosDownloadConfigured,
|
||||
linuxDownloadConfigured:
|
||||
linuxDownloadConfigured ?? normalizedFallback.linuxDownloadConfigured,
|
||||
operatorCheckoutConfigured:
|
||||
manifest?.commerce?.operator_checkout_url != null
|
||||
? Boolean(manifest.commerce.operator_checkout_url)
|
||||
: normalizedFallback.operatorCheckoutConfigured,
|
||||
studioCheckoutConfigured:
|
||||
manifest?.commerce?.studio_checkout_url != null
|
||||
? Boolean(manifest.commerce.studio_checkout_url)
|
||||
: normalizedFallback.studioCheckoutConfigured,
|
||||
mplSourceConfigured:
|
||||
manifest?.corresponding_source_url != null
|
||||
? Boolean(manifest.corresponding_source_url)
|
||||
: normalizedFallback.mplSourceConfigured,
|
||||
openSourceRepoConfigured:
|
||||
manifest?.open_source_repo_url != null
|
||||
? Boolean(manifest.open_source_repo_url)
|
||||
: normalizedFallback.openSourceRepoConfigured,
|
||||
billingProductPlanMapConfigured: normalizedFallback.billingProductPlanMapConfigured,
|
||||
billingPricePlanMapConfigured: normalizedFallback.billingPricePlanMapConfigured,
|
||||
billingWebhookSecretConfigured: normalizedFallback.billingWebhookSecretConfigured,
|
||||
publicAuthRuntimeReady: normalizedFallback.publicAuthRuntimeReady,
|
||||
}
|
||||
}
|
||||
|
||||
export function resolvePublicLaunchTargets(
|
||||
manifest: ReleaseManifestLike | null | undefined,
|
||||
health?: AuthHealthLike | null,
|
||||
): PublicLaunchTargets {
|
||||
return {
|
||||
operatorCheckoutTarget:
|
||||
health?.launch?.targets?.operatorCheckoutTarget
|
||||
?? manifest?.commerce?.operator_checkout_url
|
||||
?? null,
|
||||
studioCheckoutTarget:
|
||||
health?.launch?.targets?.studioCheckoutTarget
|
||||
?? manifest?.commerce?.studio_checkout_url
|
||||
?? null,
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveHealthPublicLaunchReadiness({
|
||||
manifest,
|
||||
health,
|
||||
fallback,
|
||||
}: {
|
||||
manifest?: ReleaseManifestLike | null
|
||||
health?: AuthHealthLike | null
|
||||
fallback?: PublicLaunchReadinessInput | null
|
||||
}): PublicLaunchReadiness {
|
||||
const manifestReadiness = resolveManifestPublicLaunchReadiness(manifest, fallback)
|
||||
const healthReadiness = normalizePublicLaunchReadiness({
|
||||
...manifestReadiness,
|
||||
billingProductPlanMapConfigured: health?.billing?.productPlanMapConfigured,
|
||||
billingPricePlanMapConfigured: health?.billing?.pricePlanMapConfigured,
|
||||
billingWebhookSecretConfigured: health?.billing?.webhookSecretConfigured,
|
||||
publicAuthRuntimeReady: health?.runtime?.public_origin_ready,
|
||||
...health?.launch?.readiness,
|
||||
})
|
||||
|
||||
return healthReadiness
|
||||
}
|
||||
|
||||
export function buildPublicLaunchStatusSummary(
|
||||
readinessInput: PublicLaunchReadinessInput,
|
||||
targets: PublicLaunchTargets = {
|
||||
operatorCheckoutTarget: null,
|
||||
studioCheckoutTarget: null,
|
||||
},
|
||||
): PublicLaunchStatusSummary {
|
||||
const readiness = normalizePublicLaunchReadiness(readinessInput)
|
||||
const ready = isPublicLaunchReady(readiness)
|
||||
|
||||
return {
|
||||
posture: ready ? 'launch-ready' : 'preview',
|
||||
ready,
|
||||
readiness,
|
||||
checklist: getPublicLaunchChecklist(readiness),
|
||||
missingLabels: getMissingPublicLaunchChecklistItems(readiness, {
|
||||
requiredOnly: true,
|
||||
}).map((item) => item.label),
|
||||
targets,
|
||||
}
|
||||
}
|
||||
|
||||
export function resolvePublicLaunchStatusSummary({
|
||||
manifest,
|
||||
health,
|
||||
fallback,
|
||||
}: {
|
||||
manifest?: ReleaseManifestLike | null
|
||||
health?: AuthHealthLike | null
|
||||
fallback?: PublicLaunchReadinessInput | null
|
||||
}): PublicLaunchStatusSummary {
|
||||
return buildPublicLaunchStatusSummary(
|
||||
resolveHealthPublicLaunchReadiness({
|
||||
manifest,
|
||||
health,
|
||||
fallback,
|
||||
}),
|
||||
resolvePublicLaunchTargets(manifest, health),
|
||||
)
|
||||
}
|
||||
|
|
@ -126,10 +126,15 @@ export const launchReadiness = {
|
|||
operatorCheckoutConfigured: Boolean(operatorCheckoutUrl),
|
||||
studioCheckoutConfigured: Boolean(studioCheckoutUrl),
|
||||
windowsDownloadConfigured: Boolean(downloadTargets.find((target) => target.platformKey === 'windows')?.configured),
|
||||
macosDownloadConfigured: Boolean(downloadTargets.find((target) => target.platformKey === 'macos')?.configured),
|
||||
macDownloadConfigured: Boolean(downloadTargets.find((target) => target.platformKey === 'macos')?.configured),
|
||||
linuxDownloadConfigured: Boolean(downloadTargets.find((target) => target.platformKey === 'linux')?.configured),
|
||||
mplSourceConfigured: Boolean(mplSourceUrl),
|
||||
openSourceRepoConfigured: Boolean(openSourceRepoUrl),
|
||||
billingProductPlanMapConfigured: false,
|
||||
billingPricePlanMapConfigured: false,
|
||||
billingWebhookSecretConfigured: false,
|
||||
publicAuthRuntimeReady: false,
|
||||
} as const
|
||||
|
||||
export const paddleReadyDescription =
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue