@syncular/console 0.0.4-25 → 0.0.4-26

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.
Files changed (47) hide show
  1. package/dist/App.d.ts.map +1 -1
  2. package/dist/App.js +2 -1
  3. package/dist/App.js.map +1 -1
  4. package/dist/browser-main.js +1 -1
  5. package/dist/browser-main.js.map +1 -1
  6. package/dist/index.d.ts +1 -0
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +1 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/layout.d.ts.map +1 -1
  11. package/dist/layout.js +2 -1
  12. package/dist/layout.js.map +1 -1
  13. package/dist/mount.d.ts.map +1 -1
  14. package/dist/mount.js +4 -1
  15. package/dist/mount.js.map +1 -1
  16. package/dist/pages/Ops.js +2 -2
  17. package/dist/pages/Ops.js.map +1 -1
  18. package/dist/server.d.ts +3 -0
  19. package/dist/server.d.ts.map +1 -0
  20. package/dist/server.js +3 -0
  21. package/dist/server.js.map +1 -0
  22. package/dist/static-server.d.ts +18 -0
  23. package/dist/static-server.d.ts.map +1 -0
  24. package/dist/static-server.js +137 -0
  25. package/dist/static-server.js.map +1 -0
  26. package/dist/styles.css +1 -0
  27. package/dist/theme-scope.d.ts +3 -0
  28. package/dist/theme-scope.d.ts.map +1 -0
  29. package/dist/theme-scope.js +5 -0
  30. package/dist/theme-scope.js.map +1 -0
  31. package/package.json +18 -7
  32. package/src/App.tsx +6 -1
  33. package/src/browser-main.tsx +1 -1
  34. package/src/index.ts +1 -0
  35. package/src/layout.tsx +4 -1
  36. package/src/mount.tsx +5 -1
  37. package/src/pages/Ops.tsx +4 -2
  38. package/src/server.ts +2 -0
  39. package/src/static-server.ts +219 -0
  40. package/src/styles/globals.css +5 -1
  41. package/src/theme-scope.ts +5 -0
  42. package/web-dist/assets/index-CTkQp6YC.js +86 -0
  43. package/web-dist/assets/index-j_U2SoXa.css +1 -0
  44. package/web-dist/console.css +1 -0
  45. package/web-dist/index.html +4 -4
  46. package/web-dist/chunk-7ayekhzx.css +0 -1
  47. package/web-dist/chunk-myppbvt5.js +0 -90
package/src/mount.tsx CHANGED
@@ -1,6 +1,7 @@
1
1
  import { StrictMode } from 'react';
2
2
  import { createRoot, type Root } from 'react-dom/client';
3
3
  import { App, type SyncularConsoleProps } from './App';
4
+ import { applyConsoleThemeScope } from './theme-scope';
4
5
 
