fix(platform): production readiness — admin-web client bundling, config sub-path exports, stale tests
- dashboards/admin-web: split product-constants.ts for client-safe imports - dashboards/admin-web: serverExternalPackages + webpack fallbacks for @bytelyst/config - dashboards/admin-web: instrumentation.ts uses @bytelyst/config/keyvault sub-path - packages/config: add ./keyvault and ./product-identity sub-path exports - packages/feedback-client: fix stale test expectation (TODO-1 → actual error message) - packages/sync: fix reprocessFailed test (flush already pushes items)
This commit is contained in:
parent
9438085cc0
commit
5195f9c052
@ -43,6 +43,21 @@ const securityHeaders = [
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
...(process.env.VERCEL ? {} : { output: 'standalone' }),
|
||||
serverExternalPackages: ['@bytelyst/config'],
|
||||
webpack: (config, { isServer }) => {
|
||||
if (!isServer) {
|
||||
// Prevent Node.js modules from being bundled in client code
|
||||
config.resolve = config.resolve ?? {};
|
||||
config.resolve.fallback = {
|
||||
...config.resolve.fallback,
|
||||
fs: false,
|
||||
path: false,
|
||||
'fs/promises': false,
|
||||
crypto: false,
|
||||
};
|
||||
}
|
||||
return config;
|
||||
},
|
||||
async headers() {
|
||||
return [
|
||||
{
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
* any route handlers or Cosmos client initialization.
|
||||
*/
|
||||
|
||||
import { resolveSecrets, LYSNR_SECRETS } from '@bytelyst/config';
|
||||
import { resolveSecrets, LYSNR_SECRETS } from '@bytelyst/config/keyvault';
|
||||
|
||||
export async function register() {
|
||||
if (process.env.NEXT_RUNTIME === 'nodejs') {
|
||||
|
||||
17
dashboards/admin-web/src/lib/product-constants.ts
Normal file
17
dashboards/admin-web/src/lib/product-constants.ts
Normal file
@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Client-safe product constants — no Node.js imports.
|
||||
*
|
||||
* Use this in 'use client' components. For server-side code that needs
|
||||
* loadProductIdentity(), import from product-config.ts instead.
|
||||
*/
|
||||
|
||||
export const PRODUCT_ID = process.env.NEXT_PUBLIC_PRODUCT_ID || process.env.PRODUCT_ID || 'lysnrai';
|
||||
export const DISPLAY_NAME = process.env.NEXT_PUBLIC_DISPLAY_NAME || 'LysnrAI';
|
||||
|
||||
/** All known products in the ByteLyst ecosystem. */
|
||||
export const KNOWN_PRODUCTS = [
|
||||
{ id: 'lysnrai', name: 'LysnrAI', icon: 'Mic' },
|
||||
{ id: 'chronomind', name: 'ChronoMind', icon: 'Clock' },
|
||||
{ id: 'nomgap', name: 'NomGap', icon: 'Apple' },
|
||||
{ id: 'mindlyst', name: 'MindLyst', icon: 'Brain' },
|
||||
] as const;
|
||||
@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { createContext, useContext, useState, useCallback, type ReactNode } from 'react';
|
||||
import { KNOWN_PRODUCTS, PRODUCT_ID } from '@/lib/product-config';
|
||||
import { KNOWN_PRODUCTS, PRODUCT_ID } from '@/lib/product-constants';
|
||||
|
||||
const STORAGE_KEY = 'admin_selected_product';
|
||||
|
||||
|
||||
@ -6,6 +6,14 @@
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
},
|
||||
"./keyvault": {
|
||||
"import": "./dist/keyvault.js",
|
||||
"types": "./dist/keyvault.d.ts"
|
||||
},
|
||||
"./product-identity": {
|
||||
"import": "./dist/product-identity.js",
|
||||
"types": "./dist/product-identity.d.ts"
|
||||
}
|
||||
},
|
||||
"main": "./dist/index.js",
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { FeedbackClient, type SubmitFeedbackParams } from './index.js';
|
||||
import { FeedbackClient } from './index.js';
|
||||
import type { ApiClient } from '@bytelyst/api-client';
|
||||
|
||||
describe('FeedbackClient', () => {
|
||||
@ -30,16 +30,19 @@ describe('FeedbackClient', () => {
|
||||
});
|
||||
|
||||
expect(result).toEqual(mockResponse);
|
||||
expect(mockApi.fetch).toHaveBeenCalledWith('/api/feedback', expect.objectContaining({
|
||||
method: 'POST',
|
||||
}));
|
||||
expect(mockApi.fetch).toHaveBeenCalledWith(
|
||||
'/api/feedback',
|
||||
expect.objectContaining({
|
||||
method: 'POST',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw if captureAndSubmit called without screenshot', async () => {
|
||||
const client = createClient();
|
||||
|
||||
await expect(
|
||||
client.captureAndSubmit({ type: 'bug', title: 'Test' })
|
||||
).rejects.toThrow('TODO-1');
|
||||
await expect(client.captureAndSubmit({ type: 'bug', title: 'Test' })).rejects.toThrow(
|
||||
'Screenshot capture only available in browser environment'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -220,11 +220,12 @@ describe('Sync Engine', () => {
|
||||
});
|
||||
|
||||
await engine.push('tasks', { title: 'Test' });
|
||||
const requestsBefore = apiClient.getRequests().length;
|
||||
await engine.reprocessFailed();
|
||||
|
||||
// Items should be reprocessed
|
||||
const result = await engine.fullSync();
|
||||
expect(result.pushed).toBe(1);
|
||||
// reprocessFailed calls flush() which pushes the item
|
||||
const requestsAfter = apiClient.getRequests().length;
|
||||
expect(requestsAfter).toBeGreaterThan(requestsBefore);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user