diff --git a/CHANGELOG.md b/CHANGELOG.md index 77db58bb9..80b7b0861 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Preserved skill independence — Each skill remains independently installable/referenceable (no global routing required) ### Fixed +- **MetadataPlugin driver bridging fallback** — Fixed `MetadataPlugin.start()` so the driver service scan fallback (`driver.*`) is reached when ObjectQL returns `null` (not just when it throws). Previously, `setDatabaseDriver` was never called in environments where ObjectQL was not loaded. +- **Auth trustedOrigins test alignment** — Updated `plugin-auth` tests to match the auto-default `http://localhost:*` behavior added in PR #1152 for better-auth CORS support. When no `trustedOrigins` are configured, the implementation correctly defaults to trusting all localhost ports for development convenience. +- **Docs build: lucide-react module resolution** — Added Turbopack `resolveAlias` in `apps/docs/next.config.mjs` so MDX content files in `content/docs/` (outside the app directory) can resolve `lucide-react`. Turbopack starts module resolution from the file's directory, which doesn't have access to the app's `node_modules/`. +- **Client Hono integration test timeout** — Fixed `afterAll` hook timeout in `client.hono.test.ts` by racing `kernel.shutdown()` against a 10s deadline. The shutdown can hang when pino's worker-thread flush callback never fires in CI, so the race ensures the hook completes within the 30s vitest limit. - **CI: Replace `pnpm/action-setup@v6` with corepack** — Switched all GitHub Actions workflows (`ci.yml`, `lint.yml`, `release.yml`, `validate-deps.yml`, `pr-automation.yml`) from `pnpm/action-setup@v6` to `corepack enable` to fix persistent `ERR_PNPM_BROKEN_LOCKFILE` errors. Corepack reads the exact `packageManager` field from `package.json` (including SHA verification), ensuring the correct pnpm version is used in CI. Also bumped pnpm store cache keys to v3 and added a pnpm version verification step. - **Broken pnpm lockfile** — Regenerated `pnpm-lock.yaml` from scratch to fix `ERR_PNPM_BROKEN_LOCKFILE` ("expected a single document in the stream, but found more") that was causing all CI jobs to fail. The previous merge of PR #1117 only included workflow cache key changes but did not carry over the regenerated lockfile. - **service-ai: Fix navigation item labels using deprecated i18n object format** — Replaced `{ key, defaultValue }` i18n objects with plain string labels in `AIServicePlugin`'s Setup App navigation contributions, completing the `I18nLabelSchema` migration from [#1054](https://github.com/objectstack-ai/framework/issues/1054). diff --git a/apps/docs/next.config.mjs b/apps/docs/next.config.mjs index 26e2d7e2f..44d31efc5 100644 --- a/apps/docs/next.config.mjs +++ b/apps/docs/next.config.mjs @@ -9,6 +9,15 @@ const config = { typescript: { ignoreBuildErrors: false, }, + turbopack: { + resolveAlias: { + // MDX content lives in ../../content/docs/ (outside the app directory). + // Turbopack resolves modules starting from the file's directory, so it + // can't find packages installed under this app's node_modules/. + // Alias lucide-react so external MDX files can import it. + 'lucide-react': './node_modules/lucide-react', + }, + }, images: { remotePatterns: [ { diff --git a/packages/client/src/client.hono.test.ts b/packages/client/src/client.hono.test.ts index b8e2a847b..2a7aa90c8 100644 --- a/packages/client/src/client.hono.test.ts +++ b/packages/client/src/client.hono.test.ts @@ -107,11 +107,19 @@ describe('ObjectStackClient (with Hono Server)', () => { baseUrl = `http://localhost:${port}`; console.log(`Test server running at ${baseUrl}`); - }); + }, 30_000); afterAll(async () => { - if (kernel) await kernel.shutdown(); - }); + if (kernel) { + // Race shutdown against a hard deadline. + // kernel.shutdown() can hang when pino's flush callback never fires + // in CI (worker-thread transport timing issues), so cap the wait. + await Promise.race([ + kernel.shutdown(), + new Promise((resolve) => setTimeout(resolve, 10_000)), + ]); + } + }, 30_000); it('should connect to hono server and discover endpoints', async () => { const client = new ObjectStackClient({ baseUrl }); diff --git a/packages/metadata/src/plugin.ts b/packages/metadata/src/plugin.ts index a2491ce21..3e3778ba8 100644 --- a/packages/metadata/src/plugin.ts +++ b/packages/metadata/src/plugin.ts @@ -107,6 +107,8 @@ export class MetadataPlugin implements Plugin { // Bridge database driver from kernel service registry to MetadataManager. // Uses ObjectQL engine's datasource mapping to resolve the correct driver // for sys_metadata (respects namespace → datasource routing). + // Falls back to the first available driver.* service when ObjectQL is unavailable. + let driverBridged = false; try { const ql = ctx.getService('objectql'); if (ql) { @@ -118,12 +120,17 @@ export class MetadataPlugin implements Plugin { driver: driver.name, }); this.manager.setDatabaseDriver(driver); + driverBridged = true; } else { ctx.logger.debug('[MetadataPlugin] ObjectQL could not resolve driver for metadata table', { tableName }); } } } catch { - // ObjectQL not available — fall back to first available driver service + // ObjectQL not available — will fall through to driver service fallback below + } + + // Fallback: scan for driver.* services when ObjectQL didn't provide a driver + if (!driverBridged) { try { const services = ctx.getServices(); for (const [serviceName, service] of services) { diff --git a/packages/plugins/plugin-auth/src/auth-manager.test.ts b/packages/plugins/plugin-auth/src/auth-manager.test.ts index 73aa9cddd..e010ee4b2 100644 --- a/packages/plugins/plugin-auth/src/auth-manager.test.ts +++ b/packages/plugins/plugin-auth/src/auth-manager.test.ts @@ -434,7 +434,7 @@ describe('AuthManager', () => { ]); }); - it('should NOT include trustedOrigins key when not provided', () => { + it('should default to localhost wildcard when trustedOrigins not provided', () => { let capturedConfig: any; (betterAuth as any).mockImplementation((config: any) => { capturedConfig = config; @@ -449,10 +449,10 @@ describe('AuthManager', () => { manager.getAuthInstance(); warnSpy.mockRestore(); - expect(capturedConfig).not.toHaveProperty('trustedOrigins'); + expect(capturedConfig.trustedOrigins).toEqual(['http://localhost:*']); }); - it('should NOT include trustedOrigins key when array is empty', () => { + it('should default to localhost wildcard when trustedOrigins array is empty', () => { let capturedConfig: any; (betterAuth as any).mockImplementation((config: any) => { capturedConfig = config; @@ -468,7 +468,7 @@ describe('AuthManager', () => { manager.getAuthInstance(); warnSpy.mockRestore(); - expect(capturedConfig).not.toHaveProperty('trustedOrigins'); + expect(capturedConfig.trustedOrigins).toEqual(['http://localhost:*']); }); });