5
6
  interface MountSyncularConsoleOptions {
6
7
  strictMode?: boolean;
@@ -27,7 +28,10 @@ export function mountSyncularConsoleApp(
27
28
  containerOrSelector: Element | string,
28
29
  options: MountSyncularConsoleOptions = {}
29
30
  ): Root {
30
- const root = createRoot(resolveContainer(containerOrSelector));
31
+ const container = resolveContainer(containerOrSelector);
32
+ applyConsoleThemeScope(container);
33
+
34
+ const root = createRoot(container);
31
35
  const app = (
32
36
  <App basePath={options.basePath} defaultConfig={options.defaultConfig} />
33
37
  );
package/src/pages/Ops.tsx CHANGED
@@ -684,8 +684,10 @@ function OperationsAuditView({ partitionId }: { partitionId?: string }) {
684
684
  </TableRow>
685
685
  </TableHeader>
686
686
  <TableBody>
687
- {(data?.items ?? []).map((event) => (
688
- <TableRow key={event.operationId}>
687
+ {(data?.items ?? []).map((event, index) => (
688
+ <TableRow
689
+ key={`${event.operationId}:${event.createdAt}:${index}`}
690
+ >
689
691
  <TableCell className="whitespace-nowrap text-xs text-neutral-400">
690
692
  {formatDateTime(event.createdAt)}
691
693
  </TableCell>
package/src/server.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './runtime-config';
2
+ export * from './static-server';
@@ -0,0 +1,219 @@
1
+ import { existsSync, readFileSync, statSync } from 'node:fs';
2
+ import { readFile } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import {
6
+ CONSOLE_BASEPATH_META,
7
+ CONSOLE_SERVER_URL_META,
8
+ CONSOLE_TOKEN_META,
9
+ normalizeBasePath,
10
+ } from './runtime-config';
11
+
12
+ const DEFAULT_MOUNT_PATH = '/console';
13
+ const DEFAULT_INDEX_CACHE_CONTROL = 'no-store';
14
+ const DEFAULT_ASSET_CACHE_CONTROL = 'public, max-age=31536000, immutable';
15
+
16
+ const CONTENT_TYPES: Record<string, string> = {
17
+ '.css': 'text/css; charset=utf-8',
18
+ '.html': 'text/html; charset=utf-8',
19
+ '.ico': 'image/x-icon',
20
+ '.js': 'text/javascript; charset=utf-8',
21
+ '.json': 'application/json; charset=utf-8',
22
+ '.manifest': 'application/manifest+json; charset=utf-8',
23
+ '.png': 'image/png',
24
+ '.svg': 'image/svg+xml',
25
+ '.txt': 'text/plain; charset=utf-8',
26
+ '.webmanifest': 'application/manifest+json; charset=utf-8',
27
+ };
28
+
29
+ export interface ConsoleUiPrefill {
30
+ basePath?: string;
31
+ serverUrl?: string;
32
+ token?: string;
33
+ }
34
+
35
+ export interface CreateConsoleStaticResponderOptions {
36
+ mountPath?: string;
37
+ staticDir?: string;
38
+ defaultPrefill?: ConsoleUiPrefill;
39
+ indexCacheControl?: string;
40
+ assetCacheControl?: string;
41
+ }
42
+
43
+ export interface ServeConsoleStaticRequestOptions {
44
+ prefill?: ConsoleUiPrefill;
45
+ }
46
+
47
+ export type ConsoleStaticResponder = (
48
+ request: Request,
49
+ options?: ServeConsoleStaticRequestOptions
50
+ ) => Promise<Response | null>;
51
+
52
+ function normalizeMountPath(mountPath: string | undefined): string {
53
+ const value = mountPath?.trim() ?? '';
54
+ if (!value || value === '/') return '/';
55
+ const withLeadingSlash = value.startsWith('/') ? value : `/${value}`;
56
+ return withLeadingSlash.replace(/\/+$/g, '') || '/';
57
+ }
58
+
59
+ function normalizeRequestPath(pathname: string): string {
60
+ const decodedPathname = decodeURIComponent(pathname);
61
+ return decodedPathname === '' ? '/' : decodedPathname;
62
+ }
63
+
64
+ function matchesMountPath(pathname: string, mountPath: string): boolean {
65
+ if (mountPath === '/') return pathname.startsWith('/');
66
+ return pathname === mountPath || pathname.startsWith(`${mountPath}/`);
67
+ }
68
+
69
+ function relativePathForMount(pathname: string, mountPath: string): string {
70
+ if (mountPath === '/') return pathname;
71
+ const withoutPrefix = pathname.slice(mountPath.length);
72
+ return withoutPrefix === '' ? '/' : withoutPrefix;
73
+ }
74
+
75
+ function escapeHtmlAttribute(value: string): string {
76
+ return value
77
+ .replaceAll('&', '&amp;')
78
+ .replaceAll('"', '&quot;')
79
+ .replaceAll('<', '&lt;')
80
+ .replaceAll('>', '&gt;');
81
+ }
82
+
83
+ function withMetaTag(html: string, name: string, value: string): string {
84
+ const escapedValue = escapeHtmlAttribute(value);
85
+ const pattern = new RegExp(`<meta name="${name}" content="[^"]*"\\s*/?>`);
86
+ if (pattern.test(html)) {
87
+ return html.replace(
88
+ pattern,
89
+ `<meta name="${name}" content="${escapedValue}" />`
90
+ );
91
+ }
92
+
93
+ return html.replace(
94
+ '</head>',
95
+ ` <meta name="${name}" content="${escapedValue}" />\n </head>`
96
+ );
97
+ }
98
+
99
+ function isWithinDirectory(baseDir: string, targetPath: string): boolean {
100
+ return (
101
+ targetPath === baseDir || targetPath.startsWith(`${baseDir}${path.sep}`)
102
+ );
103
+ }
104
+
105
+ function contentTypeFor(pathname: string): string {
106
+ const extension = path.extname(pathname).toLowerCase();
107
+ return CONTENT_TYPES[extension] ?? 'application/octet-stream';
108
+ }
109
+
110
+ function resolveConsoleStaticDir(): string {
111
+ const staticDirFromModule = fileURLToPath(
112
+ new URL('../web-dist', import.meta.url)
113
+ );
114
+ return path.resolve(staticDirFromModule);
115
+ }
116
+
117
+ function renderIndexHtml(args: {
118
+ template: string;
119
+ mountPath: string;
120
+ prefill?: ConsoleUiPrefill;
121
+ }): string {
122
+ const resolvedBasePath = normalizeBasePath(
123
+ args.prefill?.basePath ?? args.mountPath
124
+ );
125
+ const resolvedServerUrl = args.prefill?.serverUrl ?? '';
126
+ const resolvedToken = args.prefill?.token ?? '';
127
+
128
+ return withMetaTag(
129
+ withMetaTag(
130
+ withMetaTag(args.template, CONSOLE_BASEPATH_META, resolvedBasePath),
131
+ CONSOLE_SERVER_URL_META,
132
+ resolvedServerUrl
133
+ ),
134
+ CONSOLE_TOKEN_META,
135
+ resolvedToken
136
+ );
137
+ }
138
+
139
+ export function createConsoleStaticResponder(
140
+ options: CreateConsoleStaticResponderOptions = {}
141
+ ): ConsoleStaticResponder {
142
+ const mountPath = normalizeMountPath(options.mountPath ?? DEFAULT_MOUNT_PATH);
143
+ const staticDir = path.resolve(
144
+ options.staticDir ?? resolveConsoleStaticDir()
145
+ );
146
+ const indexPath = path.join(staticDir, 'index.html');
147
+
148
+ if (!existsSync(indexPath)) {
149
+ throw new Error(
150
+ `Console distributable missing: ${indexPath}. Build @syncular/console before serving static assets.`
151
+ );
152
+ }
153
+
154
+ const indexTemplate = readFileSync(indexPath, 'utf8');
155
+ const indexCacheControl =
156
+ options.indexCacheControl ?? DEFAULT_INDEX_CACHE_CONTROL;
157
+ const assetCacheControl =
158
+ options.assetCacheControl ?? DEFAULT_ASSET_CACHE_CONTROL;
159
+ const defaultPrefill = options.defaultPrefill;
160
+
161
+ return async (request, requestOptions = {}) => {
162
+ const method = request.method.toUpperCase();
163
+ if (method !== 'GET' && method !== 'HEAD') {
164
+ return null;
165
+ }
166
+
167
+ const url = new URL(request.url);
168
+ const pathname = normalizeRequestPath(url.pathname);
169
+ if (!matchesMountPath(pathname, mountPath)) {
170
+ return null;
171
+ }
172
+
173
+ const relativePath = relativePathForMount(pathname, mountPath);
174
+ const effectivePrefill = {
175
+ ...defaultPrefill,
176
+ ...requestOptions.prefill,
177
+ };
178
+ const sendIndex = () => {
179
+ const html = renderIndexHtml({
180
+ template: indexTemplate,
181
+ mountPath,
182
+ prefill: effectivePrefill,
183
+ });
184
+ return new Response(method === 'HEAD' ? undefined : html, {
185
+ status: 200,
186
+ headers: {
187
+ 'Content-Type': 'text/html; charset=utf-8',
188
+ 'Cache-Control': indexCacheControl,
189
+ },
190
+ });
191
+ };
192
+
193
+ if (
194
+ relativePath === '/' ||
195
+ relativePath === '/index.html' ||
196
+ path.extname(relativePath).length === 0
197
+ ) {
198
+ return sendIndex();
199
+ }
200
+
201
+ const candidatePath = path.resolve(staticDir, `.${relativePath}`);
202
+ if (!isWithinDirectory(staticDir, candidatePath)) {
203
+ return new Response('Forbidden', { status: 403 });
204
+ }
205
+
206
+ if (!existsSync(candidatePath) || !statSync(candidatePath).isFile()) {
207
+ return new Response('Not Found', { status: 404 });
208
+ }
209
+
210
+ const body = method === 'HEAD' ? undefined : await readFile(candidatePath);
211
+ return new Response(body, {
212
+ status: 200,
213
+ headers: {
214
+ 'Content-Type': contentTypeFor(candidatePath),
215
+ 'Cache-Control': assetCacheControl,
216
+ },
217
+ });
218
+ };
219
+ }
@@ -1 +1,5 @@
1
- @import "@syncular/ui/styles.css";
1
+ @import '@syncular/ui/styles.css';
2
+
3
+ :where(.syncular-console-root) {
4
+ color-scheme: dark;
5
+ }
@@ -0,0 +1,5 @@
1
+ export const SYNCULAR_CONSOLE_ROOT_CLASS = 'syncular-console-root';
2
+
3
+ export function applyConsoleThemeScope(container: Element): void {
4
+ container.classList.add(SYNCULAR_CONSOLE_ROOT_CLASS);
5
+ }