feat: add plugin-setup and services to Studio deployment#1063
Conversation
Agent-Logs-Url: https://github.com/objectstack-ai/framework/sessions/68f4cfb3-e7ce-4d9a-8500-cc09d3afef70 Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Agent-Logs-Url: https://github.com/objectstack-ai/framework/sessions/68f4cfb3-e7ce-4d9a-8500-cc09d3afef70 Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
This PR updates the Studio Vercel + MSW/browser kernels to include the Setup plugin and additional platform services, aiming to improve feature parity between local and deployed environments so the Setup UI is available in production-like deployments.
Changes:
- Add Studio dependencies for
@objectstack/plugin-setup,@objectstack/service-automation, and@objectstack/service-analytics(and lockfile updates). - Register Setup + Automation + Analytics plugins in the Vercel server kernel.
- Expand the MSW/browser kernel to register additional services/plugins for parity, and extend browser node polyfills used by Vite aliases.
Reviewed changes
Copilot reviewed 4 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| pnpm-lock.yaml | Adds workspace links for the newly added Studio dependencies. |
| apps/studio/package.json | Adds Setup + Automation + Analytics packages to Studio dependencies. |
| apps/studio/server/index.ts | Registers Automation/Analytics/Setup plugins in the Vercel kernel bootstrap. |
| apps/studio/src/mocks/createKernel.ts | Registers additional services/plugins in the MSW/browser kernel factory for parity. |
| apps/studio/mocks/node-polyfills.ts | Adds missing renameSync and randomUUID exports used by browser builds. |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
| await kernel.use(new MetadataPlugin({ watch: false })); | ||
| await kernel.use(new AIServicePlugin()); | ||
| await kernel.use(new AutomationServicePlugin()); | ||
| await kernel.use(new AnalyticsServicePlugin()); | ||
| await kernel.use(new SetupPlugin()); |
There was a problem hiding this comment.
SetupPlugin is registered after plugins that contribute to the setupNav service (e.g., AIServicePlugin, and on the server also Auth/Security/Audit). Because SetupPlugin only registers setupNav during its own init, those earlier plugins won't be able to contribute navigation items, resulting in an empty/incomplete Setup App. Register SetupPlugin before any plugins that call ctx.getService('setupNav') (typically immediately after ObjectQLPlugin / manifest is available, and before Auth/Security/Audit/AI/etc).
| await kernel.use(new FeedServicePlugin()); | ||
| await kernel.use(new MetadataPlugin({ watch: false })); | ||
| await kernel.use(new AIServicePlugin()); | ||
| await kernel.use(new AutomationServicePlugin()); | ||
| await kernel.use(new AnalyticsServicePlugin()); | ||
| await kernel.use(new SetupPlugin()); |
There was a problem hiding this comment.
SetupPlugin is registered last, after AIServicePlugin / other services. Since contributing plugins attempt to call ctx.getService('setupNav') during their init, and setupNav is only registered by SetupPlugin.init, those contributions will be skipped and the Setup App will have no navigation. Register SetupPlugin before the plugins that contribute (at least before AIServicePlugin, and ideally before any service/plugin that adds Setup navigation).
| await kernel.use(new FeedServicePlugin()); | |
| await kernel.use(new MetadataPlugin({ watch: false })); | |
| await kernel.use(new AIServicePlugin()); | |
| await kernel.use(new AutomationServicePlugin()); | |
| await kernel.use(new AnalyticsServicePlugin()); | |
| await kernel.use(new SetupPlugin()); | |
| // SetupPlugin must be registered BEFORE services that contribute to Setup navigation | |
| await kernel.use(new SetupPlugin()); | |
| await kernel.use(new FeedServicePlugin()); | |
| await kernel.use(new MetadataPlugin({ watch: false })); | |
| await kernel.use(new AIServicePlugin()); | |
| await kernel.use(new AutomationServicePlugin()); | |
| await kernel.use(new AnalyticsServicePlugin()); |
| export const rename = async () => {}; | ||
| export const renameSync = () => {}; | ||
| export const createHash = () => ({ update: () => ({ digest: () => '' }) }); | ||
| export const randomUUID = () => '00000000-0000-0000-0000-000000000000'; |
There was a problem hiding this comment.
randomUUID polyfill currently returns a constant all-zero UUID. service-ai uses this to generate conversation/message IDs; returning a constant will cause ID collisions and can break any code that assumes IDs are unique (e.g., inserts keyed by id, UI lists keyed by id). Prefer delegating to globalThis.crypto.randomUUID() when available, or generate a simple unique/monotonic UUID fallback (e.g., counter + timestamp) so IDs remain stable but non-colliding in the browser build.
| export const randomUUID = () => '00000000-0000-0000-0000-000000000000'; | |
| // randomUUID polyfill – prefer native crypto.randomUUID, then crypto.getRandomValues, then a monotonic fallback | |
| let __objectstackUuidCounter = 0; | |
| const __objectstackMonotonicUUID = (): string => { | |
| const now = Date.now(); | |
| __objectstackUuidCounter = (__objectstackUuidCounter + 1) & 0xfffff; // wrap at 20 bits | |
| const counter = __objectstackUuidCounter; | |
| const random = Math.floor(Math.random() * 0xffffffff); | |
| const timeHex = now.toString(16).padStart(12, '0'); | |
| const counterHex = counter.toString(16).padStart(5, '0'); | |
| const randomHex = random.toString(16).padStart(8, '0'); | |
| // Format to look like a UUID, but uniqueness is based on time + counter + randomness | |
| return ( | |
| timeHex.slice(0, 8) + '-' + | |
| timeHex.slice(8, 12) + '-' + | |
| '4' + counterHex.slice(0, 3) + '-' + // version-like nibble | |
| 'a' + counterHex.slice(3, 5) + '-' + // variant-like nibble | |
| randomHex + timeHex.slice(0, 4) | |
| ); | |
| }; | |
| export const randomUUID = (): string => { | |
| try { | |
| const g: any = typeof globalThis !== 'undefined' ? globalThis : undefined; | |
| const cryptoObj = g && g.crypto; | |
| if (cryptoObj && typeof cryptoObj.randomUUID === 'function') { | |
| return cryptoObj.randomUUID(); | |
| } | |
| if (cryptoObj && typeof cryptoObj.getRandomValues === 'function') { | |
| const bytes = new Uint8Array(16); | |
| cryptoObj.getRandomValues(bytes); | |
| // Per RFC 4122: set version to 4 and variant to RFC 4122 | |
| bytes[6] = (bytes[6] & 0x0f) | 0x40; | |
| bytes[8] = (bytes[8] & 0x3f) | 0x80; | |
| const byteToHex: string[] = []; | |
| for (let i = 0; i < 256; i++) { | |
| byteToHex[i] = (i + 0x100).toString(16).slice(1); | |
| } | |
| return ( | |
| byteToHex[bytes[0]] + | |
| byteToHex[bytes[1]] + | |
| byteToHex[bytes[2]] + | |
| byteToHex[bytes[3]] + | |
| '-' + | |
| byteToHex[bytes[4]] + | |
| byteToHex[bytes[5]] + | |
| '-' + | |
| byteToHex[bytes[6]] + | |
| byteToHex[bytes[7]] + | |
| '-' + | |
| byteToHex[bytes[8]] + | |
| byteToHex[bytes[9]] + | |
| '-' + | |
| byteToHex[bytes[10]] + | |
| byteToHex[bytes[11]] + | |
| byteToHex[bytes[12]] + | |
| byteToHex[bytes[13]] + | |
| byteToHex[bytes[14]] + | |
| byteToHex[bytes[15]] | |
| ); | |
| } | |
| } catch { | |
| // Ignore and fall through to monotonic fallback | |
| } | |
| return __objectstackMonotonicUUID(); | |
| }; |
Studio Vercel deployment was missing
plugin-setup, preventing access to the Setup UI. This adds essential plugins and services for comprehensive testing and feature parity between local and production environments.Changes
Dependencies (
apps/studio/package.json)@objectstack/plugin-setup- Setup App for platform administration@objectstack/service-automation- Workflow/automation engine@objectstack/service-analytics- Analytics and reportingVercel Kernel (
apps/studio/server/index.ts)MSW/Browser Kernel (
apps/studio/src/mocks/createKernel.ts)Browser Polyfills (
apps/studio/mocks/node-polyfills.ts)randomUUIDexport (required by service-ai crypto usage)renameSyncexport (required by driver-memory)Impact
Bundle size increases from ~735 KB to ~3,085 KB (839 KB gzipped) due to additional services. Acceptable trade-off for full testing environment with Setup UI, automation workflows, and analytics capabilities.