Merge branch 'main' into codex/create-and-organize-test-files

This commit is contained in:
2025-12-27 18:58:23 +00:00
committed by GitHub
29 changed files with 1601 additions and 1383 deletions

View File

@@ -0,0 +1,8 @@
import type { DBALConfig } from '../runtime/config'
import { DBALClient } from './client/client'
export { buildAdapter, buildEntityOperations } from './client/builders'
export { normalizeClientConfig, validateClientConfig } from './client/mappers'
export const createDBALClient = (config: DBALConfig) => new DBALClient(config)
export { DBALClient }

View File

@@ -0,0 +1,24 @@
import type { DBALAdapter } from '../../adapters/adapter'
import type { DBALConfig } from '../../runtime/config'
import { createAdapter } from './adapter-factory'
import {
createComponentOperations,
createLuaScriptOperations,
createPackageOperations,
createPageOperations,
createSessionOperations,
createUserOperations,
createWorkflowOperations
} from '../entities'
export const buildAdapter = (config: DBALConfig): DBALAdapter => createAdapter(config)
export const buildEntityOperations = (adapter: DBALAdapter) => ({
users: createUserOperations(adapter),
pages: createPageOperations(adapter),
components: createComponentOperations(adapter),
workflows: createWorkflowOperations(adapter),
luaScripts: createLuaScriptOperations(adapter),
packages: createPackageOperations(adapter),
sessions: createSessionOperations(adapter)
})

View File

