@trokky/studio 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/index-CSDhJo2Z.js +742 -0
- package/dist/assets/index-CSDhJo2Z.js.map +1 -0
- package/dist/assets/index-DykyF7LU.css +1 -0
- package/dist/assets/query-CieM36kR.js +2 -0
- package/dist/assets/query-CieM36kR.js.map +1 -0
- package/dist/assets/rolldown-runtime-CbXtAM7H.js +1 -0
- package/dist/assets/router-Dpg4KYTB.js +5 -0
- package/dist/assets/router-Dpg4KYTB.js.map +1 -0
- package/dist/assets/ui-D5AGhrb0.js +2 -0
- package/dist/assets/ui-D5AGhrb0.js.map +1 -0
- package/dist/assets/vendor-igJJvL5X.js +10 -0
- package/dist/assets/vendor-igJJvL5X.js.map +1 -0
- package/dist/index.d.ts +69 -0
- package/dist/index.html +61 -0
- package/dist/index.js +199 -0
- package/dist/server/assets.d.ts +31 -0
- package/dist/server/assets.js +169 -0
- package/dist/utils/logger.d.ts +35 -0
- package/dist/utils/logger.js +114 -0
- package/package.json +107 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @trokky/studio - Trokky CMS Studio
|
|
3
|
+
*
|
|
4
|
+
* A clean, maintainable Studio for Trokky CMS that works with API routers.
|
|
5
|
+
*/
|
|
6
|
+
interface StudioConfig {
|
|
7
|
+
/** API router for Studio to connect to (from @trokky/express or any Trokky API integration) */
|
|
8
|
+
apiRouter: any;
|
|
9
|
+
/** Mount point for Studio (default: '/studio') */
|
|
10
|
+
mount?: string;
|
|
11
|
+
/** Enable authentication */
|
|
12
|
+
auth?: boolean;
|
|
13
|
+
/** Studio branding configuration */
|
|
14
|
+
branding?: {
|
|
15
|
+
title?: string;
|
|
16
|
+
logo?: string;
|
|
17
|
+
theme?: 'light' | 'dark' | 'system';
|
|
18
|
+
};
|
|
19
|
+
/** Custom structure configuration */
|
|
20
|
+
structure?: any;
|
|
21
|
+
/** Custom field types to register */
|
|
22
|
+
customFields?: any[];
|
|
23
|
+
/** Additional Studio configuration */
|
|
24
|
+
config?: {
|
|
25
|
+
pageSize?: number;
|
|
26
|
+
enableDrafts?: boolean;
|
|
27
|
+
enableVersioning?: boolean;
|
|
28
|
+
};
|
|
29
|
+
/** Session management configuration */
|
|
30
|
+
sessionConfig?: {
|
|
31
|
+
/** Auto-refresh token buffer in milliseconds (default: 30000) */
|
|
32
|
+
refreshBufferMs?: number;
|
|
33
|
+
/** Warning display buffer in milliseconds (default: 90000) */
|
|
34
|
+
warningBufferMs?: number;
|
|
35
|
+
/** Session check interval in milliseconds (default: 5000) */
|
|
36
|
+
checkIntervalMs?: number;
|
|
37
|
+
/** Default session timeout in milliseconds (default: 2 hours) */
|
|
38
|
+
defaultTimeoutMs?: number;
|
|
39
|
+
/** Extended session timeout for "Remember Me" in milliseconds (default: 7 days) */
|
|
40
|
+
extendedTimeoutMs?: number;
|
|
41
|
+
/** Inactivity timeout in milliseconds (default: 30 minutes) */
|
|
42
|
+
inactivityTimeoutMs?: number;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
interface StudioMiddleware {
|
|
46
|
+
/** Express router containing Studio routes */
|
|
47
|
+
router: any;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Create a Trokky Studio middleware
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```typescript
|
|
54
|
+
* import { createStudio } from '@trokky/studio'
|
|
55
|
+
*
|
|
56
|
+
* const studio = createStudio({
|
|
57
|
+
* apiRouter: myApiRouter,
|
|
58
|
+
* schemas: cms.getAllSchemas(),
|
|
59
|
+
* branding: { title: 'My CMS Admin' }
|
|
60
|
+
* })
|
|
61
|
+
*
|
|
62
|
+
* app.use('/studio', studio.router)
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export declare function createStudio(config: StudioConfig): Promise<StudioMiddleware>;
|
|
66
|
+
export { createStudioLogger, StudioLogger } from './utils/logger.js';
|
|
67
|
+
export type { LogLevel, StudioLoggerConfig } from './utils/logger.js';
|
|
68
|
+
export { getStudioHTML, getStudioAsset } from './server/assets.js';
|
|
69
|
+
export type { StudioConfig, StudioMiddleware };
|
package/dist/index.html
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<script>
|
|
5
|
+
window.TROKKY_CONFIG = {
|
|
6
|
+
mode: 'development',
|
|
7
|
+
basePath: '',
|
|
8
|
+
branding: {
|
|
9
|
+
title: 'Trokky Studio (Dev)',
|
|
10
|
+
theme: 'system'
|
|
11
|
+
},
|
|
12
|
+
structure: null,
|
|
13
|
+
config: {},
|
|
14
|
+
customFields: []
|
|
15
|
+
};
|
|
16
|
+
</script>
|
|
17
|
+
<meta charset="UTF-8" />
|
|
18
|
+
<link rel="icon" type="image/svg+xml" href="/trokky-icon.svg" />
|
|
19
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
20
|
+
<meta name="description" content="Trokky Studio v2 - Zero-config content management interface" />
|
|
21
|
+
|
|
22
|
+
<!-- Theme variables with defaults (can be overridden at runtime) -->
|
|
23
|
+
<style>
|
|
24
|
+
:root {
|
|
25
|
+
/* Default blue theme */
|
|
26
|
+
--color-primary-50: 239 246 255;
|
|
27
|
+
--color-primary-100: 219 234 254;
|
|
28
|
+
--color-primary-200: 191 219 254;
|
|
29
|
+
--color-primary-300: 147 197 253;
|
|
30
|
+
--color-primary-400: 96 165 250;
|
|
31
|
+
--color-primary-500: 59 130 246;
|
|
32
|
+
--color-primary-600: 37 99 235;
|
|
33
|
+
--color-primary-700: 29 78 216;
|
|
34
|
+
--color-primary-800: 30 64 175;
|
|
35
|
+
--color-primary-900: 30 58 138;
|
|
36
|
+
--color-primary-950: 23 37 84;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* Dark mode by default respects system preference */
|
|
40
|
+
@media (prefers-color-scheme: dark) {
|
|
41
|
+
html:not(.light) {
|
|
42
|
+
color-scheme: dark;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
</style>
|
|
46
|
+
|
|
47
|
+
<title>Trokky Studio</title>
|
|
48
|
+
<script type="module" crossorigin src="/assets/index-CSDhJo2Z.js"></script>
|
|
49
|
+
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-CbXtAM7H.js">
|
|
50
|
+
<link rel="modulepreload" crossorigin href="/assets/router-Dpg4KYTB.js">
|
|
51
|
+
<link rel="modulepreload" crossorigin href="/assets/vendor-igJJvL5X.js">
|
|
52
|
+
<link rel="modulepreload" crossorigin href="/assets/query-CieM36kR.js">
|
|
53
|
+
<link rel="modulepreload" crossorigin href="/assets/ui-D5AGhrb0.js">
|
|
54
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DykyF7LU.css">
|
|
55
|
+
</head>
|
|
56
|
+
<body class="bg-gray-50 dark:bg-gray-900">
|
|
57
|
+
<div id="root"></div>
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
</body>
|
|
61
|
+
</html>
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @trokky/studio - Trokky CMS Studio
|
|
3
|
+
*
|
|
4
|
+
* A clean, maintainable Studio for Trokky CMS that works with API routers.
|
|
5
|
+
*/
|
|
6
|
+
import { createStudioLogger } from './utils/logger.js';
|
|
7
|
+
/**
|
|
8
|
+
* Create a Trokky Studio middleware
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { createStudio } from '@trokky/studio'
|
|
13
|
+
*
|
|
14
|
+
* const studio = createStudio({
|
|
15
|
+
* apiRouter: myApiRouter,
|
|
16
|
+
* schemas: cms.getAllSchemas(),
|
|
17
|
+
* branding: { title: 'My CMS Admin' }
|
|
18
|
+
* })
|
|
19
|
+
*
|
|
20
|
+
* app.use('/studio', studio.router)
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export async function createStudio(config) {
|
|
24
|
+
const logger = createStudioLogger('Studio');
|
|
25
|
+
// Validate configuration
|
|
26
|
+
if (!config.apiRouter) {
|
|
27
|
+
throw new Error('apiRouter is required in config');
|
|
28
|
+
}
|
|
29
|
+
// Note: Schemas are now fetched from /api/collections endpoint
|
|
30
|
+
// Import Express synchronously for Node.js environment
|
|
31
|
+
let router;
|
|
32
|
+
try {
|
|
33
|
+
const { createRequire } = await import('module');
|
|
34
|
+
const require = createRequire(import.meta.url);
|
|
35
|
+
const express = require('express');
|
|
36
|
+
router = express.Router();
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
console.error('[ERROR] Failed to import Express:', e);
|
|
40
|
+
throw new Error('Express is required for Studio but could not be imported');
|
|
41
|
+
}
|
|
42
|
+
logger.info('Creating Studio', {
|
|
43
|
+
mount: config.mount || '/studio',
|
|
44
|
+
auth: config.auth || false,
|
|
45
|
+
customFields: config.customFields?.length || 0
|
|
46
|
+
});
|
|
47
|
+
// Serve Studio static assets
|
|
48
|
+
router.get('/', serveStudioHTML(config));
|
|
49
|
+
router.get('/assets/*', serveStudioAssets());
|
|
50
|
+
// Note: API router is mounted at main server level (/api), not here
|
|
51
|
+
// router.use('/api', config.apiRouter); // Removed - API is at server root level
|
|
52
|
+
// Catch-all route for SPA routing - must be last
|
|
53
|
+
router.get('*', serveStudioHTML(config));
|
|
54
|
+
return { router };
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Serve the Studio HTML with injected configuration
|
|
58
|
+
*/
|
|
59
|
+
function serveStudioHTML(config) {
|
|
60
|
+
return async (_req, res) => {
|
|
61
|
+
try {
|
|
62
|
+
// Get Studio HTML template (only works in Node.js)
|
|
63
|
+
let html;
|
|
64
|
+
try {
|
|
65
|
+
const { createRequire } = await import('module');
|
|
66
|
+
const require = createRequire(import.meta.url);
|
|
67
|
+
const url = require('url');
|
|
68
|
+
const path = require('path');
|
|
69
|
+
const fs = require('fs');
|
|
70
|
+
const currentFile = url.fileURLToPath(import.meta.url);
|
|
71
|
+
const currentDir = path.dirname(currentFile);
|
|
72
|
+
// Check if we're running from TypeScript source or compiled dist
|
|
73
|
+
const isTypeScript = currentFile.includes('/src/');
|
|
74
|
+
const htmlPath = isTypeScript
|
|
75
|
+
? path.join(currentDir, '../dist/index.html') // From src/ to dist/
|
|
76
|
+
: path.join(currentDir, 'index.html'); // Already in dist/
|
|
77
|
+
html = fs.readFileSync(htmlPath, 'utf-8');
|
|
78
|
+
}
|
|
79
|
+
catch (e) {
|
|
80
|
+
console.error('[ERROR] Failed to read Studio HTML:', e.message);
|
|
81
|
+
// Provide basic HTML template
|
|
82
|
+
html = `<!DOCTYPE html>
|
|
83
|
+
<html lang="en">
|
|
84
|
+
<head>
|
|
85
|
+
<meta charset="UTF-8">
|
|
86
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
87
|
+
<title>Trokky Studio</title>
|
|
88
|
+
</head>
|
|
89
|
+
<body>
|
|
90
|
+
<div id="root">
|
|
91
|
+
<div style="display: flex; align-items: center; justify-content: center; height: 100vh; font-family: system-ui, sans-serif;">
|
|
92
|
+
<div style="text-align: center;">
|
|
93
|
+
<h1>Trokky Studio</h1>
|
|
94
|
+
<p>Loading...</p>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
</body>
|
|
99
|
+
</html>`;
|
|
100
|
+
}
|
|
101
|
+
// Inject runtime configuration and set base href
|
|
102
|
+
const basePath = _req.baseUrl || '/studio';
|
|
103
|
+
const runtimeConfig = {
|
|
104
|
+
mode: process.env.NODE_ENV || 'production',
|
|
105
|
+
apiUrl: '/api', // API is at server root level
|
|
106
|
+
apiBaseUrl: '/api',
|
|
107
|
+
branding: config.branding || { title: 'Trokky Studio' },
|
|
108
|
+
structure: config.structure || null,
|
|
109
|
+
config: config.config || {},
|
|
110
|
+
customFields: config.customFields || [],
|
|
111
|
+
sessionConfig: config.sessionConfig || null,
|
|
112
|
+
basePath
|
|
113
|
+
};
|
|
114
|
+
// Safe JSON serialization that handles functions and circular references
|
|
115
|
+
const safeStringify = (obj) => {
|
|
116
|
+
try {
|
|
117
|
+
return JSON.stringify(obj, (_, value) => {
|
|
118
|
+
if (typeof value === 'function') {
|
|
119
|
+
return undefined; // Remove functions
|
|
120
|
+
}
|
|
121
|
+
if (typeof value === 'object' && value !== null) {
|
|
122
|
+
// Simple circular reference check
|
|
123
|
+
if (obj._seen && obj._seen.has(value)) {
|
|
124
|
+
return '[Circular]';
|
|
125
|
+
}
|
|
126
|
+
if (!obj._seen)
|
|
127
|
+
obj._seen = new Set();
|
|
128
|
+
obj._seen.add(value);
|
|
129
|
+
}
|
|
130
|
+
return value;
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
console.error('[ERROR] JSON serialization failed:', error);
|
|
135
|
+
return '{}';
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
const configJson = safeStringify(runtimeConfig);
|
|
139
|
+
console.log('[DEBUG] Runtime config length:', configJson.length);
|
|
140
|
+
// Override any hardcoded TROKKY_CONFIG from Vite build
|
|
141
|
+
// Use a more robust replacement that handles nested objects
|
|
142
|
+
html = html.replace(/window\.TROKKY_CONFIG\s*=\s*\{[\s\S]*?\};/g, `window.TROKKY_CONFIG = ${configJson};`);
|
|
143
|
+
html = html.replace('<head>', `<head>
|
|
144
|
+
<base href="${basePath}/">
|
|
145
|
+
<script>
|
|
146
|
+
window.TROKKY_CONFIG = ${configJson};
|
|
147
|
+
</script>`);
|
|
148
|
+
res.setHeader('Content-Type', 'text/html');
|
|
149
|
+
res.send(html);
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
console.error('[ERROR] Failed to serve Studio HTML:', error);
|
|
153
|
+
res.status(500).json({ error: 'Failed to serve Studio' });
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Serve Studio static assets
|
|
159
|
+
*/
|
|
160
|
+
function serveStudioAssets() {
|
|
161
|
+
return async (req, res) => {
|
|
162
|
+
try {
|
|
163
|
+
// Assets handling only works in Node.js environment
|
|
164
|
+
const { createRequire } = await import('module');
|
|
165
|
+
const require = createRequire(import.meta.url);
|
|
166
|
+
const url = require('url');
|
|
167
|
+
const path = require('path');
|
|
168
|
+
const currentFile = url.fileURLToPath(import.meta.url);
|
|
169
|
+
const currentDir = path.dirname(currentFile);
|
|
170
|
+
// Check if we're running from TypeScript source or compiled dist
|
|
171
|
+
const isTypeScript = currentFile.includes('/src/');
|
|
172
|
+
const assetPath = isTypeScript
|
|
173
|
+
? path.join(currentDir, '..', req.path) // From src/ to dist/
|
|
174
|
+
: path.join(currentDir, req.path); // Already in dist/
|
|
175
|
+
// Set proper MIME types
|
|
176
|
+
const ext = path.extname(req.path);
|
|
177
|
+
if (ext === '.css') {
|
|
178
|
+
res.setHeader('Content-Type', 'text/css');
|
|
179
|
+
}
|
|
180
|
+
else if (ext === '.js') {
|
|
181
|
+
res.setHeader('Content-Type', 'application/javascript');
|
|
182
|
+
}
|
|
183
|
+
res.sendFile(assetPath, (err) => {
|
|
184
|
+
if (err) {
|
|
185
|
+
console.error('[ERROR] Failed to serve asset:', req.path, err.message);
|
|
186
|
+
res.status(404).json({ error: 'Asset not found' });
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
catch (e) {
|
|
191
|
+
// Return 404 for asset not found
|
|
192
|
+
res.status(404).json({ error: 'Asset not found' });
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
// Export Studio logger for custom components
|
|
197
|
+
export { createStudioLogger, StudioLogger } from './utils/logger.js';
|
|
198
|
+
// Export Studio asset serving utilities
|
|
199
|
+
export { getStudioHTML, getStudioAsset } from './server/assets.js';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Studio Asset Serving Utilities
|
|
3
|
+
*
|
|
4
|
+
* These utilities allow other packages (like @trokky/routes) to serve
|
|
5
|
+
* Studio HTML and assets without needing to resolve package paths.
|
|
6
|
+
*/
|
|
7
|
+
export interface StudioConfig {
|
|
8
|
+
mode: 'production';
|
|
9
|
+
apiBasePath: string;
|
|
10
|
+
basePath?: string;
|
|
11
|
+
backendUrl?: string;
|
|
12
|
+
schemas: any[];
|
|
13
|
+
branding?: {
|
|
14
|
+
title?: string;
|
|
15
|
+
theme?: 'light' | 'dark' | 'system';
|
|
16
|
+
};
|
|
17
|
+
structure?: any;
|
|
18
|
+
config?: any;
|
|
19
|
+
customFields?: any[];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Get Studio HTML with injected configuration
|
|
23
|
+
*/
|
|
24
|
+
export declare function getStudioHTML(config: StudioConfig, studioPath: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Get Studio asset content
|
|
27
|
+
*/
|
|
28
|
+
export declare function getStudioAsset(assetPath: string): {
|
|
29
|
+
content: Buffer;
|
|
30
|
+
contentType: string;
|
|
31
|
+
} | null;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Studio Asset Serving Utilities
|
|
3
|
+
*
|
|
4
|
+
* These utilities allow other packages (like @trokky/routes) to serve
|
|
5
|
+
* Studio HTML and assets without needing to resolve package paths.
|
|
6
|
+
*/
|
|
7
|
+
import { readFileSync, existsSync } from 'fs';
|
|
8
|
+
import { join, dirname, extname } from 'path';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
/**
|
|
11
|
+
* Escape a string for safe embedding in HTML content.
|
|
12
|
+
*/
|
|
13
|
+
function escapeHtml(str) {
|
|
14
|
+
return str
|
|
15
|
+
.replace(/&/g, '&')
|
|
16
|
+
.replace(/</g, '<')
|
|
17
|
+
.replace(/>/g, '>')
|
|
18
|
+
.replace(/"/g, '"')
|
|
19
|
+
.replace(/'/g, ''');
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Safely serialize an object for embedding inside a <script> tag.
|
|
23
|
+
* Prevents breaking out via </script> or similar sequences.
|
|
24
|
+
*/
|
|
25
|
+
function escapeJsonForScript(obj) {
|
|
26
|
+
return JSON.stringify(obj)
|
|
27
|
+
.replace(/</g, '\\u003c')
|
|
28
|
+
.replace(/>/g, '\\u003e')
|
|
29
|
+
.replace(/&/g, '\\u0026')
|
|
30
|
+
.replace(/'/g, '\\u0027');
|
|
31
|
+
}
|
|
32
|
+
// Get the Studio package root directory (assuming we're in dist/server/ when compiled)
|
|
33
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
34
|
+
const __dirname = dirname(__filename);
|
|
35
|
+
const studioDir = join(__dirname, '../..');
|
|
36
|
+
const distDir = join(studioDir, 'dist');
|
|
37
|
+
/**
|
|
38
|
+
* Get Studio HTML with injected configuration
|
|
39
|
+
*/
|
|
40
|
+
export function getStudioHTML(config, studioPath) {
|
|
41
|
+
try {
|
|
42
|
+
const htmlPath = join(distDir, 'index.html');
|
|
43
|
+
if (!existsSync(htmlPath)) {
|
|
44
|
+
throw new Error(`Studio HTML not found at ${htmlPath}. Please build @trokky/studio package.`);
|
|
45
|
+
}
|
|
46
|
+
let html = readFileSync(htmlPath, 'utf-8');
|
|
47
|
+
// Replace any existing config with our config (handle multi-line objects)
|
|
48
|
+
html = html.replace(/window\.TROKKY_CONFIG\s*=\s*{[\s\S]*?};/, `window.TROKKY_CONFIG = ${escapeJsonForScript(config)};
|
|
49
|
+
|
|
50
|
+
// TEMPORARY FIX: Override API client path building for dynamic API paths
|
|
51
|
+
window.TROKKY_API_PATH_FIX = function() {
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
// Wait for the API client to be available
|
|
55
|
+
const checkApiClient = setInterval(() => {
|
|
56
|
+
if (window.TrokkyApiClient) {
|
|
57
|
+
|
|
58
|
+
clearInterval(checkApiClient);
|
|
59
|
+
|
|
60
|
+
// Store original post method
|
|
61
|
+
const originalPost = window.TrokkyApiClient.post;
|
|
62
|
+
|
|
63
|
+
// Override post method to handle dynamic API paths
|
|
64
|
+
window.TrokkyApiClient.post = function(endpoint, data) {
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
// Replace /api with the correct API base path
|
|
68
|
+
if (endpoint.startsWith('/api')) {
|
|
69
|
+
const newEndpoint = endpoint.replace('/api', '${escapeHtml(config.apiBasePath)}');
|
|
70
|
+
|
|
71
|
+
endpoint = newEndpoint;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return originalPost.call(this, endpoint, data);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
}
|
|
79
|
+
}, 100);
|
|
80
|
+
|
|
81
|
+
// Timeout after 5 seconds
|
|
82
|
+
setTimeout(() => {
|
|
83
|
+
clearInterval(checkApiClient);
|
|
84
|
+
|
|
85
|
+
}, 5000);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// Apply the fix after DOM loads
|
|
89
|
+
if (document.readyState === 'loading') {
|
|
90
|
+
document.addEventListener('DOMContentLoaded', window.TROKKY_API_PATH_FIX);
|
|
91
|
+
} else {
|
|
92
|
+
window.TROKKY_API_PATH_FIX();
|
|
93
|
+
}`);
|
|
94
|
+
// Update asset paths to use our Studio route
|
|
95
|
+
html = html.replace(/src="\/assets\//g, `src="${studioPath}/assets/`);
|
|
96
|
+
html = html.replace(/href="\/assets\//g, `href="${studioPath}/assets/`);
|
|
97
|
+
// Remove favicon reference since we don't have the icon
|
|
98
|
+
html = html.replace(/<link rel="icon"[^>]*>/g, '');
|
|
99
|
+
// Update title if provided
|
|
100
|
+
if (config.branding?.title) {
|
|
101
|
+
html = html.replace(/<title>.*?<\/title>/, `<title>${escapeHtml(config.branding.title)}</title>`);
|
|
102
|
+
}
|
|
103
|
+
return html;
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
107
|
+
// Fallback HTML if built assets are not available
|
|
108
|
+
return `<!DOCTYPE html>
|
|
109
|
+
<html lang="en">
|
|
110
|
+
<head>
|
|
111
|
+
<meta charset="UTF-8">
|
|
112
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
113
|
+
<title>${escapeHtml(config.branding?.title || 'Trokky Studio')}</title>
|
|
114
|
+
<script>
|
|
115
|
+
window.TROKKY_CONFIG = ${escapeJsonForScript(config)};
|
|
116
|
+
</script>
|
|
117
|
+
</head>
|
|
118
|
+
<body>
|
|
119
|
+
<div id="root">
|
|
120
|
+
<div style="display: flex; align-items: center; justify-content: center; height: 100vh; font-family: system-ui, sans-serif;">
|
|
121
|
+
<div style="text-align: center;">
|
|
122
|
+
<h1>${escapeHtml(config.branding?.title || 'Trokky Studio')}</h1>
|
|
123
|
+
<p>Studio not built. Please run: npm run build in @trokky/studio package.</p>
|
|
124
|
+
<p style="font-size: 12px; color: #666; margin-top: 16px;">Error: ${escapeHtml(errorMessage)}</p>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
</body>
|
|
129
|
+
</html>`;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Get Studio asset content
|
|
134
|
+
*/
|
|
135
|
+
export function getStudioAsset(assetPath) {
|
|
136
|
+
try {
|
|
137
|
+
const safePath = assetPath.replace(/\.\.\//g, ''); // Prevent directory traversal
|
|
138
|
+
const fullAssetPath = join(distDir, 'assets', safePath);
|
|
139
|
+
if (!existsSync(fullAssetPath)) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
const content = readFileSync(fullAssetPath);
|
|
143
|
+
// Determine content type based on file extension
|
|
144
|
+
const ext = extname(assetPath).toLowerCase();
|
|
145
|
+
let contentType = 'application/octet-stream';
|
|
146
|
+
if (ext === '.js') {
|
|
147
|
+
contentType = 'application/javascript';
|
|
148
|
+
}
|
|
149
|
+
else if (ext === '.css') {
|
|
150
|
+
contentType = 'text/css';
|
|
151
|
+
}
|
|
152
|
+
else if (ext === '.map') {
|
|
153
|
+
contentType = 'application/json';
|
|
154
|
+
}
|
|
155
|
+
else if (ext === '.svg') {
|
|
156
|
+
contentType = 'image/svg+xml';
|
|
157
|
+
}
|
|
158
|
+
else if (ext === '.png') {
|
|
159
|
+
contentType = 'image/png';
|
|
160
|
+
}
|
|
161
|
+
else if (ext === '.jpg' || ext === '.jpeg') {
|
|
162
|
+
contentType = 'image/jpeg';
|
|
163
|
+
}
|
|
164
|
+
return { content, contentType };
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Studio Logger - Browser-optimized logging
|
|
3
|
+
*
|
|
4
|
+
* Lightweight logger for Studio frontend components.
|
|
5
|
+
* Integrates with browser dev tools and provides structured output.
|
|
6
|
+
*/
|
|
7
|
+
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
8
|
+
export interface StudioLoggerConfig {
|
|
9
|
+
level: LogLevel;
|
|
10
|
+
enabled: boolean;
|
|
11
|
+
prefix: string;
|
|
12
|
+
}
|
|
13
|
+
export declare class StudioLogger {
|
|
14
|
+
private config;
|
|
15
|
+
constructor(component: string, config?: Partial<StudioLoggerConfig>);
|
|
16
|
+
private shouldLog;
|
|
17
|
+
private formatMessage;
|
|
18
|
+
debug(message: string, data?: any): void;
|
|
19
|
+
info(message: string, data?: any): void;
|
|
20
|
+
warn(message: string, data?: any): void;
|
|
21
|
+
error(message: string, error?: Error | any): void;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Create a logger for a Studio component
|
|
25
|
+
*/
|
|
26
|
+
export declare function createStudioLogger(component: string): StudioLogger;
|
|
27
|
+
/**
|
|
28
|
+
* Global configuration for all Studio loggers
|
|
29
|
+
*/
|
|
30
|
+
export declare const StudioLoggerConfig: {
|
|
31
|
+
setLevel(level: LogLevel): void;
|
|
32
|
+
getLevel(): LogLevel;
|
|
33
|
+
disable(): void;
|
|
34
|
+
enable(): void;
|
|
35
|
+
};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Studio Logger - Browser-optimized logging
|
|
3
|
+
*
|
|
4
|
+
* Lightweight logger for Studio frontend components.
|
|
5
|
+
* Integrates with browser dev tools and provides structured output.
|
|
6
|
+
*/
|
|
7
|
+
const LOG_LEVELS = {
|
|
8
|
+
debug: 0,
|
|
9
|
+
info: 1,
|
|
10
|
+
warn: 2,
|
|
11
|
+
error: 3
|
|
12
|
+
};
|
|
13
|
+
// Default to warn level to reduce console noise
|
|
14
|
+
// Use cross-platform development detection that works in all environments
|
|
15
|
+
let GLOBAL_LOG_LEVEL = (typeof window !== 'undefined' && window.__TROKKY_DEV__ === true) ? 'warn' : 'error';
|
|
16
|
+
export class StudioLogger {
|
|
17
|
+
constructor(component, config = {}) {
|
|
18
|
+
this.config = {
|
|
19
|
+
level: GLOBAL_LOG_LEVEL,
|
|
20
|
+
enabled: true,
|
|
21
|
+
prefix: `[Studio:${component}]`,
|
|
22
|
+
...config
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
shouldLog(level) {
|
|
26
|
+
// Always use the current global log level instead of the instance level
|
|
27
|
+
return this.config.enabled && LOG_LEVELS[level] >= LOG_LEVELS[GLOBAL_LOG_LEVEL];
|
|
28
|
+
}
|
|
29
|
+
formatMessage(level, message) {
|
|
30
|
+
return `${this.config.prefix}[${level}] ${message}`;
|
|
31
|
+
}
|
|
32
|
+
debug(message, data) {
|
|
33
|
+
if (this.shouldLog('debug')) {
|
|
34
|
+
const formatted = this.formatMessage('debug', message);
|
|
35
|
+
if (data) {
|
|
36
|
+
console.debug(formatted, data);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
console.debug(formatted);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
info(message, data) {
|
|
44
|
+
if (this.shouldLog('info')) {
|
|
45
|
+
const formatted = this.formatMessage('info', message);
|
|
46
|
+
if (data) {
|
|
47
|
+
console.info(formatted, data);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
console.info(formatted);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
warn(message, data) {
|
|
55
|
+
if (this.shouldLog('warn')) {
|
|
56
|
+
const formatted = this.formatMessage('warn', message);
|
|
57
|
+
if (data) {
|
|
58
|
+
console.warn(formatted, data);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
console.warn(formatted);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
error(message, error) {
|
|
66
|
+
if (this.shouldLog('error')) {
|
|
67
|
+
const formatted = this.formatMessage('error', message);
|
|
68
|
+
if (error instanceof Error) {
|
|
69
|
+
console.error(formatted, error);
|
|
70
|
+
}
|
|
71
|
+
else if (error) {
|
|
72
|
+
console.error(formatted, error);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
console.error(formatted);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Create a logger for a Studio component
|
|
82
|
+
*/
|
|
83
|
+
export function createStudioLogger(component) {
|
|
84
|
+
return new StudioLogger(component);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Global configuration for all Studio loggers
|
|
88
|
+
*/
|
|
89
|
+
export const StudioLoggerConfig = {
|
|
90
|
+
setLevel(level) {
|
|
91
|
+
GLOBAL_LOG_LEVEL = level;
|
|
92
|
+
// Also store in window for persistence across page reloads in dev
|
|
93
|
+
if (typeof window !== 'undefined') {
|
|
94
|
+
window.__TROKKY_LOG_LEVEL__ = level;
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
getLevel() {
|
|
98
|
+
return GLOBAL_LOG_LEVEL;
|
|
99
|
+
},
|
|
100
|
+
disable() {
|
|
101
|
+
GLOBAL_LOG_LEVEL = 'error'; // Effectively disable by setting to highest level
|
|
102
|
+
},
|
|
103
|
+
enable() {
|
|
104
|
+
GLOBAL_LOG_LEVEL = (typeof window !== 'undefined' && window.__TROKKY_DEV__ === true) ? 'warn' : 'error';
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
// Check for persisted log level from previous session
|
|
108
|
+
if (typeof window !== 'undefined' && window.__TROKKY_LOG_LEVEL__) {
|
|
109
|
+
GLOBAL_LOG_LEVEL = window.__TROKKY_LOG_LEVEL__;
|
|
110
|
+
}
|
|
111
|
+
// Expose logger config to window for easy console control
|
|
112
|
+
if (typeof window !== 'undefined') {
|
|
113
|
+
window.TrokkyLogger = StudioLoggerConfig;
|
|
114
|
+
}
|