@magnet-cms/plugin-polar 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,140 @@
1
+ import { EnvVarRequirement, PluginMagnetProvider } from '@magnet-cms/common';
2
+
3
+ /**
4
+ * Configuration for the Polar plugin.
5
+ *
6
+ * Passed via `PluginModule.forRoot({ plugins: [{ plugin: PolarPlugin, options: { ... } }] })`
7
+ * and injected in services via `@InjectPluginOptions('polar')`.
8
+ */
9
+ interface PolarPluginConfig {
10
+ /** Polar access token (polar_at_...). Auto-resolved from POLAR_ACCESS_TOKEN env var. */
11
+ accessToken?: string;
12
+ /** Polar webhook signing secret. Auto-resolved from POLAR_WEBHOOK_SECRET env var. */
13
+ webhookSecret?: string;
14
+ /** Polar organization ID. Auto-resolved from POLAR_ORGANIZATION_ID env var. */
15
+ organizationId?: string;
16
+ /** Whether to sync products from Polar via webhooks (default: true) */
17
+ syncProducts?: boolean;
18
+ /** Default currency for checkout sessions (default: 'usd') */
19
+ currency?: string;
20
+ /**
21
+ * Feature flags per plan name.
22
+ * Map of product/plan name to list of feature identifiers.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * {
27
+ * 'pro': ['unlimited-servers', 'priority-support'],
28
+ * 'basic': ['5-servers'],
29
+ * }
30
+ * ```
31
+ */
32
+ features?: Record<string, string[]>;
33
+ }
34
+ /**
35
+ * Response from the subscription access endpoint.
36
+ */
37
+ interface SubscriptionAccessResponse {
38
+ /** Whether the user has an active subscription */
39
+ hasActiveSubscription: boolean;
40
+ /** Plan/product name (null if no subscription) */
41
+ plan: string | null;
42
+ /** When the current period ends (null if no subscription) */
43
+ expiresAt: Date | null;
44
+ /** Feature flags for the user's plan */
45
+ features: string[];
46
+ }
47
+ /**
48
+ * Request body for creating a checkout session.
49
+ */
50
+ interface CreateCheckoutDto {
51
+ /** List of Polar Product IDs to offer at checkout */
52
+ products: string[];
53
+ /** URL to redirect to on success */
54
+ successUrl?: string;
55
+ /** URL to redirect to when customer clicks back */
56
+ returnUrl?: string;
57
+ /** Optional customer email to pre-fill */
58
+ customerEmail?: string;
59
+ /** Optional Magnet user ID to associate with the customer via externalCustomerId */
60
+ userId?: string;
61
+ /** Optional metadata to attach to the checkout session */
62
+ metadata?: Record<string, string>;
63
+ }
64
+ /**
65
+ * Request body for creating a customer portal session.
66
+ */
67
+ interface CreatePortalDto {
68
+ /** Polar Customer ID */
69
+ customerId?: string;
70
+ /** Magnet user ID (used to look up customer if customerId not provided) */
71
+ userId?: string;
72
+ /** URL to redirect to when the user is done */
73
+ returnUrl?: string;
74
+ }
75
+ /**
76
+ * Response from creating a checkout or portal session.
77
+ */
78
+ interface SessionResponse {
79
+ /** Session ID */
80
+ sessionId: string;
81
+ /** URL to redirect the user to */
82
+ url: string;
83
+ }
84
+ /**
85
+ * Admin metrics response from the dashboard endpoint.
86
+ */
87
+ interface PolarMetricsResponse {
88
+ /** Monthly Recurring Revenue in cents */
89
+ mrr: number;
90
+ /** Total revenue this month in cents */
91
+ revenueThisMonth: number;
92
+ /** Number of active subscriptions */
93
+ activeSubscriptions: number;
94
+ /** Churn rate as a percentage (0-100) */
95
+ churnRate: number;
96
+ /** Revenue data for the last 12 months */
97
+ revenueByMonth: Array<{
98
+ month: string;
99
+ revenue: number;
100
+ }>;
101
+ /** Recent orders */
102
+ recentOrders: Array<{
103
+ id: string;
104
+ totalAmount: number;
105
+ currency: string;
106
+ status: string;
107
+ customerEmail: string;
108
+ createdAt: string;
109
+ }>;
110
+ }
111
+
112
+ /**
113
+ * Polar Plugin
114
+ *
115
+ * Provides Polar.sh payment integration for Magnet CMS:
116
+ * - Product, subscription, and customer management
117
+ * - Webhook processing with idempotency
118
+ * - Checkout and Customer Portal sessions
119
+ * - Admin dashboard with revenue metrics
120
+ * - User subscription access and feature flags
121
+ * - Benefit management (license keys, downloads, etc.)
122
+ */
123
+ declare class PolarPlugin {
124
+ /** Environment variables used by this plugin */
125
+ static readonly envVars: EnvVarRequirement[];
126
+ /**
127
+ * Create a configured plugin provider for MagnetModule.forRoot().
128
+ * Auto-resolves secret values from environment variables if not provided.
129
+ *
130
+ * @example
131
+ * ```typescript
132
+ * MagnetModule.forRoot([
133
+ * PolarPlugin.forRoot({ currency: 'usd' }),
134
+ * ])
135
+ * ```
136
+ */
137
+ static forRoot(config?: Partial<PolarPluginConfig>): PluginMagnetProvider;
138
+ }
139
+
140
+ export { type CreateCheckoutDto, type CreatePortalDto, type PolarMetricsResponse, PolarPlugin, type PolarPluginConfig, type SessionResponse, type SubscriptionAccessResponse };
@@ -0,0 +1,140 @@
1
+ import { EnvVarRequirement, PluginMagnetProvider } from '@magnet-cms/common';
2
+
3
+ /**
4
+ * Configuration for the Polar plugin.
5
+ *
6
+ * Passed via `PluginModule.forRoot({ plugins: [{ plugin: PolarPlugin, options: { ... } }] })`
7
+ * and injected in services via `@InjectPluginOptions('polar')`.
8
+ */
9
+ interface PolarPluginConfig {
10
+ /** Polar access token (polar_at_...). Auto-resolved from POLAR_ACCESS_TOKEN env var. */
11
+ accessToken?: string;
12
+ /** Polar webhook signing secret. Auto-resolved from POLAR_WEBHOOK_SECRET env var. */
13
+ webhookSecret?: string;
14
+ /** Polar organization ID. Auto-resolved from POLAR_ORGANIZATION_ID env var. */
15
+ organizationId?: string;
16
+ /** Whether to sync products from Polar via webhooks (default: true) */
17
+ syncProducts?: boolean;
18
+ /** Default currency for checkout sessions (default: 'usd') */
19
+ currency?: string;
20
+ /**
21
+ * Feature flags per plan name.
22
+ * Map of product/plan name to list of feature identifiers.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * {
27
+ * 'pro': ['unlimited-servers', 'priority-support'],
28
+ * 'basic': ['5-servers'],
29
+ * }
30
+ * ```
31
+ */
32
+ features?: Record<string, string[]>;
33
+ }
34
+ /**
35
+ * Response from the subscription access endpoint.
36
+ */
37
+ interface SubscriptionAccessResponse {
38
+ /** Whether the user has an active subscription */
39
+ hasActiveSubscription: boolean;
40
+ /** Plan/product name (null if no subscription) */
41
+ plan: string | null;
42
+ /** When the current period ends (null if no subscription) */
43
+ expiresAt: Date | null;
44
+ /** Feature flags for the user's plan */
45
+ features: string[];
46
+ }
47
+ /**
48
+ * Request body for creating a checkout session.
49
+ */
50
+ interface CreateCheckoutDto {
51
+ /** List of Polar Product IDs to offer at checkout */
52
+ products: string[];
53
+ /** URL to redirect to on success */
54
+ successUrl?: string;
55
+ /** URL to redirect to when customer clicks back */
56
+ returnUrl?: string;
57
+ /** Optional customer email to pre-fill */
58
+ customerEmail?: string;
59
+ /** Optional Magnet user ID to associate with the customer via externalCustomerId */
60
+ userId?: string;
61
+ /** Optional metadata to attach to the checkout session */
62
+ metadata?: Record<string, string>;
63
+ }
64
+ /**
65
+ * Request body for creating a customer portal session.
66
+ */
67
+ interface CreatePortalDto {
68
+ /** Polar Customer ID */
69
+ customerId?: string;
70
+ /** Magnet user ID (used to look up customer if customerId not provided) */
71
+ userId?: string;
72
+ /** URL to redirect to when the user is done */
73
+ returnUrl?: string;
74
+ }
75
+ /**
76
+ * Response from creating a checkout or portal session.
77
+ */
78
+ interface SessionResponse {
79
+ /** Session ID */
80
+ sessionId: string;
81
+ /** URL to redirect the user to */
82
+ url: string;
83
+ }
84
+ /**
85
+ * Admin metrics response from the dashboard endpoint.
86
+ */
87
+ interface PolarMetricsResponse {
88
+ /** Monthly Recurring Revenue in cents */
89
+ mrr: number;
90
+ /** Total revenue this month in cents */
91
+ revenueThisMonth: number;
92
+ /** Number of active subscriptions */
93
+ activeSubscriptions: number;
94
+ /** Churn rate as a percentage (0-100) */
95
+ churnRate: number;
96
+ /** Revenue data for the last 12 months */
97
+ revenueByMonth: Array<{
98
+ month: string;
99
+ revenue: number;
100
+ }>;
101
+ /** Recent orders */
102
+ recentOrders: Array<{
103
+ id: string;
104
+ totalAmount: number;
105
+ currency: string;
106
+ status: string;
107
+ customerEmail: string;
108
+ createdAt: string;
109
+ }>;
110
+ }
111
+
112
+ /**
113
+ * Polar Plugin
114
+ *
115
+ * Provides Polar.sh payment integration for Magnet CMS:
116
+ * - Product, subscription, and customer management
117
+ * - Webhook processing with idempotency
118
+ * - Checkout and Customer Portal sessions
119
+ * - Admin dashboard with revenue metrics
120
+ * - User subscription access and feature flags
121
+ * - Benefit management (license keys, downloads, etc.)
122
+ */
123
+ declare class PolarPlugin {
124
+ /** Environment variables used by this plugin */
125
+ static readonly envVars: EnvVarRequirement[];
126
+ /**
127
+ * Create a configured plugin provider for MagnetModule.forRoot().
128
+ * Auto-resolves secret values from environment variables if not provided.
129
+ *
130
+ * @example
131
+ * ```typescript
132
+ * MagnetModule.forRoot([
133
+ * PolarPlugin.forRoot({ currency: 'usd' }),
134
+ * ])
135
+ * ```
136
+ */
137
+ static forRoot(config?: Partial<PolarPluginConfig>): PluginMagnetProvider;
138
+ }
139
+
140
+ export { type CreateCheckoutDto, type CreatePortalDto, type PolarMetricsResponse, PolarPlugin, type PolarPluginConfig, type SessionResponse, type SubscriptionAccessResponse };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export { PolarPlugin } from './chunk-7KT7JQIH.js';
package/package.json ADDED
@@ -0,0 +1,83 @@
1
+ {
2
+ "name": "@magnet-cms/plugin-polar",
3
+ "version": "0.1.0",
4
+ "description": "Polar.sh payments plugin for Magnet CMS — products, subscriptions, webhooks, and admin dashboard",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "exports": {
9
+ ".": {
10
+ "import": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ },
14
+ "require": {
15
+ "types": "./dist/index.d.cts",
16
+ "default": "./dist/index.cjs"
17
+ }
18
+ },
19
+ "./backend": {
20
+ "import": {
21
+ "types": "./dist/backend/index.d.ts",
22
+ "default": "./dist/backend/index.js"
23
+ },
24
+ "require": {
25
+ "types": "./dist/backend/index.d.cts",
26
+ "default": "./dist/backend/index.cjs"
27
+ }
28
+ },
29
+ "./frontend": {
30
+ "import": "./src/admin/index.ts",
31
+ "types": "./src/admin/index.ts"
32
+ }
33
+ },
34
+ "types": "./dist/index.d.ts",
35
+ "files": [
36
+ "dist",
37
+ "src/admin"
38
+ ],
39
+ "scripts": {
40
+ "build:dev": "tsup --watch",
41
+ "build": "tsup && bun run build:frontend",
42
+ "build:frontend": "vite build",
43
+ "build:frontend:watch": "vite build --watch"
44
+ },
45
+ "devDependencies": {
46
+ "@magnet-cms/admin": "workspace:*",
47
+ "@magnet-cms/common": "workspace:*",
48
+ "@magnet-cms/core": "workspace:*",
49
+ "@magnet-cms/ui": "workspace:*",
50
+ "@magnet-cms/utils": "workspace:*",
51
+ "@nestjs/common": "^11.1.12",
52
+ "@polar-sh/sdk": "0.46.4",
53
+ "@repo/biome": "workspace:*",
54
+ "@repo/tsup": "workspace:*",
55
+ "@repo/typescript-config": "workspace:*",
56
+ "@types/react": "^19.0.1",
57
+ "@vitejs/plugin-react": "^4.3.4",
58
+ "lucide-react": "^0.468.0",
59
+ "react": "^19.0.0",
60
+ "react-router-dom": "^7.1.1",
61
+ "recharts": "^2.15.0",
62
+ "reflect-metadata": "0.2.2",
63
+ "vite": "^6.0.7"
64
+ },
65
+ "peerDependencies": {
66
+ "@magnet-cms/admin": "^0.1.2",
67
+ "@magnet-cms/common": "^0.1.0",
68
+ "@magnet-cms/core": "^1.0.3",
69
+ "@magnet-cms/ui": "^0.1.0",
70
+ "@magnet-cms/utils": "^0.1.0",
71
+ "@nestjs/common": "^11.1.12",
72
+ "@polar-sh/sdk": "^0.46.0",
73
+ "lucide-react": ">=0.400.0",
74
+ "react": ">=18.0.0",
75
+ "react-router-dom": ">=6.0.0",
76
+ "reflect-metadata": "0.2.2"
77
+ },
78
+ "magnet": {
79
+ "type": "plugin",
80
+ "backend": true,
81
+ "frontend": true
82
+ }
83
+ }
@@ -0,0 +1,80 @@
1
+ import {
2
+ Badge,
3
+ DataTable,
4
+ type DataTableColumn,
5
+ } from '@magnet-cms/ui/components'
6
+
7
+ interface RecentOrder {
8
+ id: string
9
+ totalAmount: number
10
+ currency: string
11
+ status: string
12
+ customerEmail: string
13
+ createdAt: string
14
+ }
15
+
16
+ interface RecentOrdersProps {
17
+ orders: RecentOrder[]
18
+ }
19
+
20
+ function formatCurrency(cents: number, currency: string): string {
21
+ return `${currency.toUpperCase()} $${(cents / 100).toFixed(2)}`
22
+ }
23
+
24
+ function getStatusVariant(
25
+ status: string,
26
+ ): 'default' | 'secondary' | 'destructive' {
27
+ switch (status) {
28
+ case 'paid':
29
+ return 'default'
30
+ case 'refunded':
31
+ return 'destructive'
32
+ default:
33
+ return 'secondary'
34
+ }
35
+ }
36
+
37
+ const columns: DataTableColumn<RecentOrder>[] = [
38
+ {
39
+ type: 'text',
40
+ header: 'Amount',
41
+ accessorKey: 'totalAmount',
42
+ format: (_value, row) => formatCurrency(row.totalAmount, row.currency),
43
+ },
44
+ {
45
+ type: 'custom',
46
+ header: 'Status',
47
+ cell: (row) => (
48
+ <Badge variant={getStatusVariant(row.original.status)}>
49
+ {row.original.status}
50
+ </Badge>
51
+ ),
52
+ },
53
+ {
54
+ type: 'text',
55
+ header: 'Customer',
56
+ accessorKey: 'customerEmail',
57
+ format: (value) => (
58
+ <span className="font-mono text-sm">{value as string}</span>
59
+ ),
60
+ },
61
+ {
62
+ type: 'text',
63
+ header: 'Date',
64
+ accessorKey: 'createdAt',
65
+ format: (value) => new Date(value as string).toLocaleDateString(),
66
+ },
67
+ ]
68
+
69
+ export function RecentOrders({ orders }: RecentOrdersProps) {
70
+ return (
71
+ <DataTable
72
+ data={orders}
73
+ columns={columns}
74
+ getRowId={(row) => row.id}
75
+ showCount={false}
76
+ className="border rounded-lg"
77
+ variant="content-manager"
78
+ />
79
+ )
80
+ }
@@ -0,0 +1,56 @@
1
+ import {
2
+ ChartContainer,
3
+ ChartTooltip,
4
+ ChartTooltipContent,
5
+ } from '@magnet-cms/ui/components'
6
+ import type { ChartConfig } from '@magnet-cms/ui/components'
7
+ import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from 'recharts'
8
+
9
+ interface RevenueChartProps {
10
+ data: Array<{ month: string; revenue: number }>
11
+ }
12
+
13
+ const chartConfig: ChartConfig = {
14
+ revenue: {
15
+ label: 'Revenue',
16
+ color: 'hsl(var(--chart-1))',
17
+ },
18
+ }
19
+
20
+ function formatCurrency(cents: number): string {
21
+ return `$${(cents / 100).toFixed(0)}`
22
+ }
23
+
24
+ export function RevenueChart({ data }: RevenueChartProps) {
25
+ return (
26
+ <ChartContainer config={chartConfig} className="h-[300px] w-full">
27
+ <BarChart data={data}>
28
+ <CartesianGrid strokeDasharray="3 3" vertical={false} />
29
+ <XAxis
30
+ dataKey="month"
31
+ tickLine={false}
32
+ axisLine={false}
33
+ fontSize={12}
34
+ />
35
+ <YAxis
36
+ tickLine={false}
37
+ axisLine={false}
38
+ fontSize={12}
39
+ tickFormatter={formatCurrency}
40
+ />
41
+ <ChartTooltip
42
+ content={
43
+ <ChartTooltipContent
44
+ formatter={(value) => formatCurrency(Number(value))}
45
+ />
46
+ }
47
+ />
48
+ <Bar
49
+ dataKey="revenue"
50
+ fill="var(--color-revenue)"
51
+ radius={[4, 4, 0, 0]}
52
+ />
53
+ </BarChart>
54
+ </ChartContainer>
55
+ )
56
+ }
@@ -0,0 +1,65 @@
1
+ import { Card } from '@magnet-cms/ui/components'
2
+ import { CreditCard, DollarSign, TrendingDown, Users } from 'lucide-react'
3
+
4
+ interface MetricCardProps {
5
+ title: string
6
+ value: string
7
+ icon: React.ReactNode
8
+ }
9
+
10
+ function MetricCard({ title, value, icon }: MetricCardProps) {
11
+ return (
12
+ <Card className="p-6">
13
+ <div className="flex items-center justify-between">
14
+ <div>
15
+ <p className="text-sm font-medium text-muted-foreground">{title}</p>
16
+ <p className="text-2xl font-bold mt-1">{value}</p>
17
+ </div>
18
+ <div className="text-muted-foreground">{icon}</div>
19
+ </div>
20
+ </Card>
21
+ )
22
+ }
23
+
24
+ interface SubscriptionMetricsProps {
25
+ mrr: number
26
+ revenueThisMonth: number
27
+ activeSubscriptions: number
28
+ churnRate: number
29
+ }
30
+
31
+ function formatCurrency(cents: number): string {
32
+ return `$${(cents / 100).toLocaleString('en-US', { minimumFractionDigits: 2 })}`
33
+ }
34
+
35
+ export function SubscriptionMetrics({
36
+ mrr,
37
+ revenueThisMonth,
38
+ activeSubscriptions,
39
+ churnRate,
40
+ }: SubscriptionMetricsProps) {
41
+ return (
42
+ <div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
43
+ <MetricCard
44
+ title="Monthly Recurring Revenue"
45
+ value={formatCurrency(mrr)}
46
+ icon={<DollarSign className="h-5 w-5" />}
47
+ />
48
+ <MetricCard
49
+ title="Revenue This Month"
50
+ value={formatCurrency(revenueThisMonth)}
51
+ icon={<CreditCard className="h-5 w-5" />}
52
+ />
53
+ <MetricCard
54
+ title="Active Subscriptions"
55
+ value={String(activeSubscriptions)}
56
+ icon={<Users className="h-5 w-5" />}
57
+ />
58
+ <MetricCard
59
+ title="Churn Rate"
60
+ value={`${churnRate}%`}
61
+ icon={<TrendingDown className="h-5 w-5" />}
62
+ />
63
+ </div>
64
+ )
65
+ }
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Polar Plugin - Frontend Entry
3
+ *
4
+ * Self-registers the plugin when loaded via script injection.
5
+ * The admin app loads plugin bundles at runtime and plugins self-register
6
+ * on window.__MAGNET_PLUGINS__.
7
+ */
8
+
9
+ import type { ComponentType } from 'react'
10
+
11
+ interface FrontendPluginManifest {
12
+ pluginName: string
13
+ routes?: {
14
+ path: string
15
+ componentId: string
16
+ children?: { path: string; componentId: string }[]
17
+ }[]
18
+ sidebar?: {
19
+ id: string
20
+ title: string
21
+ url: string
22
+ icon: string
23
+ order?: number
24
+ items?: { id: string; title: string; url: string; icon: string }[]
25
+ }[]
26
+ }
27
+
28
+ interface PluginRegistration {
29
+ manifest: FrontendPluginManifest
30
+ components: Record<string, () => Promise<{ default: ComponentType<unknown> }>>
31
+ }
32
+
33
+ const manifest: FrontendPluginManifest = {
34
+ pluginName: 'polar',
35
+ routes: [
36
+ {
37
+ path: 'polar',
38
+ componentId: 'PolarDashboard',
39
+ children: [
40
+ { path: '', componentId: 'PolarDashboard' },
41
+ { path: 'customers', componentId: 'PolarCustomers' },
42
+ { path: 'products', componentId: 'PolarProducts' },
43
+ { path: 'subscriptions', componentId: 'PolarSubscriptions' },
44
+ { path: 'orders', componentId: 'PolarOrders' },
45
+ { path: 'benefits', componentId: 'PolarBenefits' },
46
+ ],
47
+ },
48
+ ],
49
+ sidebar: [
50
+ {
51
+ id: 'polar',
52
+ title: 'Polar Payments',
53
+ url: '/polar',
54
+ icon: 'Zap',
55
+ order: 31,
56
+ items: [
57
+ {
58
+ id: 'polar-dashboard',
59
+ title: 'Dashboard',
60
+ url: '/polar',
61
+ icon: 'BarChart3',
62
+ },
63
+ {
64
+ id: 'polar-customers',
65
+ title: 'Customers',
66
+ url: '/polar/customers',
67
+ icon: 'Users',
68
+ },
69
+ {
70
+ id: 'polar-products',
71
+ title: 'Products',
72
+ url: '/polar/products',
73
+ icon: 'Package',
74
+ },
75
+ {
76
+ id: 'polar-subscriptions',
77
+ title: 'Subscriptions',
78
+ url: '/polar/subscriptions',
79
+ icon: 'RefreshCw',
80
+ },
81
+ {
82
+ id: 'polar-orders',
83
+ title: 'Orders',
84
+ url: '/polar/orders',
85
+ icon: 'Receipt',
86
+ },
87
+ {
88
+ id: 'polar-benefits',
89
+ title: 'Benefits',
90
+ url: '/polar/benefits',
91
+ icon: 'Gift',
92
+ },
93
+ ],
94
+ },
95
+ ],
96
+ }
97
+
98
+ const components: Record<
99
+ string,
100
+ () => Promise<{ default: ComponentType<unknown> }>
101
+ > = {
102
+ PolarDashboard: () => import('./pages/polar-dashboard'),
103
+ PolarCustomers: () => import('./pages/customers'),
104
+ PolarProducts: () => import('./pages/products'),
105
+ PolarSubscriptions: () => import('./pages/subscriptions'),
106
+ PolarOrders: () => import('./pages/orders'),
107
+ PolarBenefits: () => import('./pages/benefits'),
108
+ }
109
+
110
+ function registerPlugin() {
111
+ if (!window.__MAGNET_PLUGINS__) {
112
+ window.__MAGNET_PLUGINS__ = []
113
+ }
114
+
115
+ const alreadyRegistered = window.__MAGNET_PLUGINS__.some(
116
+ (p) => p.manifest.pluginName === manifest.pluginName,
117
+ )
118
+
119
+ if (!alreadyRegistered) {
120
+ window.__MAGNET_PLUGINS__.push({ manifest, components })
121
+ console.log(`[Magnet] Plugin registered: ${manifest.pluginName}`)
122
+ }
123
+ }
124
+
125
+ registerPlugin()
126
+
127
+ export const polarPlugin = () => ({ manifest, components })
128
+ export default polarPlugin