@@ -1,7 +1,7 @@
/**
* @file client.ts
* @description DBAL Client - Main interface for database operations
*
*
* Provides CRUD operations for all entities through modular operation handlers.
* Each entity type has its own dedicated operations module following the
* single-responsibility pattern.
@@ -9,82 +9,67 @@
import type { DBALConfig } from '../../runtime/config'
import type { DBALAdapter } from '../../adapters/adapter'
import { createAdapter } from './adapter-factory'
import {
createUserOperations,
createPageOperations,
createComponentOperations,
createWorkflowOperations,
createLuaScriptOperations,
createPackageOperations,
createSessionOperations,
} from '../entities'
import { buildAdapter, buildEntityOperations } from './builders'
import { normalizeClientConfig, validateClientConfig } from './mappers'
export class DBALClient {
private adapter: DBALAdapter
private config: DBALConfig
private operations: ReturnType<typeof buildEntityOperations>
constructor(config: DBALConfig) {
this.config = config
// Validate configuration
if (!config.adapter) {
throw new Error('Adapter type must be specified')
}
if (config.mode !== 'production' && !config.database?.url) {
throw new Error('Database URL must be specified for non-production mode')
}
this.adapter = createAdapter(config)
this.config = normalizeClientConfig(validateClientConfig(config))
this.adapter = buildAdapter(this.config)
this.operations = buildEntityOperations(this.adapter)
}
/**
* User entity operations
*/
get users() {
return createUserOperations(this.adapter)
return this.operations.users
}
/**
* Page entity operations
*/
get pages() {
return createPageOperations(this.adapter)
return this.operations.pages
}
/**
* Component hierarchy entity operations
*/
get components() {
return createComponentOperations(this.adapter)
return this.operations.components
}
/**
* Workflow entity operations
*/
get workflows() {
return createWorkflowOperations(this.adapter)
return this.operations.workflows
}
/**
* Lua script entity operations
*/
get luaScripts() {
return createLuaScriptOperations(this.adapter)
return this.operations.luaScripts
}
/**
* Package entity operations
*/
get packages() {
return createPackageOperations(this.adapter)
return this.operations.packages
}
/**
* Session entity operations
*/
get sessions() {
return createSessionOperations(this.adapter)
return this.operations.sessions
}
/**

View File

@@ -0,0 +1,25 @@
import type { DBALConfig } from '../../runtime/config'
import { DBALError } from '../foundation/errors'
export const validateClientConfig = (config: DBALConfig): DBALConfig => {
if (!config.adapter) {
throw DBALError.validationError('Adapter type must be specified', [])
}
if (config.mode !== 'production' && !config.database?.url) {
throw DBALError.validationError('Database URL must be specified for non-production mode', [])
}
return config
}
export const normalizeClientConfig = (config: DBALConfig): DBALConfig => ({
...config,
security: {
sandbox: config.security?.sandbox ?? 'strict',
enableAuditLog: config.security?.enableAuditLog ?? true
},
performance: {
...config.performance
}
})

View File

@@ -1,4 +1,4 @@
export { DBALClient } from './core/client/client'
export { DBALClient, createDBALClient } from './core/client'
export type { DBALConfig } from './runtime/config'
export type * from './core/foundation/types'
export { DBALError, DBALErrorCode } from './core/foundation/errors'

View File

@@ -1,5 +1,52 @@
import { describe, it, expect } from 'vitest'
import { summarizeWorkflowRuns } from './analyze-workflow-runs'
import {
analyzeWorkflowRuns,
parseWorkflowRuns,
summarizeWorkflowRuns,
} from './analyze-workflow-runs'
describe('parseWorkflowRuns', () => {
it('normalizes unknown entries and ignores items without numeric IDs', () => {
const runs = [
{
id: 1,
name: 'Build',
status: 'completed',
conclusion: 'success',
created_at: '2024-01-01T00:00:00Z',
updated_at: '2024-01-01T00:10:00Z',
head_branch: 'main',
event: 'push',
},
{ id: 'not-a-number' },
{
id: 2,
name: '',
status: '',
conclusion: 'failure',
created_at: '',
updated_at: '',
head_branch: '',
event: '',
},
]
const parsed = parseWorkflowRuns(runs)
expect(parsed).toHaveLength(2)
expect(parsed[0].name).toBe('Build')
expect(parsed[1]).toEqual({
id: 2,
name: 'Unknown workflow',
status: 'unknown',
conclusion: 'failure',
created_at: '',
updated_at: '',
head_branch: 'unknown',
event: 'unknown',
})
})
})
describe('summarizeWorkflowRuns', () => {
it('summarizes totals, success rate, and failure hotspots', () => {
@@ -60,3 +107,24 @@ describe('summarizeWorkflowRuns', () => {
expect(summary.mostRecent).toBeNull()
})
})
describe('analyzeWorkflowRuns', () => {
it('returns parsed summary and formatted output', () => {
const result = analyzeWorkflowRuns([
{
id: 7,
name: 'Deploy',
status: 'completed',
conclusion: 'success',
created_at: '2024-02-01T00:00:00Z',
updated_at: '2024-02-01T00:05:00Z',
head_branch: 'main',
event: 'workflow_dispatch',
},
])
expect(result.summary.total).toBe(1)
expect(result.formatted).toContain('Workflow Run Analysis')
expect(result.formatted).toContain('Deploy')
})
})

View File

@@ -1,164 +1,18 @@
export type WorkflowRunLike = {
id: number
name: string
status: string
conclusion: string | null
created_at: string
updated_at: string
head_branch: string
event: string
}
import { parseWorkflowRuns, WorkflowRunLike } from './parser'
import { formatWorkflowRunAnalysis, summarizeWorkflowRuns, WorkflowRunSummary } from './stats'
export type WorkflowRunSummary = {
total: number
completed: number
successful: number
failed: number
cancelled: number
inProgress: number
successRate: number
mostRecent: WorkflowRunLike | null
recentRuns: WorkflowRunLike[]
topFailingWorkflows: Array<{ name: string; failures: number }>
failingBranches: Array<{ branch: string; failures: number }>
failingEvents: Array<{ event: string; failures: number }>
}
export type { WorkflowRunLike, WorkflowRunSummary }
export { parseWorkflowRuns, summarizeWorkflowRuns, formatWorkflowRunAnalysis }
const DEFAULT_RECENT_COUNT = 5
const DEFAULT_TOP_COUNT = 3
function toTopCounts(
values: string[],
topCount: number
): Array<{ key: string; count: number }> {
const counts = new Map<string, number>()
values.forEach((value) => {
counts.set(value, (counts.get(value) || 0) + 1)
})
return Array.from(counts.entries())
.map(([key, count]) => ({ key, count }))
.sort((a, b) => b.count - a.count || a.key.localeCompare(b.key))
.slice(0, topCount)
}
export function summarizeWorkflowRuns(
runs: WorkflowRunLike[],
export function analyzeWorkflowRuns(
runs: unknown[],
options?: { recentCount?: number; topCount?: number }
): WorkflowRunSummary {
const recentCount = options?.recentCount ?? DEFAULT_RECENT_COUNT
const topCount = options?.topCount ?? DEFAULT_TOP_COUNT
const total = runs.length
const completedRuns = runs.filter((run) => run.status === 'completed')
const successful = completedRuns.filter((run) => run.conclusion === 'success').length
const failed = completedRuns.filter((run) => run.conclusion === 'failure').length
const cancelled = completedRuns.filter((run) => run.conclusion === 'cancelled').length
const inProgress = total - completedRuns.length
const successRate = completedRuns.length
? Math.round((successful / completedRuns.length) * 100)
: 0
const sortedByUpdated = [...runs].sort(
(a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime()
)
const mostRecent = sortedByUpdated[0] ?? null
const recentRuns = sortedByUpdated.slice(0, recentCount)
const failureRuns = completedRuns.filter((run) => run.conclusion === 'failure')
const topFailingWorkflows = toTopCounts(
failureRuns.map((run) => run.name),
topCount
).map((entry) => ({ name: entry.key, failures: entry.count }))
const failingBranches = toTopCounts(
failureRuns.map((run) => run.head_branch),
topCount
).map((entry) => ({ branch: entry.key, failures: entry.count }))
const failingEvents = toTopCounts(
failureRuns.map((run) => run.event),
topCount
).map((entry) => ({ event: entry.key, failures: entry.count }))
) {
const parsedRuns = parseWorkflowRuns(runs)
const summary = summarizeWorkflowRuns(parsedRuns, options)
return {
total,
completed: completedRuns.length,
successful,
failed,
cancelled,
inProgress,
successRate,
mostRecent,
recentRuns,
topFailingWorkflows,
failingBranches,
failingEvents,
summary,
formatted: formatWorkflowRunAnalysis(summary),
}
}
export function formatWorkflowRunAnalysis(summary: WorkflowRunSummary) {
const lines: string[] = []
lines.push('Workflow Run Analysis')
lines.push('---------------------')
lines.push(`Total runs: ${summary.total}`)
lines.push(
`Completed: ${summary.completed} (success: ${summary.successful}, failed: ${summary.failed}, cancelled: ${summary.cancelled})`
)
lines.push(`In progress: ${summary.inProgress}`)
lines.push(`Success rate: ${summary.successRate}%`)
if (summary.mostRecent) {
lines.push('')
lines.push('Most recent run:')
lines.push(
`- ${summary.mostRecent.name} | ${summary.mostRecent.status}${
summary.mostRecent.conclusion ? `/${summary.mostRecent.conclusion}` : ''
} | ${summary.mostRecent.head_branch} | ${summary.mostRecent.updated_at}`
)
}
if (summary.recentRuns.length > 0) {
lines.push('')
lines.push('Recent runs:')
summary.recentRuns.forEach((run) => {
lines.push(
`- ${run.name} | ${run.status}${
run.conclusion ? `/${run.conclusion}` : ''
} | ${run.head_branch} | ${run.updated_at}`
)
})
}
if (summary.topFailingWorkflows.length > 0) {
lines.push('')
lines.push('Top failing workflows:')
summary.topFailingWorkflows.forEach((entry) => {
lines.push(`- ${entry.name}: ${entry.failures}`)
})
}
if (summary.failingBranches.length > 0) {
lines.push('')
lines.push('Failing branches:')
summary.failingBranches.forEach((entry) => {
lines.push(`- ${entry.branch}: ${entry.failures}`)
})
}
if (summary.failingEvents.length > 0) {
lines.push('')
lines.push('Failing events:')
summary.failingEvents.forEach((entry) => {
lines.push(`- ${entry.event}: ${entry.failures}`)
})
}
if (summary.total === 0) {
lines.push('')
lines.push('No workflow runs available to analyze.')
}
return lines.join('\n')
}

View File

@@ -0,0 +1,50 @@
export type WorkflowRunLike = {
id: number
name: string
status: string
conclusion: string | null
created_at: string
updated_at: string
head_branch: string
event: string
}
const FALLBACK_NAME = 'Unknown workflow'
const FALLBACK_STATUS = 'unknown'
const FALLBACK_BRANCH = 'unknown'
const FALLBACK_EVENT = 'unknown'
function toStringOrFallback(value: unknown, fallback: string) {
return typeof value === 'string' && value.trim() ? value : fallback
}
export function parseWorkflowRuns(runs: unknown[]): WorkflowRunLike[] {
if (!Array.isArray(runs)) {
return []
}
return runs
.map((run) => {
const candidate = run as Partial<WorkflowRunLike> & { id?: unknown }
const id = Number(candidate.id)
if (!Number.isFinite(id)) {
return null
}
return {
id,
name: toStringOrFallback(candidate.name, FALLBACK_NAME),
status: toStringOrFallback(candidate.status, FALLBACK_STATUS),
conclusion:
candidate.conclusion === null || typeof candidate.conclusion === 'string'
? candidate.conclusion
: null,
created_at: toStringOrFallback(candidate.created_at, ''),
updated_at: toStringOrFallback(candidate.updated_at, ''),
head_branch: toStringOrFallback(candidate.head_branch, FALLBACK_BRANCH),
event: toStringOrFallback(candidate.event, FALLBACK_EVENT),
}
})
.filter((run): run is WorkflowRunLike => Boolean(run))
}

View File

@@ -0,0 +1,153 @@
import { WorkflowRunLike } from './parser'
export type WorkflowRunSummary = {
total: number
completed: number
successful: number
failed: number
cancelled: number
inProgress: number
successRate: number
mostRecent: WorkflowRunLike | null
recentRuns: WorkflowRunLike[]
topFailingWorkflows: Array<{ name: string; failures: number }>
failingBranches: Array<{ branch: string; failures: number }>
failingEvents: Array<{ event: string; failures: number }>
}
const DEFAULT_RECENT_COUNT = 5
const DEFAULT_TOP_COUNT = 3
function toTopCounts(
values: string[],
topCount: number
): Array<{ key: string; count: number }> {
const counts = new Map<string, number>()
values.forEach((value) => {
counts.set(value, (counts.get(value) || 0) + 1)
})
return Array.from(counts.entries())
.map(([key, count]) => ({ key, count }))
.sort((a, b) => b.count - a.count || a.key.localeCompare(b.key))
.slice(0, topCount)
}
export function summarizeWorkflowRuns(
runs: WorkflowRunLike[],
options?: { recentCount?: number; topCount?: number }
): WorkflowRunSummary {
const recentCount = options?.recentCount ?? DEFAULT_RECENT_COUNT
const topCount = options?.topCount ?? DEFAULT_TOP_COUNT
const total = runs.length
const completedRuns = runs.filter((run) => run.status === 'completed')
const successful = completedRuns.filter((run) => run.conclusion === 'success').length
const failed = completedRuns.filter((run) => run.conclusion === 'failure').length
const cancelled = completedRuns.filter((run) => run.conclusion === 'cancelled').length
const inProgress = total - completedRuns.length
const successRate = completedRuns.length
? Math.round((successful / completedRuns.length) * 100)
: 0
const sortedByUpdated = [...runs].sort(
(a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime()
)
const mostRecent = sortedByUpdated[0] ?? null
const recentRuns = sortedByUpdated.slice(0, recentCount)
const failureRuns = completedRuns.filter((run) => run.conclusion === 'failure')
const topFailingWorkflows = toTopCounts(
failureRuns.map((run) => run.name),
topCount
).map((entry) => ({ name: entry.key, failures: entry.count }))
const failingBranches = toTopCounts(
failureRuns.map((run) => run.head_branch),
topCount
).map((entry) => ({ branch: entry.key, failures: entry.count }))
const failingEvents = toTopCounts(
failureRuns.map((run) => run.event),
topCount
).map((entry) => ({ event: entry.key, failures: entry.count }))
return {
total,
completed: completedRuns.length,
successful,
failed,
cancelled,
inProgress,
successRate,
mostRecent,
recentRuns,
topFailingWorkflows,
failingBranches,
failingEvents,
}
}
export function formatWorkflowRunAnalysis(summary: WorkflowRunSummary) {
const lines: string[] = []
lines.push('Workflow Run Analysis')
lines.push('---------------------')
lines.push(`Total runs: ${summary.total}`)
lines.push(
`Completed: ${summary.completed} (success: ${summary.successful}, failed: ${summary.failed}, cancelled: ${summary.cancelled})`
)
lines.push(`In progress: ${summary.inProgress}`)
lines.push(`Success rate: ${summary.successRate}%`)
if (summary.mostRecent) {
lines.push('')
lines.push('Most recent run:')
lines.push(
`- ${summary.mostRecent.name} | ${summary.mostRecent.status}${
summary.mostRecent.conclusion ? `/${summary.mostRecent.conclusion}` : ''
} | ${summary.mostRecent.head_branch} | ${summary.mostRecent.updated_at}`
)
}
if (summary.recentRuns.length > 0) {
lines.push('')
lines.push('Recent runs:')
summary.recentRuns.forEach((run) => {
lines.push(
`- ${run.name} | ${run.status}${run.conclusion ? `/${run.conclusion}` : ''} | ${run.head_branch} | ${run.updated_at}`
)
})
}
if (summary.topFailingWorkflows.length > 0) {
lines.push('')
lines.push('Top failing workflows:')
summary.topFailingWorkflows.forEach((entry) => {
lines.push(`- ${entry.name}: ${entry.failures}`)
})
}
if (summary.failingBranches.length > 0) {
lines.push('')
lines.push('Failing branches:')
summary.failingBranches.forEach((entry) => {
lines.push(`- ${entry.branch}: ${entry.failures}`)
})
}
if (summary.failingEvents.length > 0) {
lines.push('')
lines.push('Failing events:')
summary.failingEvents.forEach((entry) => {
lines.push(`- ${entry.event}: ${entry.failures}`)
})
}
if (summary.total === 0) {
lines.push('')
lines.push('No workflow runs available to analyze.')
}
return lines.join('\n')
}

View File

@@ -0,0 +1,3 @@
import type { PackageTemplateConfig } from '../../types'
export const ADVANCED_PACKAGE_TEMPLATE_CONFIGS: PackageTemplateConfig[] = []

View File

@@ -0,0 +1,267 @@
import type { PackageTemplateConfig, ReactAppTemplateConfig } from '../../types'
export const BASE_REACT_APP_TEMPLATE_CONFIG: ReactAppTemplateConfig = {
id: 'react_next_starter',
name: 'Next.js Web App',
description: 'A clean Next.js starter with app router, hero component, and typed config files.',
rootName: 'web_app',
tags: ['nextjs', 'react', 'web', 'starter'],
}
const socialHubComponents = [
{
id: 'social_hub_root',
type: 'Stack',
props: { className: 'flex flex-col gap-6' },
children: [
{
id: 'social_hub_hero',
type: 'Card',
props: { className: 'p-6' },
children: [
{
id: 'social_hub_heading',
type: 'Heading',
props: { children: 'Social Hub', level: '2', className: 'text-2xl font-bold' },
children: [],
},
{
id: 'social_hub_subtitle',
type: 'Text',
props: { children: 'A modern feed for creator updates, curated stories, and live moments.' },
children: [],
},
],
},
{
id: 'social_hub_stats',
type: 'Grid',
props: { className: 'grid grid-cols-3 gap-4' },
children: [
{
id: 'social_hub_stat_1',
type: 'Card',
props: { className: 'p-4' },
children: [
{
id: 'social_hub_stat_label_1',
type: 'Text',
props: { children: 'Creators live', className: 'text-sm text-muted-foreground' },
children: [],
},
{
id: 'social_hub_stat_value_1',
type: 'Heading',
props: { children: '128', level: '3', className: 'text-xl font-semibold' },
children: [],
},
],
},
{
id: 'social_hub_stat_2',
type: 'Card',
props: { className: 'p-4' },
children: [
{
id: 'social_hub_stat_label_2',
type: 'Text',
props: { children: 'Trending tags', className: 'text-sm text-muted-foreground' },
children: [],
},
{
id: 'social_hub_stat_value_2',
type: 'Heading',
props: { children: '42', level: '3', className: 'text-xl font-semibold' },
children: [],
},
],
},
{
id: 'social_hub_stat_3',
type: 'Card',
props: { className: 'p-4' },
children: [
{
id: 'social_hub_stat_label_3',
type: 'Text',
props: { children: 'Live rooms', className: 'text-sm text-muted-foreground' },
children: [],
},
{
id: 'social_hub_stat_value_3',
type: 'Heading',
props: { children: '7', level: '3', className: 'text-xl font-semibold' },
children: [],
},
],
},
],
},
{
id: 'social_hub_composer',
type: 'Card',
props: { className: 'p-4' },
children: [
{
id: 'social_hub_composer_label',
type: 'Label',
props: { children: 'Share a quick update' },
children: [],
},
{
id: 'social_hub_composer_input',
type: 'Textarea',
props: { placeholder: 'What are you building today?', rows: 3 },
children: [],
},
{
id: 'social_hub_composer_actions',
type: 'Flex',
props: { className: 'flex gap-2' },
children: [
{
id: 'social_hub_composer_publish',
type: 'Button',
props: { children: 'Publish', variant: 'default' },
children: [],
},
{
id: 'social_hub_composer_media',
type: 'Button',
props: { children: 'Add media', variant: 'outline' },
children: [],
},
],
},
],
},
{
id: 'social_hub_feed',
type: 'Stack',
props: { className: 'flex flex-col gap-4' },
children: [
{
id: 'social_hub_feed_post_1',
type: 'Card',
props: { className: 'p-5' },
children: [
{
id: 'social_hub_feed_post_1_title',
type: 'Heading',
props: { children: 'Launch day recap', level: '3', className: 'text-lg font-semibold' },
children: [],
},
{
id: 'social_hub_feed_post_1_body',
type: 'Text',
props: { children: 'We shipped the new live rooms and saw a 32% boost in engagement.' },
children: [],
},
{
id: 'social_hub_feed_post_1_badge',
type: 'Badge',
props: { children: 'Community' },
children: [],
},
],
},
{
id: 'social_hub_feed_post_2',
type: 'Card',
props: { className: 'p-5' },
children: [
{
id: 'social_hub_feed_post_2_title',
type: 'Heading',
props: { children: 'Creator spotlight', level: '3', className: 'text-lg font-semibold' },
children: [],
},
{
id: 'social_hub_feed_post_2_body',
type: 'Text',
props: { children: 'Nova shares her workflow for livestreaming and managing subscribers.' },
children: [],
},
{
id: 'social_hub_feed_post_2_badge',
type: 'Badge',
props: { children: 'Spotlight', variant: 'secondary' },
children: [],
},
],
},
],
},
],
},
]
const socialHubExamples = {
feedItems: [
{
id: 'post_001',
author: 'Nova',
title: 'Launch day recap',
summary: 'We shipped live rooms and doubled community sessions.',
tags: ['launch', 'community'],
},
{
id: 'post_002',
author: 'Kai',
title: 'Build log: day 42',
summary: 'Refined the moderation pipeline and added creator scorecards.',
tags: ['buildinpublic'],
},
],
trendingTags: ['#buildinpublic', '#metabuilder', '#live'],
rooms: [
{ id: 'room_1', title: 'Creator Q&A', host: 'Eli', live: true },
{ id: 'room_2', title: 'Patch Notes', host: 'Nova', live: false },
],
}
const socialHubLuaScripts = [
{
fileName: 'init.lua',
description: 'Lifecycle hooks for package installation.',
code: 'local M = {}\\n\\nfunction M.on_install(context)\\n return { message = "Social Hub installed", version = context.version }\\nend\\n\\nfunction M.on_uninstall()\\n return { message = "Social Hub removed" }\\nend\\n\\nreturn M',
},
{
fileName: 'permissions.lua',
description: 'Role-based access rules for posting and moderation.',
code: 'local Permissions = {}\\n\\nfunction Permissions.can_post(user)\\n return user and (user.role == "user" or user.role == "admin" or user.role == "god")\\nend\\n\\nfunction Permissions.can_moderate(user)\\n return user and (user.role == "admin" or user.role == "god" or user.role == "supergod")\\nend\\n\\nreturn Permissions',
},
{
fileName: 'feed_rank.lua',
description: 'Score feed items based on recency and engagement.',
code: 'local FeedRank = {}\\n\\nfunction FeedRank.score(item)\\n local freshness = item.age_minutes and (100 - item.age_minutes) or 50\\n local engagement = (item.likes or 0) * 2 + (item.comments or 0) * 3\\n return freshness + engagement\\nend\\n\\nreturn FeedRank',
},
{
fileName: 'moderation.lua',
description: 'Flag content for review using lightweight heuristics.',
code: 'local Moderation = {}\\n\\nfunction Moderation.flag(content)\\n local lowered = string.lower(content or "")\\n if string.find(lowered, "spam") then\\n return { flagged = true, reason = "spam_keyword" }\\n end\\n return { flagged = false }\\nend\\n\\nreturn Moderation',
},
{
fileName: 'analytics.lua',
description: 'Aggregate engagement signals for dashboards.',
code: 'local Analytics = {}\\n\\nfunction Analytics.aggregate(events)\\n local summary = { views = 0, likes = 0, comments = 0 }\\n for _, event in ipairs(events or {}) do\\n summary.views = summary.views + (event.views or 0)\\n summary.likes = summary.likes + (event.likes or 0)\\n summary.comments = summary.comments + (event.comments or 0)\\n end\\n return summary\\nend\\n\\nreturn Analytics',
},
]
export const BASE_PACKAGE_TEMPLATE_CONFIGS: PackageTemplateConfig[] = [
{
id: 'package_social_hub',
name: 'Social Hub Package',
description: 'A package blueprint for social feeds, creator updates, and live rooms.',
rootName: 'social_hub',
packageId: 'social_hub',
author: 'MetaBuilder',
version: '1.0.0',
category: 'social',
summary: 'Modern social feed with creator tools and live rooms.',
components: socialHubComponents,
examples: socialHubExamples,
luaScripts: socialHubLuaScripts,
tags: ['package', 'social', 'feed', 'lua'],
},
]

View File

@@ -0,0 +1,3 @@
import type { PackageTemplateConfig } from '../../types'
export const EXPERIMENTAL_PACKAGE_TEMPLATE_CONFIGS: PackageTemplateConfig[] = []

View File

@@ -1,267 +1,12 @@
import type { PackageTemplateConfig, ReactAppTemplateConfig } from './types'
import type { PackageTemplateConfig, ReactAppTemplateConfig } from '../types'
import { ADVANCED_PACKAGE_TEMPLATE_CONFIGS } from './configs/advanced'
import { BASE_PACKAGE_TEMPLATE_CONFIGS, BASE_REACT_APP_TEMPLATE_CONFIG } from './configs/base'
import { EXPERIMENTAL_PACKAGE_TEMPLATE_CONFIGS } from './configs/experimental'
export const REACT_APP_TEMPLATE_CONFIG: ReactAppTemplateConfig = {
id: 'react_next_starter',
name: 'Next.js Web App',
description: 'A clean Next.js starter with app router, hero component, and typed config files.',
rootName: 'web_app',
tags: ['nextjs', 'react', 'web', 'starter'],
}
const socialHubComponents = [
{
id: 'social_hub_root',
type: 'Stack',
props: { className: 'flex flex-col gap-6' },
children: [
{
id: 'social_hub_hero',
type: 'Card',
props: { className: 'p-6' },
children: [
{
id: 'social_hub_heading',
type: 'Heading',
props: { children: 'Social Hub', level: '2', className: 'text-2xl font-bold' },
children: [],
},
{
id: 'social_hub_subtitle',
type: 'Text',
props: { children: 'A modern feed for creator updates, curated stories, and live moments.' },
children: [],
},
],
},
{
id: 'social_hub_stats',
type: 'Grid',
props: { className: 'grid grid-cols-3 gap-4' },
children: [
{
id: 'social_hub_stat_1',
type: 'Card',
props: { className: 'p-4' },
children: [
{
id: 'social_hub_stat_label_1',
type: 'Text',
props: { children: 'Creators live', className: 'text-sm text-muted-foreground' },
children: [],
},
{
id: 'social_hub_stat_value_1',
type: 'Heading',
props: { children: '128', level: '3', className: 'text-xl font-semibold' },
children: [],
},
],
},
{
id: 'social_hub_stat_2',
type: 'Card',
props: { className: 'p-4' },
children: [
{
id: 'social_hub_stat_label_2',
type: 'Text',
props: { children: 'Trending tags', className: 'text-sm text-muted-foreground' },
children: [],
},
{
id: 'social_hub_stat_value_2',
type: 'Heading',
props: { children: '42', level: '3', className: 'text-xl font-semibold' },
children: [],
},
],
},
{
id: 'social_hub_stat_3',
type: 'Card',
props: { className: 'p-4' },
children: [
{
id: 'social_hub_stat_label_3',
type: 'Text',
props: { children: 'Live rooms', className: 'text-sm text-muted-foreground' },
children: [],
},
{
id: 'social_hub_stat_value_3',
type: 'Heading',
props: { children: '7', level: '3', className: 'text-xl font-semibold' },
children: [],
},
],
},
],
},
{
id: 'social_hub_composer',
type: 'Card',
props: { className: 'p-4' },
children: [
{
id: 'social_hub_composer_label',
type: 'Label',
props: { children: 'Share a quick update' },
children: [],
},
{
id: 'social_hub_composer_input',
type: 'Textarea',
props: { placeholder: 'What are you building today?', rows: 3 },
children: [],
},
{
id: 'social_hub_composer_actions',
type: 'Flex',
props: { className: 'flex gap-2' },
children: [
{
id: 'social_hub_composer_publish',
type: 'Button',
props: { children: 'Publish', variant: 'default' },
children: [],
},
{
id: 'social_hub_composer_media',
type: 'Button',
props: { children: 'Add media', variant: 'outline' },
children: [],
},
],
},
],
},
{
id: 'social_hub_feed',
type: 'Stack',
props: { className: 'flex flex-col gap-4' },
children: [
{
id: 'social_hub_feed_post_1',
type: 'Card',
props: { className: 'p-5' },
children: [
{
id: 'social_hub_feed_post_1_title',
type: 'Heading',
props: { children: 'Launch day recap', level: '3', className: 'text-lg font-semibold' },
children: [],
},
{
id: 'social_hub_feed_post_1_body',
type: 'Text',
props: { children: 'We shipped the new live rooms and saw a 32% boost in engagement.' },
children: [],
},
{
id: 'social_hub_feed_post_1_badge',
type: 'Badge',
props: { children: 'Community' },
children: [],
},
],
},
{
id: 'social_hub_feed_post_2',
type: 'Card',
props: { className: 'p-5' },
children: [
{
id: 'social_hub_feed_post_2_title',
type: 'Heading',
props: { children: 'Creator spotlight', level: '3', className: 'text-lg font-semibold' },
children: [],
},
{
id: 'social_hub_feed_post_2_body',
type: 'Text',
props: { children: 'Nova shares her workflow for livestreaming and managing subscribers.' },
children: [],
},
{
id: 'social_hub_feed_post_2_badge',
type: 'Badge',
props: { children: 'Spotlight', variant: 'secondary' },
children: [],
},
],
},
],
},
],
},
]
const socialHubExamples = {
feedItems: [
{
id: 'post_001',
author: 'Nova',
title: 'Launch day recap',
summary: 'We shipped live rooms and doubled community sessions.',
tags: ['launch', 'community'],
},
{
id: 'post_002',
author: 'Kai',
title: 'Build log: day 42',
summary: 'Refined the moderation pipeline and added creator scorecards.',
tags: ['buildinpublic'],
},
],
trendingTags: ['#buildinpublic', '#metabuilder', '#live'],
rooms: [
{ id: 'room_1', title: 'Creator Q&A', host: 'Eli', live: true },
{ id: 'room_2', title: 'Patch Notes', host: 'Nova', live: false },
],
}
const socialHubLuaScripts = [
{
fileName: 'init.lua',
description: 'Lifecycle hooks for package installation.',
code: 'local M = {}\n\nfunction M.on_install(context)\n return { message = "Social Hub installed", version = context.version }\nend\n\nfunction M.on_uninstall()\n return { message = "Social Hub removed" }\nend\n\nreturn M',
},
{
fileName: 'permissions.lua',
description: 'Role-based access rules for posting and moderation.',
code: 'local Permissions = {}\n\nfunction Permissions.can_post(user)\n return user and (user.role == "user" or user.role == "admin" or user.role == "god")\nend\n\nfunction Permissions.can_moderate(user)\n return user and (user.role == "admin" or user.role == "god" or user.role == "supergod")\nend\n\nreturn Permissions',
},
{
fileName: 'feed_rank.lua',
description: 'Score feed items based on recency and engagement.',
code: 'local FeedRank = {}\n\nfunction FeedRank.score(item)\n local freshness = item.age_minutes and (100 - item.age_minutes) or 50\n local engagement = (item.likes or 0) * 2 + (item.comments or 0) * 3\n return freshness + engagement\nend\n\nreturn FeedRank',
},
{
fileName: 'moderation.lua',
description: 'Flag content for review using lightweight heuristics.',
code: 'local Moderation = {}\n\nfunction Moderation.flag(content)\n local lowered = string.lower(content or "")\n if string.find(lowered, "spam") then\n return { flagged = true, reason = "spam_keyword" }\n end\n return { flagged = false }\nend\n\nreturn Moderation',
},
{
fileName: 'analytics.lua',
description: 'Aggregate engagement signals for dashboards.',
code: 'local Analytics = {}\n\nfunction Analytics.aggregate(events)\n local summary = { views = 0, likes = 0, comments = 0 }\n for _, event in ipairs(events or {}) do\n summary.views = summary.views + (event.views or 0)\n summary.likes = summary.likes + (event.likes or 0)\n summary.comments = summary.comments + (event.comments or 0)\n end\n return summary\nend\n\nreturn Analytics',
},
]
export const REACT_APP_TEMPLATE_CONFIG: ReactAppTemplateConfig = BASE_REACT_APP_TEMPLATE_CONFIG
export const PACKAGE_TEMPLATE_CONFIGS: PackageTemplateConfig[] = [
{
id: 'package_social_hub',
name: 'Social Hub Package',
description: 'A package blueprint for social feeds, creator updates, and live rooms.',
rootName: 'social_hub',
packageId: 'social_hub',
author: 'MetaBuilder',
version: '1.0.0',
category: 'social',
summary: 'Modern social feed with creator tools and live rooms.',
components: socialHubComponents,
examples: socialHubExamples,
luaScripts: socialHubLuaScripts,
tags: ['package', 'social', 'feed', 'lua'],
},
...BASE_PACKAGE_TEMPLATE_CONFIGS,
...ADVANCED_PACKAGE_TEMPLATE_CONFIGS,
...EXPERIMENTAL_PACKAGE_TEMPLATE_CONFIGS,
]

View File

@@ -1,308 +1,6 @@
import type { SchemaConfig } from '../types/schema-types'
import { defaultApps } from './default/components'
export const defaultSchema: SchemaConfig = {
apps: [
{
name: 'blog',
label: 'Blog',
models: [
{
name: 'post',
label: 'Post',
labelPlural: 'Posts',
icon: 'Article',
listDisplay: ['title', 'author', 'status', 'publishedAt'],
listFilter: ['status', 'author'],
searchFields: ['title', 'content'],
ordering: ['-publishedAt'],
fields: [
{
name: 'id',
type: 'string',
label: 'ID',
required: true,
unique: true,
editable: false,
listDisplay: false,
},
{
name: 'title',
type: 'string',
label: 'Title',
required: true,
validation: {
minLength: 3,
maxLength: 200,
},
listDisplay: true,
searchable: true,
sortable: true,
},
{
name: 'slug',
type: 'string',
label: 'Slug',
required: true,
unique: true,
helpText: 'URL-friendly version of the title',
validation: {
pattern: '^[a-z0-9-]+$',
},
listDisplay: false,
sortable: true,
},
{
name: 'content',
type: 'text',
label: 'Content',
required: true,
helpText: 'Main post content',
listDisplay: false,
searchable: true,
},
{
name: 'excerpt',
type: 'text',
label: 'Excerpt',
required: false,
helpText: ['Short summary of the post', 'Used in list views and previews'],
validation: {
maxLength: 500,
},
listDisplay: false,
},
{
name: 'author',
type: 'relation',
label: 'Author',
required: true,
relatedModel: 'author',
listDisplay: true,
sortable: true,
},
{
name: 'status',
type: 'select',
label: 'Status',
required: true,
default: 'draft',
choices: [
{ value: 'draft', label: 'Draft' },
{ value: 'published', label: 'Published' },
{ value: 'archived', label: 'Archived' },
],
listDisplay: true,
sortable: true,
},
{
name: 'featured',
type: 'boolean',
label: 'Featured',
default: false,
helpText: 'Display on homepage',
listDisplay: true,
},
{
name: 'publishedAt',
type: 'datetime',
label: 'Published At',
required: false,
listDisplay: true,
sortable: true,
},
{
name: 'tags',
type: 'json',
label: 'Tags',
required: false,
helpText: 'JSON array of tag strings',
listDisplay: false,
},
{
name: 'views',
type: 'number',
label: 'Views',
default: 0,
validation: {
min: 0,
},
listDisplay: false,
},
],
},
{
name: 'author',
label: 'Author',
labelPlural: 'Authors',
icon: 'User',
listDisplay: ['name', 'email', 'active', 'createdAt'],
listFilter: ['active'],
searchFields: ['name', 'email'],
ordering: ['name'],
fields: [
{
name: 'id',
type: 'string',
label: 'ID',
required: true,
unique: true,
editable: false,
listDisplay: false,
},
{
name: 'name',
type: 'string',
label: 'Name',
required: true,
validation: {
minLength: 2,
maxLength: 100,
},
listDisplay: true,
searchable: true,
sortable: true,
},
{
name: 'email',
type: 'email',
label: 'Email',
required: true,
unique: true,
listDisplay: true,
searchable: true,
sortable: true,
},
{
name: 'bio',
type: 'text',
label: 'Bio',
required: false,
helpText: 'Author biography',
validation: {
maxLength: 1000,
},
listDisplay: false,
},
{
name: 'website',
type: 'url',
label: 'Website',
required: false,
listDisplay: false,
},
{
name: 'active',
type: 'boolean',
label: 'Active',
default: true,
listDisplay: true,
},
{
name: 'createdAt',
type: 'datetime',
label: 'Created At',
required: true,
editable: false,
listDisplay: true,
sortable: true,
},
],
},
],
},
{
name: 'ecommerce',
label: 'E-Commerce',
models: [
{
name: 'product',
label: 'Product',
labelPlural: 'Products',
icon: 'ShoppingCart',
listDisplay: ['name', 'price', 'stock', 'available'],
listFilter: ['available', 'category'],
searchFields: ['name', 'description'],
ordering: ['name'],
fields: [
{
name: 'id',
type: 'string',
label: 'ID',
required: true,
unique: true,
editable: false,
listDisplay: false,
},
{
name: 'name',
type: 'string',
label: 'Product Name',
required: true,
validation: {
minLength: 3,
maxLength: 200,
},
listDisplay: true,
searchable: true,
sortable: true,
},
{
name: 'description',
type: 'text',
label: 'Description',
required: false,
helpText: 'Product description',
listDisplay: false,
searchable: true,
},
{
name: 'price',
type: 'number',
label: 'Price',
required: true,
validation: {
min: 0,
},
listDisplay: true,
sortable: true,
},
{
name: 'stock',
type: 'number',
label: 'Stock',
required: true,
default: 0,
validation: {
min: 0,
},
listDisplay: true,
sortable: true,
},
{
name: 'category',
type: 'select',
label: 'Category',
required: true,
choices: [
{ value: 'electronics', label: 'Electronics' },
{ value: 'clothing', label: 'Clothing' },
{ value: 'books', label: 'Books' },
{ value: 'home', label: 'Home & Garden' },
{ value: 'toys', label: 'Toys' },
],
listDisplay: false,
sortable: true,
},
{
name: 'available',
type: 'boolean',
label: 'Available',
default: true,
listDisplay: true,
},
],
},
],
},
],
apps: defaultApps,
}

View File

@@ -0,0 +1,54 @@
import type { AppSchema, ModelSchema } from '../../types/schema-types'
import { authorFields, postFields, productFields } from './forms'
export const blogModels: ModelSchema[] = [
{
name: 'post',
label: 'Post',
labelPlural: 'Posts',
icon: 'Article',
listDisplay: ['title', 'author', 'status', 'publishedAt'],
listFilter: ['status', 'author'],
searchFields: ['title', 'content'],
ordering: ['-publishedAt'],
fields: postFields,
},
{
name: 'author',
label: 'Author',
labelPlural: 'Authors',
icon: 'User',
listDisplay: ['name', 'email', 'active', 'createdAt'],
listFilter: ['active'],
searchFields: ['name', 'email'],
ordering: ['name'],
fields: authorFields,
},
]
export const ecommerceModels: ModelSchema[] = [
{
name: 'product',
label: 'Product',
labelPlural: 'Products',
icon: 'ShoppingCart',
listDisplay: ['name', 'price', 'stock', 'available'],
listFilter: ['available', 'category'],
searchFields: ['name', 'description'],
ordering: ['name'],
fields: productFields,
},
]
export const defaultApps: AppSchema[] = [
{
name: 'blog',
label: 'Blog',
models: blogModels,
},
{
name: 'ecommerce',
label: 'E-Commerce',
models: ecommerceModels,
},
]

View File

@@ -0,0 +1,244 @@
import type { FieldSchema } from '../../types/schema-types'
import { authorValidations, postValidations, productValidations } from './validation'
export const postFields: FieldSchema[] = [
{
name: 'id',
type: 'string',
label: 'ID',
required: true,
unique: true,
editable: false,
listDisplay: false,
},
{
name: 'title',
type: 'string',
label: 'Title',
required: true,
validation: postValidations.title,
listDisplay: true,
searchable: true,
sortable: true,
},
{
name: 'slug',
type: 'string',
label: 'Slug',
required: true,
unique: true,
helpText: 'URL-friendly version of the title',
validation: postValidations.slug,
listDisplay: false,
sortable: true,
},
{
name: 'content',
type: 'text',
label: 'Content',
required: true,
helpText: 'Main post content',
listDisplay: false,
searchable: true,
},
{
name: 'excerpt',
type: 'text',
label: 'Excerpt',
required: false,
helpText: ['Short summary of the post', 'Used in list views and previews'],
validation: postValidations.excerpt,
listDisplay: false,
},
{
name: 'author',
type: 'relation',
label: 'Author',
required: true,
relatedModel: 'author',
listDisplay: true,
sortable: true,
},
{
name: 'status',
type: 'select',
label: 'Status',
required: true,
default: 'draft',
choices: [
{ value: 'draft', label: 'Draft' },
{ value: 'published', label: 'Published' },
{ value: 'archived', label: 'Archived' },
],
listDisplay: true,
sortable: true,
},
{
name: 'featured',
type: 'boolean',
label: 'Featured',
default: false,
helpText: 'Display on homepage',
listDisplay: true,
},
{
name: 'publishedAt',
type: 'datetime',
label: 'Published At',
required: false,
listDisplay: true,
sortable: true,
},
{
name: 'tags',
type: 'json',
label: 'Tags',
required: false,
helpText: 'JSON array of tag strings',
listDisplay: false,
},
{
name: 'views',
type: 'number',
label: 'Views',
default: 0,
validation: postValidations.views,
listDisplay: false,
},
]
export const authorFields: FieldSchema[] = [
{
name: 'id',
type: 'string',
label: 'ID',
required: true,
unique: true,
editable: false,
listDisplay: false,
},
{
name: 'name',
type: 'string',
label: 'Name',
required: true,
validation: authorValidations.name,
listDisplay: true,
searchable: true,
sortable: true,
},
{
name: 'email',
type: 'email',
label: 'Email',
required: true,
unique: true,
listDisplay: true,
searchable: true,
sortable: true,
},
{
name: 'bio',
type: 'text',
label: 'Bio',
required: false,
helpText: 'Author biography',
validation: authorValidations.bio,
listDisplay: false,
},
{
name: 'website',
type: 'url',
label: 'Website',
required: false,
listDisplay: false,
},
{
name: 'active',
type: 'boolean',
label: 'Active',
default: true,
listDisplay: true,
},
{
name: 'createdAt',
type: 'datetime',
label: 'Created At',
required: true,
editable: false,
listDisplay: true,
sortable: true,
},
]
export const productFields: FieldSchema[] = [
{
name: 'id',
type: 'string',
label: 'ID',
required: true,
unique: true,
editable: false,
listDisplay: false,
},
{
name: 'name',
type: 'string',
label: 'Product Name',
required: true,
validation: productValidations.name,
listDisplay: true,
searchable: true,
sortable: true,
},
{
name: 'description',
type: 'text',
label: 'Description',
required: false,
helpText: 'Product description',
listDisplay: false,
searchable: true,
},
{
name: 'price',
type: 'number',
label: 'Price',
required: true,
validation: productValidations.price,
listDisplay: true,
sortable: true,
},
{
name: 'stock',
type: 'number',
label: 'Stock',
required: true,
default: 0,
validation: productValidations.stock,
listDisplay: true,
sortable: true,
},
{
name: 'category',
type: 'select',
label: 'Category',
required: true,
choices: [
{ value: 'electronics', label: 'Electronics' },
{ value: 'clothing', label: 'Clothing' },
{ value: 'books', label: 'Books' },
{ value: 'home', label: 'Home & Garden' },
{ value: 'toys', label: 'Toys' },
],
listDisplay: false,
sortable: true,
},
{
name: 'available',
type: 'boolean',
label: 'Available',
default: true,
listDisplay: true,
},
]

View File

@@ -0,0 +1,19 @@
import type { FieldSchema } from '../../types/schema-types'
export const postValidations: Record<string, FieldSchema['validation']> = {
title: { minLength: 3, maxLength: 200 },
slug: { pattern: '^[a-z0-9-]+$' },
excerpt: { maxLength: 500 },
views: { min: 0 },
}
export const authorValidations: Record<string, FieldSchema['validation']> = {
name: { minLength: 2, maxLength: 100 },
bio: { maxLength: 1000 },
}
export const productValidations: Record<string, FieldSchema['validation']> = {
name: { minLength: 3, maxLength: 200 },
price: { min: 0 },
stock: { min: 0 },
}

View File

@@ -1,3 +1,6 @@
// Schema utilities exports
export * from './schema-utils'
export { defaultSchema } from './default-schema'
export * from './default/components'
export * from './default/forms'
export * from './default/validation'

View File

@@ -4,181 +4,12 @@
*/
import type { SecurityPattern } from '../types'
import { JAVASCRIPT_INJECTION_PATTERNS } from './javascript/injection'
import { JAVASCRIPT_MISC_PATTERNS } from './javascript/misc'
import { JAVASCRIPT_XSS_PATTERNS } from './javascript/xss'
export const JAVASCRIPT_PATTERNS: SecurityPattern[] = [
{
pattern: /eval\s*\(/gi,
type: 'dangerous',
severity: 'critical',
message: 'Use of eval() detected - can execute arbitrary code',
recommendation: 'Use safe alternatives like JSON.parse() or Function constructor with strict validation'
},
{
pattern: /Function\s*\(/gi,
type: 'dangerous',
severity: 'high',
message: 'Dynamic Function constructor detected',
recommendation: 'Avoid dynamic code generation or use with extreme caution'
},
{
pattern: /innerHTML\s*=/gi,
type: 'dangerous',
severity: 'high',
message: 'innerHTML assignment detected - XSS vulnerability risk',
recommendation: 'Use textContent, createElement, or React JSX instead'
},
{
pattern: /dangerouslySetInnerHTML/gi,
type: 'dangerous',
severity: 'high',
message: 'dangerouslySetInnerHTML detected - XSS vulnerability risk',
recommendation: 'Sanitize HTML content or use safe alternatives'
},
{
pattern: /document\.write\s*\(/gi,
type: 'dangerous',
severity: 'medium',
message: 'document.write() detected - can cause security issues',
recommendation: 'Use DOM manipulation methods instead'
},
{
pattern: /\.call\s*\(\s*window/gi,
type: 'suspicious',
severity: 'medium',
message: 'Calling functions with window context',
recommendation: 'Be careful with context manipulation'
},
{
pattern: /\.apply\s*\(\s*window/gi,
type: 'suspicious',
severity: 'medium',
message: 'Applying functions with window context',
recommendation: 'Be careful with context manipulation'
},
{
pattern: /__proto__/gi,
type: 'dangerous',
severity: 'critical',
message: 'Prototype pollution attempt detected',
recommendation: 'Never manipulate __proto__ directly'
},
{
pattern: /constructor\s*\[\s*['"]prototype['"]\s*\]/gi,
type: 'dangerous',
severity: 'critical',
message: 'Prototype manipulation detected',
recommendation: 'Use Object.create() or proper class syntax'
},
{
pattern: /import\s+.*\s+from\s+['"]https?:/gi,
type: 'dangerous',
severity: 'critical',
message: 'Remote code import detected',
recommendation: 'Only import from trusted, local sources'
},
{
pattern: /<script[^>]*>/gi,
type: 'dangerous',
severity: 'critical',
message: 'Script tag injection detected',
recommendation: 'Never inject script tags dynamically'
},
{
pattern: /on(click|load|error|mouseover|mouseout|focus|blur)\s*=/gi,
type: 'suspicious',
severity: 'medium',
message: 'Inline event handler detected',
recommendation: 'Use addEventListener or React event handlers'
},
{
pattern: /javascript:\s*/gi,
type: 'dangerous',
severity: 'high',
message: 'javascript: protocol detected',
recommendation: 'Never use javascript: protocol in URLs'
},
{
pattern: /data:\s*text\/html/gi,
type: 'dangerous',
severity: 'high',
message: 'Data URI with HTML detected',
recommendation: 'Avoid data URIs with executable content'
},
{
pattern: /setTimeout\s*\(\s*['"`]/gi,
type: 'dangerous',
severity: 'high',
message: 'setTimeout with string argument detected',
recommendation: 'Use setTimeout with function reference instead'
},
{
pattern: /setInterval\s*\(\s*['"`]/gi,
type: 'dangerous',
severity: 'high',
message: 'setInterval with string argument detected',
recommendation: 'Use setInterval with function reference instead'
},
{
pattern: /localStorage|sessionStorage/gi,
type: 'warning',
severity: 'low',
message: 'Local/session storage usage detected',
recommendation: 'Use useKV hook for persistent data instead'
},
{
pattern: /crypto\.subtle|atob|btoa/gi,
type: 'warning',
severity: 'low',
message: 'Cryptographic operation detected',
recommendation: 'Ensure proper key management and secure practices'
},
{
pattern: /XMLHttpRequest|fetch\s*\(\s*['"`]http/gi,
type: 'warning',
severity: 'medium',
message: 'External HTTP request detected',
recommendation: 'Ensure CORS and security headers are properly configured'
},
{
pattern: /window\.open/gi,
type: 'suspicious',
severity: 'medium',
message: 'window.open detected',
recommendation: 'Be cautious with popup windows'
},
{
pattern: /location\.href\s*=/gi,
type: 'suspicious',
severity: 'medium',
message: 'Direct location manipulation detected',
recommendation: 'Use React Router or validate URLs carefully'
},
{
pattern: /require\s*\(\s*[^'"`]/gi,
type: 'dangerous',
severity: 'high',
message: 'Dynamic require() detected',
recommendation: 'Use static imports only'
},
{
pattern: /\.exec\s*\(|child_process|spawn|fork|execFile/gi,
type: 'malicious',
severity: 'critical',
message: 'System command execution attempt detected',
recommendation: 'This is not allowed in browser environment'
},
{
pattern: /fs\.|path\.|os\./gi,
type: 'malicious',
severity: 'critical',
message: 'Node.js system module usage detected',
recommendation: 'File system access not allowed in browser'
},
{
pattern: /process\.env|process\.exit/gi,
type: 'suspicious',
severity: 'medium',
message: 'Process manipulation detected',
recommendation: 'Not applicable in browser environment'
}
...JAVASCRIPT_INJECTION_PATTERNS,
...JAVASCRIPT_XSS_PATTERNS,
...JAVASCRIPT_MISC_PATTERNS
]

View File

@@ -0,0 +1,53 @@
import type { SecurityPattern } from '../../types'
export const JAVASCRIPT_INJECTION_PATTERNS: SecurityPattern[] = [
{
pattern: /eval\s*\(/gi,
type: 'dangerous',
severity: 'critical',
message: 'Use of eval() detected - can execute arbitrary code',
recommendation: 'Use safe alternatives like JSON.parse() or Function constructor with strict validation'
},
{
pattern: /Function\s*\(/gi,
type: 'dangerous',
severity: 'high',
message: 'Dynamic Function constructor detected',
recommendation: 'Avoid dynamic code generation or use with extreme caution'
},
{
pattern: /import\s+.*\s+from\s+['"]https?:/gi,
type: 'dangerous',
severity: 'critical',
message: 'Remote code import detected',
recommendation: 'Only import from trusted, local sources'
},
{
pattern: /setTimeout\s*\(\s*['"`]/gi,
type: 'dangerous',
severity: 'high',
message: 'setTimeout with string argument detected',
recommendation: 'Use setTimeout with function reference instead'
},
{
pattern: /setInterval\s*\(\s*['"`]/gi,
type: 'dangerous',
severity: 'high',
message: 'setInterval with string argument detected',
recommendation: 'Use setInterval with function reference instead'
},
{
pattern: /require\s*\(\s*[^'"`]/gi,
type: 'dangerous',
severity: 'high',
message: 'Dynamic require() detected',
recommendation: 'Use static imports only'
},
{
pattern: /\.exec\s*\(|child_process|spawn|fork|execFile/gi,
type: 'malicious',
severity: 'critical',
message: 'System command execution attempt detected',
recommendation: 'This is not allowed in browser environment'
}
]

View File

@@ -0,0 +1,81 @@
import type { SecurityPattern } from '../../types'
export const JAVASCRIPT_MISC_PATTERNS: SecurityPattern[] = [
{
pattern: /\.call\s*\(\s*window/gi,
type: 'suspicious',
severity: 'medium',
message: 'Calling functions with window context',
recommendation: 'Be careful with context manipulation'
},
{
pattern: /\.apply\s*\(\s*window/gi,
type: 'suspicious',
severity: 'medium',
message: 'Applying functions with window context',
recommendation: 'Be careful with context manipulation'
},
{
pattern: /__proto__/gi,
type: 'dangerous',
severity: 'critical',
message: 'Prototype pollution attempt detected',
recommendation: 'Never manipulate __proto__ directly'
},
{
pattern: /constructor\s*\[\s*['"]prototype['"]\s*\]/gi,
type: 'dangerous',
severity: 'critical',
message: 'Prototype manipulation detected',
recommendation: 'Use Object.create() or proper class syntax'
},
{
pattern: /localStorage|sessionStorage/gi,
type: 'warning',
severity: 'low',
message: 'Local/session storage usage detected',
recommendation: 'Use useKV hook for persistent data instead'
},
{
pattern: /crypto\.subtle|atob|btoa/gi,
type: 'warning',
severity: 'low',
message: 'Cryptographic operation detected',
recommendation: 'Ensure proper key management and secure practices'
},
{
pattern: /XMLHttpRequest|fetch\s*\(\s*['"`]http/gi,
type: 'warning',
severity: 'medium',
message: 'External HTTP request detected',
recommendation: 'Ensure CORS and security headers are properly configured'
},
{
pattern: /window\.open/gi,
type: 'suspicious',
severity: 'medium',
message: 'window.open detected',
recommendation: 'Be cautious with popup windows'
},
{
pattern: /location\.href\s*=/gi,
type: 'suspicious',
severity: 'medium',
message: 'Direct location manipulation detected',
recommendation: 'Use React Router or validate URLs carefully'
},
{
pattern: /fs\.|path\.|os\./gi,
type: 'malicious',
severity: 'critical',
message: 'Node.js system module usage detected',
recommendation: 'File system access not allowed in browser'
},
{
pattern: /process\.env|process\.exit/gi,
type: 'suspicious',
severity: 'medium',
message: 'Process manipulation detected',
recommendation: 'Not applicable in browser environment'
}
]

View File

@@ -0,0 +1,53 @@
import type { SecurityPattern } from '../../types'
export const JAVASCRIPT_XSS_PATTERNS: SecurityPattern[] = [
{
pattern: /innerHTML\s*=/gi,
type: 'dangerous',
severity: 'high',
message: 'innerHTML assignment detected - XSS vulnerability risk',
recommendation: 'Use textContent, createElement, or React JSX instead'
},
{
pattern: /dangerouslySetInnerHTML/gi,
type: 'dangerous',
severity: 'high',
message: 'dangerouslySetInnerHTML detected - XSS vulnerability risk',
recommendation: 'Sanitize HTML content or use safe alternatives'
},
{
pattern: /document\.write\s*\(/gi,
type: 'dangerous',
severity: 'medium',
message: 'document.write() detected - can cause security issues',
recommendation: 'Use DOM manipulation methods instead'
},
{
pattern: /<script[^>]*>/gi,
type: 'dangerous',
severity: 'critical',
message: 'Script tag injection detected',
recommendation: 'Never inject script tags dynamically'
},
{
pattern: /on(click|load|error|mouseover|mouseout|focus|blur)\s*=/gi,
type: 'suspicious',
severity: 'medium',
message: 'Inline event handler detected',
recommendation: 'Use addEventListener or React event handlers'
},
{
pattern: /javascript:\s*/gi,
type: 'dangerous',
severity: 'high',
message: 'javascript: protocol detected',
recommendation: 'Never use javascript: protocol in URLs'
},
{
pattern: /data:\s*text\/html/gi,
type: 'dangerous',
severity: 'high',
message: 'Data URI with HTML detected',
recommendation: 'Avoid data URIs with executable content'
}
]

View File

@@ -0,0 +1,234 @@
import { describe, expect, it } from 'vitest'
import { scanForVulnerabilities, securityScanner } from '@/lib/security-scanner'
describe('security-scanner detection', () => {
describe('scanJavaScript', () => {
it.each([
{
name: 'flag eval usage as critical',
code: ['const safe = true;', 'const result = eval("1 + 1")'].join('\n'),
expectedSeverity: 'critical',
expectedSafe: false,
expectedIssueType: 'dangerous',
expectedIssuePattern: 'eval',
expectedLine: 2,
},
{
name: 'warn on localStorage usage but stay safe',
code: 'localStorage.setItem("k", "v")',
expectedSeverity: 'low',
expectedSafe: true,
expectedIssueType: 'warning',
expectedIssuePattern: 'localStorage',
},
{
name: 'return safe for benign code',
code: 'const sum = (a, b) => a + b',
expectedSeverity: 'safe',
expectedSafe: true,
},
])(
'should $name',
({ code, expectedSeverity, expectedSafe, expectedIssueType, expectedIssuePattern, expectedLine }) => {
const result = securityScanner.scanJavaScript(code)
expect(result.severity).toBe(expectedSeverity)
expect(result.safe).toBe(expectedSafe)
if (expectedIssueType || expectedIssuePattern) {
const issue = result.issues.find(item => {
const matchesType = expectedIssueType ? item.type === expectedIssueType : true
const matchesPattern = expectedIssuePattern ? item.pattern.includes(expectedIssuePattern) : true
return matchesType && matchesPattern
})
expect(issue).toBeDefined()
if (expectedLine !== undefined) {
expect(issue?.line).toBe(expectedLine)
}
} else {
expect(result.issues.length).toBe(0)
}
if (expectedSafe) {
expect(result.sanitizedCode).toBe(code)
} else {
expect(result.sanitizedCode).toBeUndefined()
}
}
)
})
describe('scanLua', () => {
it.each([
{
name: 'flag os.execute usage as critical',
code: 'os.execute("rm -rf /")',
expectedSeverity: 'critical',
expectedSafe: false,
expectedIssueType: 'malicious',
expectedIssuePattern: 'os.execute',
},
{
name: 'return safe for simple Lua function',
code: 'function add(a, b) return a + b end',
expectedSeverity: 'safe',
expectedSafe: true,
},
])('should $name', ({ code, expectedSeverity, expectedSafe, expectedIssueType, expectedIssuePattern }) => {
const result = securityScanner.scanLua(code)
expect(result.severity).toBe(expectedSeverity)
expect(result.safe).toBe(expectedSafe)
if (expectedIssueType || expectedIssuePattern) {
const issue = result.issues.find(item => {
const matchesType = expectedIssueType ? item.type === expectedIssueType : true
const matchesPattern = expectedIssuePattern ? item.pattern.includes(expectedIssuePattern) : true
return matchesType && matchesPattern
})
expect(issue).toBeDefined()
} else {
expect(result.issues.length).toBe(0)
}
if (expectedSafe) {
expect(result.sanitizedCode).toBe(code)
} else {
expect(result.sanitizedCode).toBeUndefined()
}
})
})
describe('scanJSON', () => {
it.each([
{
name: 'flag invalid JSON as medium severity',
json: '{"value": }',
expectedSeverity: 'medium',
expectedSafe: false,
expectedIssuePattern: 'JSON parse error',
},
{
name: 'flag prototype pollution in JSON as critical',
json: '{"__proto__": {"polluted": true}}',
expectedSeverity: 'critical',
expectedSafe: false,
expectedIssuePattern: '__proto__',
},
{
name: 'return safe for valid JSON',
json: '{"ok": true}',
expectedSeverity: 'safe',
expectedSafe: true,
},
])('should $name', ({ json, expectedSeverity, expectedSafe, expectedIssuePattern }) => {
const result = securityScanner.scanJSON(json)
expect(result.severity).toBe(expectedSeverity)
expect(result.safe).toBe(expectedSafe)
if (expectedIssuePattern) {
expect(result.issues.some(issue => issue.pattern.includes(expectedIssuePattern))).toBe(true)
} else {
expect(result.issues.length).toBe(0)
}
if (expectedSafe) {
expect(result.sanitizedCode).toBe(json)
} else {
expect(result.sanitizedCode).toBeUndefined()
}
})
})
describe('scanHTML', () => {
it.each([
{
name: 'flag script tags as critical',
html: '<div><script>alert(1)</script></div>',
expectedSeverity: 'critical',
expectedSafe: false,
},
{
name: 'flag inline handlers as high',
html: '<button onclick="alert(1)">Click</button>',
expectedSeverity: 'high',
expectedSafe: false,
},
{
name: 'return safe for plain markup',
html: '<div><span>Safe</span></div>',
expectedSeverity: 'safe',
expectedSafe: true,
},
])('should $name', ({ html, expectedSeverity, expectedSafe }) => {
const result = securityScanner.scanHTML(html)
expect(result.severity).toBe(expectedSeverity)
expect(result.safe).toBe(expectedSafe)
})
})
describe('sanitizeInput', () => {
it.each([
{
name: 'remove script tags and inline handlers from text',
input: '<div onclick="alert(1)">Click</div><script>alert(2)</script><a href="javascript:alert(3)">x</a>',
type: 'text' as const,
shouldExclude: ['<script', 'onclick', 'javascript:'],
},
{
name: 'remove data html URIs from html',
input: '<img src="data:text/html;base64,abc"><script>alert(1)</script>',
type: 'html' as const,
shouldExclude: ['data:text/html', '<script'],
},
{
name: 'neutralize prototype pollution in json',
input: '{"__proto__": {"polluted": true}, "note": "constructor[\\"prototype\\"]"}',
type: 'json' as const,
shouldInclude: ['_proto_'],
shouldExclude: ['__proto__', 'constructor["prototype"]'],
},
])('should $name', ({ input, type, shouldExclude = [], shouldInclude = [] }) => {
const sanitized = securityScanner.sanitizeInput(input, type)
shouldExclude.forEach(value => {
expect(sanitized).not.toContain(value)
})
shouldInclude.forEach(value => {
expect(sanitized).toContain(value)
})
})
})
describe('scanForVulnerabilities', () => {
it.each([
{
name: 'auto-detects JSON and flags prototype pollution',
code: '{"__proto__": {"polluted": true}}',
expectedSeverity: 'critical',
},
{
name: 'auto-detects Lua when function/end present',
code: 'function dangerous() os.execute("rm -rf /") end',
expectedSeverity: 'critical',
},
{
name: 'auto-detects HTML and flags script tags',
code: '<div><script>alert(1)</script></div>',
expectedSeverity: 'critical',
},
{
name: 'falls back to JavaScript scanning',
code: 'const result = eval("1 + 1")',
expectedSeverity: 'critical',
},
{
name: 'honors explicit type parameter',
code: 'return 1',
type: 'lua' as const,
expectedSeverity: 'safe',
},
])('should $name', ({ code, type, expectedSeverity }) => {
const result = scanForVulnerabilities(code, type)
expect(result.severity).toBe(expectedSeverity)
})
})
})

View File

@@ -0,0 +1,29 @@
import { describe, expect, it } from 'vitest'
import { getSeverityColor, getSeverityIcon } from '@/lib/security-scanner'
describe('security-scanner reporting', () => {
describe('getSeverityColor', () => {
it.each([
{ severity: 'critical', expected: 'error' },
{ severity: 'high', expected: 'warning' },
{ severity: 'medium', expected: 'info' },
{ severity: 'low', expected: 'secondary' },
{ severity: 'safe', expected: 'success' },
])('should map $severity to expected classes', ({ severity, expected }) => {
expect(getSeverityColor(severity)).toBe(expected)
})
})
describe('getSeverityIcon', () => {
it.each([
{ severity: 'critical', expected: '\u{1F6A8}' },
{ severity: 'high', expected: '\u26A0\uFE0F' },
{ severity: 'medium', expected: '\u26A1' },
{ severity: 'low', expected: '\u2139\uFE0F' },
{ severity: 'safe', expected: '\u2713' },
])('should map $severity to expected icon', ({ severity, expected }) => {
expect(getSeverityIcon(severity)).toBe(expected)
})
})
})

View File

@@ -1,257 +1,2 @@
import { describe, it, expect } from 'vitest'
import { securityScanner, scanForVulnerabilities, getSeverityColor, getSeverityIcon } from '@/lib/security-scanner'
describe('security-scanner', () => {
describe('scanJavaScript', () => {
it.each([
{
name: 'flag eval usage as critical',
code: ['const safe = true;', 'const result = eval("1 + 1")'].join('\n'),
expectedSeverity: 'critical',
expectedSafe: false,
expectedIssueType: 'dangerous',
expectedIssuePattern: 'eval',
expectedLine: 2,
},
{
name: 'warn on localStorage usage but stay safe',
code: 'localStorage.setItem("k", "v")',
expectedSeverity: 'low',
expectedSafe: true,
expectedIssueType: 'warning',
expectedIssuePattern: 'localStorage',
},
{
name: 'return safe for benign code',
code: 'const sum = (a, b) => a + b',
expectedSeverity: 'safe',
expectedSafe: true,
},
])(
'should $name',
({ code, expectedSeverity, expectedSafe, expectedIssueType, expectedIssuePattern, expectedLine }) => {
const result = securityScanner.scanJavaScript(code)
expect(result.severity).toBe(expectedSeverity)
expect(result.safe).toBe(expectedSafe)
if (expectedIssueType || expectedIssuePattern) {
const issue = result.issues.find(item => {
const matchesType = expectedIssueType ? item.type === expectedIssueType : true
const matchesPattern = expectedIssuePattern ? item.pattern.includes(expectedIssuePattern) : true
return matchesType && matchesPattern
})
expect(issue).toBeDefined()
if (expectedLine !== undefined) {
expect(issue?.line).toBe(expectedLine)
}
} else {
expect(result.issues.length).toBe(0)
}
if (expectedSafe) {
expect(result.sanitizedCode).toBe(code)
} else {
expect(result.sanitizedCode).toBeUndefined()
}
}
)
})
describe('scanLua', () => {
it.each([
{
name: 'flag os.execute usage as critical',
code: 'os.execute("rm -rf /")',
expectedSeverity: 'critical',
expectedSafe: false,
expectedIssueType: 'malicious',
expectedIssuePattern: 'os.execute',
},
{
name: 'return safe for simple Lua function',
code: 'function add(a, b) return a + b end',
expectedSeverity: 'safe',
expectedSafe: true,
},
])('should $name', ({ code, expectedSeverity, expectedSafe, expectedIssueType, expectedIssuePattern }) => {
const result = securityScanner.scanLua(code)
expect(result.severity).toBe(expectedSeverity)
expect(result.safe).toBe(expectedSafe)
if (expectedIssueType || expectedIssuePattern) {
const issue = result.issues.find(item => {
const matchesType = expectedIssueType ? item.type === expectedIssueType : true
const matchesPattern = expectedIssuePattern ? item.pattern.includes(expectedIssuePattern) : true
return matchesType && matchesPattern
})
expect(issue).toBeDefined()
} else {
expect(result.issues.length).toBe(0)
}
if (expectedSafe) {
expect(result.sanitizedCode).toBe(code)
} else {
expect(result.sanitizedCode).toBeUndefined()
}
})
})
describe('scanJSON', () => {
it.each([
{
name: 'flag invalid JSON as medium severity',
json: '{"value": }',
expectedSeverity: 'medium',
expectedSafe: false,
expectedIssuePattern: 'JSON parse error',
},
{
name: 'flag prototype pollution in JSON as critical',
json: '{"__proto__": {"polluted": true}}',
expectedSeverity: 'critical',
expectedSafe: false,
expectedIssuePattern: '__proto__',
},
{
name: 'return safe for valid JSON',
json: '{"ok": true}',
expectedSeverity: 'safe',
expectedSafe: true,
},
])('should $name', ({ json, expectedSeverity, expectedSafe, expectedIssuePattern }) => {
const result = securityScanner.scanJSON(json)
expect(result.severity).toBe(expectedSeverity)
expect(result.safe).toBe(expectedSafe)
if (expectedIssuePattern) {
expect(result.issues.some(issue => issue.pattern.includes(expectedIssuePattern))).toBe(true)
} else {
expect(result.issues.length).toBe(0)
}
if (expectedSafe) {
expect(result.sanitizedCode).toBe(json)
} else {
expect(result.sanitizedCode).toBeUndefined()
}
})
})
describe('scanHTML', () => {
it.each([
{
name: 'flag script tags as critical',
html: '<div><script>alert(1)</script></div>',
expectedSeverity: 'critical',
expectedSafe: false,
},
{
name: 'flag inline handlers as high',
html: '<button onclick="alert(1)">Click</button>',
expectedSeverity: 'high',
expectedSafe: false,
},
{
name: 'return safe for plain markup',
html: '<div><span>Safe</span></div>',
expectedSeverity: 'safe',
expectedSafe: true,
},
])('should $name', ({ html, expectedSeverity, expectedSafe }) => {
const result = securityScanner.scanHTML(html)
expect(result.severity).toBe(expectedSeverity)
expect(result.safe).toBe(expectedSafe)
})
})
describe('sanitizeInput', () => {
it.each([
{
name: 'remove script tags and inline handlers from text',
input: '<div onclick="alert(1)">Click</div><script>alert(2)</script><a href="javascript:alert(3)">x</a>',
type: 'text' as const,
shouldExclude: ['<script', 'onclick', 'javascript:'],
},
{
name: 'remove data html URIs from html',
input: '<img src="data:text/html;base64,abc"><script>alert(1)</script>',
type: 'html' as const,
shouldExclude: ['data:text/html', '<script'],
},
{
name: 'neutralize prototype pollution in json',
input: '{"__proto__": {"polluted": true}, "note": "constructor[\\"prototype\\"]"}',
type: 'json' as const,
shouldInclude: ['_proto_'],
shouldExclude: ['__proto__', 'constructor["prototype"]'],
},
])('should $name', ({ input, type, shouldExclude = [], shouldInclude = [] }) => {
const sanitized = securityScanner.sanitizeInput(input, type)
shouldExclude.forEach(value => {
expect(sanitized).not.toContain(value)
})
shouldInclude.forEach(value => {
expect(sanitized).toContain(value)
})
})
})
describe('getSeverityColor', () => {
it.each([
{ severity: 'critical', expected: 'error' },
{ severity: 'high', expected: 'warning' },
{ severity: 'medium', expected: 'info' },
{ severity: 'low', expected: 'secondary' },
{ severity: 'safe', expected: 'success' },
])('should map $severity to expected classes', ({ severity, expected }) => {
expect(getSeverityColor(severity)).toBe(expected)
})
})
describe('getSeverityIcon', () => {
it.each([
{ severity: 'critical', expected: '\u{1F6A8}' },
{ severity: 'high', expected: '\u26A0\uFE0F' },
{ severity: 'medium', expected: '\u26A1' },
{ severity: 'low', expected: '\u2139\uFE0F' },
{ severity: 'safe', expected: '\u2713' },
])('should map $severity to expected icon', ({ severity, expected }) => {
expect(getSeverityIcon(severity)).toBe(expected)
})
})
describe('scanForVulnerabilities', () => {
it.each([
{
name: 'auto-detects JSON and flags prototype pollution',
code: '{"__proto__": {"polluted": true}}',
expectedSeverity: 'critical',
},
{
name: 'auto-detects Lua when function/end present',
code: 'function dangerous() os.execute("rm -rf /") end',
expectedSeverity: 'critical',
},
{
name: 'auto-detects HTML and flags script tags',
code: '<div><script>alert(1)</script></div>',
expectedSeverity: 'critical',
},
{
name: 'falls back to JavaScript scanning',
code: 'const result = eval("1 + 1")',
expectedSeverity: 'critical',
},
{
name: 'honors explicit type parameter',
code: 'return 1',
type: 'lua' as const,
expectedSeverity: 'safe',
},
])('should $name', ({ code, type, expectedSeverity }) => {
const result = scanForVulnerabilities(code, type)
expect(result.severity).toBe(expectedSeverity)
})
})
})
import './__tests__/security-scanner.detection.test'
import './__tests__/security-scanner.reporting.test'

View File

@@ -0,0 +1,71 @@
import '@mui/material/styles'
import '@mui/material/Typography'
import '@mui/material/Button'
import '@mui/material/Chip'
import '@mui/material/IconButton'
import '@mui/material/Badge'
import '@mui/material/Alert'
// Typography variants and component overrides
declare module '@mui/material/styles' {
interface TypographyVariants {
code: React.CSSProperties
kbd: React.CSSProperties
label: React.CSSProperties
}
interface TypographyVariantsOptions {
code?: React.CSSProperties
kbd?: React.CSSProperties
label?: React.CSSProperties
}
}
declare module '@mui/material/Typography' {
interface TypographyPropsVariantOverrides {
code: true
kbd: true
label: true
}
}
declare module '@mui/material/Button' {
interface ButtonPropsVariantOverrides {
soft: true
ghost: true
}
interface ButtonPropsColorOverrides {
neutral: true
}
}
declare module '@mui/material/Chip' {
interface ChipPropsVariantOverrides {
soft: true
}
interface ChipPropsColorOverrides {
neutral: true
}
}
declare module '@mui/material/IconButton' {
interface IconButtonPropsColorOverrides {
neutral: true
}
}
declare module '@mui/material/Badge' {
interface BadgePropsColorOverrides {
neutral: true
}
}
declare module '@mui/material/Alert' {
interface AlertPropsVariantOverrides {
soft: true
}
}
export {}

View File

@@ -0,0 +1,70 @@
import '@mui/material/styles'
// Custom theme properties for layout and design tokens
declare module '@mui/material/styles' {
interface Theme {
custom: {
fonts: {
body: string
heading: string
mono: string
}
borderRadius: {
none: number
sm: number
md: number
lg: number
xl: number
full: number
}
contentWidth: {
sm: string
md: string
lg: string
xl: string
full: string
}
sidebar: {
width: number
collapsedWidth: number
}
header: {
height: number
}
}
}
interface ThemeOptions {
custom?: {
fonts?: {
body?: string
heading?: string
mono?: string
}
borderRadius?: {
none?: number
sm?: number
md?: number
lg?: number
xl?: number
full?: number
}
contentWidth?: {
sm?: string
md?: string
lg?: string
xl?: string
full?: string
}
sidebar?: {
width?: number
collapsedWidth?: number
}
header?: {
height?: number
}
}
}
}
export {}

View File

@@ -0,0 +1,38 @@
import '@mui/material/styles'
// Extend palette with custom neutral colors
declare module '@mui/material/styles' {
interface Palette {
neutral: {
50: string
100: string
200: string
300: string
400: string
500: string
600: string
700: string
800: string
900: string
950: string
}
}
interface PaletteOptions {
neutral?: {
50?: string
100?: string
200?: string
300?: string
400?: string
500?: string
600?: string
700?: string
800?: string
900?: string
950?: string
}
}
}
export {}

View File

@@ -1,200 +1,10 @@
/**
* MUI Theme Type Extensions
*
* This file extends Material-UI's theme interface with custom properties.
* All custom design tokens and component variants should be declared here.
*
* This file aggregates the theme augmentation modules to keep the
* main declaration lightweight while still exposing all custom tokens.
*/
import '@mui/material/styles'
import '@mui/material/Typography'
import '@mui/material/Button'
// ============================================================================
// Custom Palette Extensions
// ============================================================================
declare module '@mui/material/styles' {
// Extend palette with custom neutral colors
interface Palette {
neutral: {
50: string
100: string
200: string
300: string
400: string
500: string
600: string
700: string
800: string
900: string
950: string
}
}
interface PaletteOptions {
neutral?: {
50?: string
100?: string
200?: string
300?: string
400?: string
500?: string
600?: string
700?: string
800?: string
900?: string
950?: string
}
}
// Custom typography variants
interface TypographyVariants {
code: React.CSSProperties
kbd: React.CSSProperties
label: React.CSSProperties
}
interface TypographyVariantsOptions {
code?: React.CSSProperties
kbd?: React.CSSProperties
label?: React.CSSProperties
}
// Custom theme properties
interface Theme {
custom: {
fonts: {
body: string
heading: string
mono: string
}
borderRadius: {
none: number
sm: number
md: number
lg: number
xl: number
full: number
}
contentWidth: {
sm: string
md: string
lg: string
xl: string
full: string
}
sidebar: {
width: number
collapsedWidth: number
}
header: {
height: number
}
}
}
interface ThemeOptions {
custom?: {
fonts?: {
body?: string
heading?: string
mono?: string
}
borderRadius?: {
none?: number
sm?: number
md?: number
lg?: number
xl?: number
full?: number
}
contentWidth?: {
sm?: string
md?: string
lg?: string
xl?: string
full?: string
}
sidebar?: {
width?: number
collapsedWidth?: number
}
header?: {
height?: number
}
}
}
}
// ============================================================================
// Typography Variant Mapping
// ============================================================================
declare module '@mui/material/Typography' {
interface TypographyPropsVariantOverrides {
code: true
kbd: true
label: true
}
}
// ============================================================================
// Button Variants & Colors
// ============================================================================
declare module '@mui/material/Button' {
interface ButtonPropsVariantOverrides {
soft: true
ghost: true
}
interface ButtonPropsColorOverrides {
neutral: true
}
}
// ============================================================================
// Chip Variants
// ============================================================================
declare module '@mui/material/Chip' {
interface ChipPropsVariantOverrides {
soft: true
}
interface ChipPropsColorOverrides {
neutral: true
}
}
// ============================================================================
// IconButton Colors
// ============================================================================
declare module '@mui/material/IconButton' {
interface IconButtonPropsColorOverrides {
neutral: true
}
}
// ============================================================================
// Badge Colors
// ============================================================================
declare module '@mui/material/Badge' {
interface BadgePropsColorOverrides {
neutral: true
}
}
// ============================================================================
// Alert Variants
// ============================================================================
declare module '@mui/material/Alert' {
interface AlertPropsVariantOverrides {
soft: true
}
}
export {}
export * from './palette'
export * from './layout'
export * from './components'