diff --git a/apps/console/vercel.json b/apps/console/vercel.json index 0cc3ddb2..94620d4b 100644 --- a/apps/console/vercel.json +++ b/apps/console/vercel.json @@ -1,7 +1,7 @@ { "$schema": "https://openapi.vercel.sh/vercel.json", "installCommand": "cd ../.. && pnpm install --frozen-lockfile", - "buildCommand": "cd ../.. && pnpm turbo run build --filter=@object-ui/console^... && cd apps/console && VITE_BASE_PATH=/ pnpm build:vercel", + "buildCommand": "pnpm msw:init && NODE_OPTIONS=--max-old-space-size=4096 VITE_BASE_PATH=/ VITE_USE_MOCK_SERVER=true vite build", "outputDirectory": "dist", "framework": "vite", "rewrites": [ diff --git a/apps/console/vite.config.ts b/apps/console/vite.config.ts index 94596292..bd0a8482 100644 --- a/apps/console/vite.config.ts +++ b/apps/console/vite.config.ts @@ -40,6 +40,55 @@ function preloadCriticalChunks(): Plugin { // auto-mount slug. Override with VITE_BASE_PATH only if deploying standalone. const basePath = process.env.VITE_BASE_PATH || '/console/'; +// On Vercel/CI we skip the compression and visualizer plugins because the +// Vercel CDN handles gzip/brotli automatically and bundle analysis is not +// needed during CI builds. This reduces peak memory by ~1.5 GB. +// +// Workspace src/ aliases are kept in ALL environments (dev + CI) so that +// plugin side-effect imports (ComponentRegistry.register) resolve correctly. +// Without them, Vite would import pre-built dist/ bundles where the +// singleton ComponentRegistry can get duplicated across chunks, causing +// "Unknown component type" errors at runtime. +const isCI = !!(process.env.VERCEL || process.env.CI); + +// Workspace src/ aliases — gives instant HMR in dev and ensures correct +// side-effect resolution (plugin registrations) in production builds. +const workspaceAliases: Record = { + '@object-ui/components': path.resolve(__dirname, '../../packages/components/src'), + '@object-ui/core': path.resolve(__dirname, '../../packages/core/src'), + '@object-ui/fields': path.resolve(__dirname, '../../packages/fields/src'), + '@object-ui/layout': path.resolve(__dirname, '../../packages/layout/src'), + '@object-ui/plugin-dashboard': path.resolve(__dirname, '../../packages/plugin-dashboard/src'), + '@object-ui/plugin-report': path.resolve(__dirname, '../../packages/plugin-report/src'), + '@object-ui/plugin-form': path.resolve(__dirname, '../../packages/plugin-form/src'), + '@object-ui/plugin-grid': path.resolve(__dirname, '../../packages/plugin-grid/src'), + '@object-ui/react': path.resolve(__dirname, '../../packages/react/src'), + '@object-ui/types': path.resolve(__dirname, '../../packages/types/src'), + '@object-ui/data-objectstack': path.resolve(__dirname, '../../packages/data-objectstack/src'), + '@object-ui/auth': path.resolve(__dirname, '../../packages/auth/src'), + '@object-ui/permissions': path.resolve(__dirname, '../../packages/permissions/src'), + '@object-ui/collaboration': path.resolve(__dirname, '../../packages/collaboration/src'), + '@object-ui/tenant': path.resolve(__dirname, '../../packages/tenant/src'), + '@object-ui/i18n': path.resolve(__dirname, '../../packages/i18n/src'), + '@object-ui/mobile': path.resolve(__dirname, '../../packages/mobile/src'), + + // Plugin Aliases + '@object-ui/plugin-aggrid': path.resolve(__dirname, '../../packages/plugin-aggrid/src'), + '@object-ui/plugin-calendar': path.resolve(__dirname, '../../packages/plugin-calendar/src'), + '@object-ui/plugin-charts': path.resolve(__dirname, '../../packages/plugin-charts/src'), + '@object-ui/plugin-chatbot': path.resolve(__dirname, '../../packages/plugin-chatbot/src'), + '@object-ui/plugin-detail': path.resolve(__dirname, '../../packages/plugin-detail/src'), + '@object-ui/plugin-editor': path.resolve(__dirname, '../../packages/plugin-editor/src'), + '@object-ui/plugin-gantt': path.resolve(__dirname, '../../packages/plugin-gantt/src'), + '@object-ui/plugin-kanban': path.resolve(__dirname, '../../packages/plugin-kanban/src'), + '@object-ui/plugin-list': path.resolve(__dirname, '../../packages/plugin-list/src'), + '@object-ui/plugin-map': path.resolve(__dirname, '../../packages/plugin-map/src'), + '@object-ui/plugin-markdown': path.resolve(__dirname, '../../packages/plugin-markdown/src'), + '@object-ui/plugin-timeline': path.resolve(__dirname, '../../packages/plugin-timeline/src'), + '@object-ui/plugin-view': path.resolve(__dirname, '../../packages/plugin-view/src'), + '@object-ui/plugin-designer': path.resolve(__dirname, '../../packages/plugin-designer/src'), +}; + // https://vitejs.dev/config/ export default defineConfig({ base: basePath, @@ -54,63 +103,30 @@ export default defineConfig({ react(), // Inject for critical chunks preloadCriticalChunks(), - // Gzip compression for production assets - compression({ - algorithm: 'gzip', - exclude: [/\.(br)$/, /\.(gz)$/], - threshold: 1024, - }), - // Brotli compression for modern browsers - compression({ - algorithm: 'brotliCompress', - exclude: [/\.(br)$/, /\.(gz)$/], - threshold: 1024, - }), - // Bundle analysis (generates stats.html in dist/) - visualizer({ - filename: 'dist/stats.html', - gzipSize: true, - brotliSize: true, - open: false, - }), + // Gzip/Brotli compression & bundle visualizer are skipped on Vercel/CI to + // reduce memory usage — Vercel's CDN compresses assets automatically. + ...(!isCI ? [ + compression({ + algorithm: 'gzip', + exclude: [/\.(br)$/, /\.(gz)$/], + threshold: 1024, + }), + compression({ + algorithm: 'brotliCompress', + exclude: [/\.(br)$/, /\.(gz)$/], + threshold: 1024, + }), + visualizer({ + filename: 'dist/stats.html', + gzipSize: true, + brotliSize: true, + open: false, + }), + ] : []), ], resolve: { extensions: ['.mjs', '.js', '.mts', '.ts', '.jsx', '.tsx', '.json'], - alias: { - '@object-ui/components': path.resolve(__dirname, '../../packages/components/src'), - '@object-ui/core': path.resolve(__dirname, '../../packages/core/src'), - '@object-ui/fields': path.resolve(__dirname, '../../packages/fields/src'), - '@object-ui/layout': path.resolve(__dirname, '../../packages/layout/src'), - '@object-ui/plugin-dashboard': path.resolve(__dirname, '../../packages/plugin-dashboard/src'), - '@object-ui/plugin-report': path.resolve(__dirname, '../../packages/plugin-report/src'), - '@object-ui/plugin-form': path.resolve(__dirname, '../../packages/plugin-form/src'), - '@object-ui/plugin-grid': path.resolve(__dirname, '../../packages/plugin-grid/src'), - '@object-ui/react': path.resolve(__dirname, '../../packages/react/src'), - '@object-ui/types': path.resolve(__dirname, '../../packages/types/src'), - '@object-ui/data-objectstack': path.resolve(__dirname, '../../packages/data-objectstack/src'), - '@object-ui/auth': path.resolve(__dirname, '../../packages/auth/src'), - '@object-ui/permissions': path.resolve(__dirname, '../../packages/permissions/src'), - '@object-ui/collaboration': path.resolve(__dirname, '../../packages/collaboration/src'), - '@object-ui/tenant': path.resolve(__dirname, '../../packages/tenant/src'), - '@object-ui/i18n': path.resolve(__dirname, '../../packages/i18n/src'), - - // Missing Plugin Aliases - '@object-ui/plugin-aggrid': path.resolve(__dirname, '../../packages/plugin-aggrid/src'), - '@object-ui/plugin-calendar': path.resolve(__dirname, '../../packages/plugin-calendar/src'), - '@object-ui/plugin-charts': path.resolve(__dirname, '../../packages/plugin-charts/src'), - '@object-ui/plugin-chatbot': path.resolve(__dirname, '../../packages/plugin-chatbot/src'), - '@object-ui/plugin-detail': path.resolve(__dirname, '../../packages/plugin-detail/src'), - '@object-ui/plugin-editor': path.resolve(__dirname, '../../packages/plugin-editor/src'), - '@object-ui/plugin-gantt': path.resolve(__dirname, '../../packages/plugin-gantt/src'), - '@object-ui/plugin-kanban': path.resolve(__dirname, '../../packages/plugin-kanban/src'), - '@object-ui/plugin-list': path.resolve(__dirname, '../../packages/plugin-list/src'), - '@object-ui/plugin-map': path.resolve(__dirname, '../../packages/plugin-map/src'), - '@object-ui/plugin-markdown': path.resolve(__dirname, '../../packages/plugin-markdown/src'), - '@object-ui/plugin-timeline': path.resolve(__dirname, '../../packages/plugin-timeline/src'), - '@object-ui/plugin-view': path.resolve(__dirname, '../../packages/plugin-view/src'), - - - }, + alias: workspaceAliases, }, optimizeDeps: { include: [ diff --git a/packages/auth/package.json b/packages/auth/package.json index 5155a2ab..b35a4b37 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -16,7 +16,8 @@ "exports": { ".": { "types": "./dist/index.d.ts", - "import": "./dist/index.js" + "import": "./dist/index.js", + "default": "./dist/index.js" } }, "files": [ diff --git a/packages/cli/package.json b/packages/cli/package.json index 0f360b4d..5310dbc2 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -18,7 +18,8 @@ "exports": { ".": { "types": "./dist/index.d.ts", - "import": "./dist/index.js" + "import": "./dist/index.js", + "default": "./dist/index.js" } }, "files": [ diff --git a/packages/collaboration/package.json b/packages/collaboration/package.json index 6f8d19f6..11608201 100644 --- a/packages/collaboration/package.json +++ b/packages/collaboration/package.json @@ -16,7 +16,8 @@ "exports": { ".": { "types": "./dist/index.d.ts", - "import": "./dist/index.js" + "import": "./dist/index.js", + "default": "./dist/index.js" } }, "files": [ diff --git a/packages/core/package.json b/packages/core/package.json index c48b28fc..3821ae3d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -20,7 +20,8 @@ "exports": { ".": { "types": "./dist/index.d.ts", - "import": "./dist/index.js" + "import": "./dist/index.js", + "default": "./dist/index.js" } }, "scripts": { diff --git a/packages/i18n/package.json b/packages/i18n/package.json index 0afde714..6f76549b 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -20,11 +20,13 @@ "exports": { ".": { "types": "./dist/index.d.ts", - "import": "./dist/index.js" + "import": "./dist/index.js", + "default": "./dist/index.js" }, "./locales/*": { "types": "./dist/locales/*.d.ts", - "import": "./dist/locales/*.js" + "import": "./dist/locales/*.js", + "default": "./dist/locales/*.js" } }, "scripts": { diff --git a/packages/mobile/package.json b/packages/mobile/package.json index 599b9a47..e7046680 100644 --- a/packages/mobile/package.json +++ b/packages/mobile/package.json @@ -16,7 +16,8 @@ "exports": { ".": { "types": "./dist/index.d.ts", - "import": "./dist/index.js" + "import": "./dist/index.js", + "default": "./dist/index.js" } }, "files": [ diff --git a/packages/permissions/package.json b/packages/permissions/package.json index bc1cf233..744cd953 100644 --- a/packages/permissions/package.json +++ b/packages/permissions/package.json @@ -16,7 +16,8 @@ "exports": { ".": { "types": "./dist/index.d.ts", - "import": "./dist/index.js" + "import": "./dist/index.js", + "default": "./dist/index.js" } }, "files": [ diff --git a/packages/plugin-dashboard/src/WidgetConfigPanel.tsx b/packages/plugin-dashboard/src/WidgetConfigPanel.tsx index 6f48d8b2..a2175aa8 100644 --- a/packages/plugin-dashboard/src/WidgetConfigPanel.tsx +++ b/packages/plugin-dashboard/src/WidgetConfigPanel.tsx @@ -509,7 +509,7 @@ export function WidgetConfigPanel({ availableFields, }: WidgetConfigPanelProps) { // Pre-process config to resolve any I18nLabel values for title/description - const normalizedConfig = React.useMemo(() => ({ + const normalizedConfig: Record = React.useMemo(() => ({ ...config, title: typeof config.title === 'object' ? resolveLabel(config.title) : config.title, description: typeof config.description === 'object' ? resolveLabel(config.description) : config.description, diff --git a/packages/plugin-dashboard/src/utils.ts b/packages/plugin-dashboard/src/utils.ts index da020732..66761af7 100644 --- a/packages/plugin-dashboard/src/utils.ts +++ b/packages/plugin-dashboard/src/utils.ts @@ -7,7 +7,7 @@ */ /** Returns true when the widget data config uses provider: 'object' (async data source). */ -export function isObjectProvider(widgetData: unknown): widgetData is { provider: 'object'; object?: string; aggregate?: any } { +export function isObjectProvider(widgetData: unknown): widgetData is { provider: 'object'; object?: string; aggregate?: any; filter?: any } { return ( widgetData != null && typeof widgetData === 'object' && diff --git a/packages/plugin-detail/src/DetailView.tsx b/packages/plugin-detail/src/DetailView.tsx index 990834e9..40006b7a 100644 --- a/packages/plugin-detail/src/DetailView.tsx +++ b/packages/plugin-detail/src/DetailView.tsx @@ -319,7 +319,7 @@ export const DetailView: React.FC = ({ }, [schema.autoDiscoverRelated, schema.related, objectSchema]); // Merge explicit and auto-discovered related lists - const effectiveRelated = React.useMemo(() => { + const effectiveRelated: NonNullable = React.useMemo(() => { if (schema.related && schema.related.length > 0) return schema.related; return discoveredRelated.map((r) => ({ title: r.title, diff --git a/packages/plugin-grid/src/ObjectGrid.tsx b/packages/plugin-grid/src/ObjectGrid.tsx index 0db037e3..a7600e2c 100644 --- a/packages/plugin-grid/src/ObjectGrid.tsx +++ b/packages/plugin-grid/src/ObjectGrid.tsx @@ -23,6 +23,7 @@ import React, { useEffect, useState, useCallback, useMemo } from 'react'; import type { ObjectGridSchema, DataSource, ListColumn, ViewData } from '@object-ui/types'; +import type { I18nLabel } from '@objectstack/spec/ui'; import { SchemaRenderer, useDataScope, useNavigationOverlay, useAction, useObjectTranslation, useSafeFieldLabel } from '@object-ui/react'; import { getCellRenderer, formatCurrency, formatCompactCurrency, formatDate, formatPercent, humanizeLabel } from '@object-ui/fields'; import { @@ -90,6 +91,13 @@ function useGridTranslation() { } } +/** Resolve an I18nLabel (string | {key, defaultValue}) to a plain string. */ +function resolveColumnLabel(label: string | I18nLabel | undefined): string | undefined { + if (label == null) return undefined; + if (typeof label === 'string') return label; + return label.defaultValue || label.key; +} + export interface ObjectGridProps { schema: ObjectGridSchema; dataSource?: DataSource; @@ -619,7 +627,7 @@ export const ObjectGrid: React.FC = ({ return (cols as ListColumn[]) .filter((col) => col?.field && typeof col.field === 'string' && !col.hidden) .map((col, colIndex) => { - const rawHeader = col.label || col.field.charAt(0).toUpperCase() + col.field.slice(1).replace(/_/g, ' '); + const rawHeader = resolveColumnLabel(col.label) || col.field.charAt(0).toUpperCase() + col.field.slice(1).replace(/_/g, ' '); const header = schema.objectName ? resolveFieldLabel(schema.objectName, col.field, rawHeader) : rawHeader; // Build custom cell renderer based on column configuration diff --git a/packages/react/package.json b/packages/react/package.json index e17efddb..f96b3b3f 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -19,7 +19,8 @@ "exports": { ".": { "types": "./dist/index.d.ts", - "import": "./dist/index.js" + "import": "./dist/index.js", + "default": "./dist/index.js" } }, "scripts": { diff --git a/packages/tenant/package.json b/packages/tenant/package.json index 2356d12d..063cdb2b 100644 --- a/packages/tenant/package.json +++ b/packages/tenant/package.json @@ -16,7 +16,8 @@ "exports": { ".": { "types": "./dist/index.d.ts", - "import": "./dist/index.js" + "import": "./dist/index.js", + "default": "./dist/index.js" } }, "files": [ diff --git a/packages/types/src/complex.ts b/packages/types/src/complex.ts index bd54c330..7df5614b 100644 --- a/packages/types/src/complex.ts +++ b/packages/types/src/complex.ts @@ -544,6 +544,16 @@ export interface DashboardWidgetSchema { * Aligned with @objectstack/spec DashboardWidgetSchema.responsive. */ responsive?: any; + /** + * Enable search input for table-type widgets. + * @default false + */ + searchable?: boolean; + /** + * Enable pagination for table-type widgets. + * @default false + */ + pagination?: boolean; /** * ARIA accessibility attributes. * Aligned with @objectstack/spec AriaPropsSchema.