@kyro-cms/admin 0.2.4 → 0.3.0

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 (68) hide show
  1. package/README.md +46 -272
  2. package/package.json +37 -10
  3. package/src/blocks/examples/sample-block-2.tsx +27 -0
  4. package/src/blocks/examples/sample-block.tsx +26 -0
  5. package/src/blocks/index.ts +14 -0
  6. package/src/blocks/registry.ts +38 -0
  7. package/src/blocks/types.ts +23 -0
  8. package/src/components/Admin.tsx +1 -1
  9. package/src/components/ApiKeysManager.tsx +1 -1
  10. package/src/components/AuditLogsPage.tsx +1 -1
  11. package/src/components/AutoForm.tsx +2 -2
  12. package/src/components/BrandingHub.tsx +1 -1
  13. package/src/components/CreateView.tsx +1 -1
  14. package/src/components/DetailView.tsx +1 -1
  15. package/src/components/DeveloperCenter.tsx +1 -1
  16. package/src/components/EnhancedListView.tsx +1 -1
  17. package/src/components/ListView.tsx +1 -1
  18. package/src/components/LoginPage.tsx +1 -1
  19. package/src/components/MediaGallery.tsx +1 -1
  20. package/src/components/UserManagement.tsx +1 -1
  21. package/src/components/WebhookManager.tsx +2 -2
  22. package/src/components/fields/RelationshipBlockField.tsx +1 -1
  23. package/src/components/fields/RelationshipField.tsx +1 -1
  24. package/src/components/fields/UploadField.tsx +1 -6
  25. package/src/components/ui/CommandPalette.tsx +1 -1
  26. package/src/fields/examples/sample-field-2.tsx +30 -0
  27. package/src/fields/examples/sample-field.tsx +30 -0
  28. package/src/fields/index.ts +33 -0
  29. package/src/fields/registry.tsx +46 -0
  30. package/src/fields/types.ts +24 -0
  31. package/src/hooks/data.ts +116 -0
  32. package/src/hooks/examples/sample-hook-2.ts +13 -0
  33. package/src/hooks/examples/sample-hook.ts +12 -0
  34. package/src/hooks/index.ts +19 -0
  35. package/src/hooks/lifecycle.ts +81 -0
  36. package/src/hooks/types.ts +40 -0
  37. package/src/index.ts +78 -0
  38. package/src/integration.ts +52 -0
  39. package/src/pages/api/[collection]/[id]/publish.ts +2 -2
  40. package/src/pages/api/[collection]/[id]/unpublish.ts +2 -2
  41. package/src/pages/api/[collection]/[id]/versions.ts +1 -1
  42. package/src/pages/api/[collection]/[id].ts +2 -2
  43. package/src/pages/api/[collection]/index.ts +2 -2
  44. package/src/pages/api/collections.ts +1 -1
  45. package/src/pages/api/globals/[slug].ts +2 -2
  46. package/src/pages/api/graphql.ts +3 -3
  47. package/src/pages/api/media/folders.ts +1 -1
  48. package/src/pages/api/media/index.ts +1 -1
  49. package/src/pages/api/media/resize.ts +1 -1
  50. package/src/pages/api/slug-availability.ts +2 -2
  51. package/src/pages/api/storage-config.ts +1 -1
  52. package/src/pages/api/storage-status.ts +1 -1
  53. package/src/pages/api/upload.ts +1 -1
  54. package/src/plugins/examples/sample-plugin-2.ts +21 -0
  55. package/src/plugins/examples/sample-plugin.ts +21 -0
  56. package/src/plugins/index.ts +10 -0
  57. package/src/plugins/registry.ts +36 -0
  58. package/src/plugins/types.ts +22 -0
  59. package/src/styles/main.css +2 -41
  60. package/src/theme/ThemeProvider.tsx +238 -0
  61. package/src/theme/index.ts +20 -0
  62. package/src/theme/tokens.ts +222 -0
  63. package/src/components/Modal.tsx +0 -206
  64. package/src/components/index.ts +0 -29
  65. package/src/env.ts +0 -20
  66. package/src/lib/i18n.tsx +0 -353
  67. package/src/lib/validation.ts +0 -250
  68. package/src/pages/api/globals/[slug]/test.ts +0 -171
@@ -1,5 +1,5 @@
1
1
  import React, { useState, useEffect } from "react";
2
- import { apiGet, apiPatch } from "@kyro-cms/utils/lib/api";
2
+ import { apiGet, apiPatch } from "../lib/api";
3
3
  import {
4
4
  Users,
5
5
  UserPlus,
@@ -1,6 +1,6 @@
1
1
  import React, { useState, useEffect } from "react";
2
- import { apiGet, apiPost, apiPatch, apiDelete } from "@kyro-cms/utils/lib/api";
3
- import { formatDate } from "@kyro-cms/utils/lib/date-utils";
2
+ import { apiGet, apiPost, apiPatch, apiDelete } from "../lib/api";
3
+ import { formatDate } from "../lib/date-utils";
4
4
  import {
5
5
  Webhook,
6
6
  Plus,
@@ -1,6 +1,6 @@
1
1
  import React, { useState, useEffect } from "react";
2
2
  import { Search, Loader2, X } from "lucide-react";
3
- import { apiGet, buildSearchQuery } from "@kyro-cms/utils/lib/api";
3
+ import { apiGet, buildSearchQuery } from "../../lib/api";
4
4
 
5
5
  interface RelationshipBlockFieldProps {
6
6
  relationTo?: string;
@@ -1,6 +1,6 @@
1
1
  import { useEffect, useState, useRef } from "react";
2
2
  import { Search, X, ChevronDown, Loader2 } from "lucide-react";
3
- import { apiGet, buildSearchQuery } from "@kyro-cms/utils/lib/api";
3
+ import { apiGet, buildSearchQuery } from "../../lib/api";
4
4
 
5
5
  interface RelationshipFieldProps {
6
6
  field: {
@@ -1,12 +1,7 @@
1
1
  import React, { useState, useEffect, useRef, useMemo } from "react";
2
2
  import { createPortal } from "react-dom";
3
3
  import { Image, Film, FileText, Music, File, X, Loader2 } from "lucide-react";
4
- import {
5
- apiGet,
6
- withCacheBust,
7
- apiPost,
8
- apiUpload,
9
- } from "@kyro-cms/utils/lib/api";
4
+ import { apiGet, withCacheBust, apiPost, apiUpload } from "../../lib/api";
10
5
 
11
6
  interface UploadFieldProps {
12
7
  field: any;
@@ -1,5 +1,5 @@
1
1
  import React, { useState, useEffect, useRef, useCallback } from "react";
2
- import { apiGet } from "@kyro-cms/utils/lib/api";
2
+ import { apiGet } from "../../lib/api";
3
3
  import {
4
4
  Search,
5
5
  FileText,
@@ -0,0 +1,30 @@
1
+ import React from "react";
2
+ import type { FieldEditorProps } from "../types.ts";
3
+ import { registerField } from "../registry.tsx";
4
+ import type { KyroField } from "../types.ts";
5
+
6
+ const SampleTextareaEditor: React.FC<FieldEditorProps> = ({
7
+ name,
8
+ value,
9
+ onChange,
10
+ }) => {
11
+ return (
12
+ <textarea
13
+ name={name}
14
+ value={typeof value === "string" ? value : ""}
15
+ onChange={(e) => onChange?.(e.target.value)}
16
+ placeholder="Sample textarea"
17
+ />
18
+ );
19
+ };
20
+
21
+ const field: KyroField = {
22
+ id: "sample-textarea",
23
+ type: "textarea",
24
+ label: "Sample Textarea",
25
+ editor: SampleTextareaEditor,
26
+ };
27
+
28
+ registerField(field);
29
+ export default field;
30
+ export { field as sampleField2 };
@@ -0,0 +1,30 @@
1
+ import React from "react";
2
+ import type { FieldEditorProps } from "../types.ts";
3
+ import { registerField } from "../registry.tsx";
4
+ import type { KyroField } from "../types.ts";
5
+
6
+ const SampleTextEditor: React.FC<FieldEditorProps> = ({
7
+ name,
8
+ value,
9
+ onChange,
10
+ }) => {
11
+ return (
12
+ <input
13
+ name={name}
14
+ value={typeof value === "string" ? value : ""}
15
+ onChange={(e) => onChange?.(e.target.value)}
16
+ placeholder="Sample text"
17
+ />
18
+ );
19
+ };
20
+
21
+ const field: KyroField = {
22
+ id: "sample-text",
23
+ type: "text",
24
+ label: "Sample Text",
25
+ editor: SampleTextEditor,
26
+ };
27
+
28
+ registerField(field);
29
+ export default field;
30
+ export { field as sampleField };
@@ -0,0 +1,33 @@
1
+ export {
2
+ registerField,
3
+ unregisterField,
4
+ getField,
5
+ getFields,
6
+ getFieldByType,
7
+ useFieldRenderer,
8
+ } from "./registry.tsx";
9
+ export type { KyroField, FieldEditorProps } from "./types.ts";
10
+ export { default as sampleField } from "./examples/sample-field";
11
+ export { default as sampleField2 } from "./examples/sample-field-2.tsx";
12
+
13
+ // Re-export core field types for type-safe field registration
14
+ export type {
15
+ FieldType,
16
+ Field,
17
+ TextField,
18
+ NumberField,
19
+ CheckboxField,
20
+ DateField,
21
+ SelectField,
22
+ TextareaField,
23
+ RichTextField,
24
+ CodeField,
25
+ JSONField,
26
+ ImageField,
27
+ UploadField,
28
+ RelationshipField,
29
+ BlocksField,
30
+ ArrayField,
31
+ GroupField,
32
+ ALL_FIELD_TYPES,
33
+ } from "@kyro-cms/core";
@@ -0,0 +1,46 @@
1
+ import type { ReactNode, ComponentType } from "react";
2
+ import type { FieldType } from "@kyro-cms/core";
3
+ import type { KyroField, FieldEditorProps } from "./types.ts";
4
+
5
+ const fields: Map<string, KyroField> = new Map();
6
+
7
+ export function registerField(field: KyroField): void {
8
+ if (!field.id || typeof field.id !== "string") {
9
+ throw new Error("Field must have a valid id");
10
+ }
11
+ if (!field.type || typeof field.type !== "string") {
12
+ throw new Error("Field must have a valid type");
13
+ }
14
+ fields.set(field.id, field);
15
+ }
16
+
17
+ export function unregisterField(id: string): void {
18
+ fields.delete(id);
19
+ }
20
+
21
+ export function getField(id: string): KyroField | undefined {
22
+ return fields.get(id);
23
+ }
24
+
25
+ export function getFields(): KyroField[] {
26
+ return Array.from(fields.values());
27
+ }
28
+
29
+ export function getFieldByType(type: FieldType): KyroField | undefined {
30
+ return Array.from(fields.values()).find((f) => f.type === type);
31
+ }
32
+
33
+ export function useFieldRenderer(
34
+ fieldId: string,
35
+ props: Omit<FieldEditorProps, "onChange"> & {
36
+ onChange?: (value: unknown) => void;
37
+ },
38
+ ): ReactNode | null {
39
+ const field = fields.get(fieldId);
40
+ if (!field) {
41
+ console.warn(`Field "${fieldId}" not found in registry`);
42
+ return null;
43
+ }
44
+ const FieldEditor: ComponentType<FieldEditorProps> = field.editor;
45
+ return <FieldEditor {...(props as FieldEditorProps)} />;
46
+ }
@@ -0,0 +1,24 @@
1
+ import type { ReactNode } from "react";
2
+ import type { FieldType } from "@kyro-cms/core";
3
+
4
+ export interface FieldEditorProps {
5
+ name: string;
6
+ label: string;
7
+ value: unknown;
8
+ onChange: (value: unknown) => void;
9
+ onBlur?: () => void;
10
+ error?: string;
11
+ required?: boolean;
12
+ disabled?: boolean;
13
+ schema?: Record<string, unknown>;
14
+ }
15
+
16
+ export interface KyroField {
17
+ id: string;
18
+ type: FieldType;
19
+ label?: string;
20
+ editor: React.ComponentType<FieldEditorProps>;
21
+ schema?: Record<string, unknown>;
22
+ defaultValue?: unknown;
23
+ validate?: (value: unknown) => string | null;
24
+ }
@@ -0,0 +1,116 @@
1
+ import { useState, useEffect, useCallback } from "react";
2
+
3
+ export interface QueryOptions {
4
+ page?: number;
5
+ limit?: number;
6
+ filter?: Record<string, unknown>;
7
+ sort?: string;
8
+ order?: "asc" | "desc";
9
+ }
10
+
11
+ export interface QueryResult<T> {
12
+ data: T | null;
13
+ loading: boolean;
14
+ error: string | null;
15
+ refetch: () => Promise<void>;
16
+ }
17
+
18
+ export interface MutationResult {
19
+ mutate: (
20
+ data?: Record<string, unknown>,
21
+ ) => Promise<Record<string, unknown> | null>;
22
+ loading: boolean;
23
+ error: string | null;
24
+ }
25
+
26
+ function getApiUrl(path: string): string {
27
+ const base = typeof window !== "undefined" ? window.location.origin : "";
28
+ return `${base}${path}`;
29
+ }
30
+
31
+ function getToken(): string | null {
32
+ if (typeof window === "undefined") return null;
33
+ return localStorage.getItem("kyro_token");
34
+ }
35
+
36
+ export function useKyroQuery<T = Record<string, unknown>>(
37
+ slug: string,
38
+ options: QueryOptions = {},
39
+ ): QueryResult<T> {
40
+ const [data, setData] = useState<T | null>(null);
41
+ const [loading, setLoading] = useState(true);
42
+ const [error, setError] = useState<string | null>(null);
43
+
44
+ const fetchData = useCallback(async () => {
45
+ setLoading(true);
46
+ setError(null);
47
+ try {
48
+ const params = new URLSearchParams();
49
+ if (options.page) params.set("page", String(options.page));
50
+ if (options.limit) params.set("limit", String(options.limit));
51
+ if (options.sort) params.set("sort", options.sort);
52
+ if (options.order) params.set("order", options.order);
53
+
54
+ const url = `${getApiUrl(`/api/${slug}`)}?${params.toString()}`;
55
+ const token = getToken();
56
+ const res = await fetch(url, {
57
+ headers: token ? { Authorization: `Bearer ${token}` } : {},
58
+ });
59
+
60
+ if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`);
61
+ const json = await res.json();
62
+ setData(json.data ?? json);
63
+ } catch (err) {
64
+ setError(err instanceof Error ? err.message : String(err));
65
+ } finally {
66
+ setLoading(false);
67
+ }
68
+ }, [slug, options.page, options.limit, options.sort, options.order]);
69
+
70
+ useEffect(() => {
71
+ fetchData();
72
+ }, [fetchData]);
73
+
74
+ return { data, loading, error, refetch: fetchData };
75
+ }
76
+
77
+ export function useKyroMutation(
78
+ slug: string,
79
+ method: "POST" | "PATCH" | "DELETE" = "POST",
80
+ ): MutationResult {
81
+ const [loading, setLoading] = useState(false);
82
+ const [error, setError] = useState<string | null>(null);
83
+
84
+ const mutate = useCallback(
85
+ async (
86
+ data?: Record<string, unknown>,
87
+ ): Promise<Record<string, unknown> | null> => {
88
+ setLoading(true);
89
+ setError(null);
90
+ try {
91
+ const url = getApiUrl(`/api/${slug}`);
92
+ const token = getToken();
93
+ const res = await fetch(url, {
94
+ method,
95
+ headers: {
96
+ "Content-Type": "application/json",
97
+ ...(token ? { Authorization: `Bearer ${token}` } : {}),
98
+ },
99
+ body: data ? JSON.stringify(data) : undefined,
100
+ });
101
+
102
+ if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`);
103
+ const json = await res.json();
104
+ return json.data ?? json;
105
+ } catch (err) {
106
+ setError(err instanceof Error ? err.message : String(err));
107
+ return null;
108
+ } finally {
109
+ setLoading(false);
110
+ }
111
+ },
112
+ [slug, method],
113
+ );
114
+
115
+ return { mutate, loading, error };
116
+ }
@@ -0,0 +1,13 @@
1
+ import { onAdminReady } from "../lifecycle.ts";
2
+ import type { AdminContext, HookResult } from "../types.ts";
3
+
4
+ // Second MVP hook showcasing a different approach (logs to console)
5
+ export default function registerSampleHook2() {
6
+ onAdminReady((ctx: AdminContext) => {
7
+ // Minimal side-effect demonstration
8
+ void ctx;
9
+ // eslint-disable-next-line no-console
10
+ console.log("[KyroAdmin] sample-hook-2 ready");
11
+ return { success: true } as HookResult;
12
+ });
13
+ }
@@ -0,0 +1,12 @@
1
+ import { onAdminReady } from "../lifecycle.ts";
2
+ import type { AdminContext, HookResult } from "../types.ts";
3
+
4
+ // A tiny MVP hook that demonstrates registration via the public API
5
+ // and emits a trivial HookResult when the admin is ready.
6
+ export default function registerSampleHook() {
7
+ onAdminReady((ctx: AdminContext) => {
8
+ // Minimal side-effect; in real plugins, you could register editors or analytics
9
+ void ctx;
10
+ return { success: true } as HookResult;
11
+ });
12
+ }
@@ -0,0 +1,19 @@
1
+ export {
2
+ onAdminReady,
3
+ beforeDeploy,
4
+ afterDeploy,
5
+ emitAdminReady,
6
+ emitBeforeDeploy,
7
+ emitAfterDeploy,
8
+ } from "./lifecycle.ts";
9
+ export { useKyroQuery, useKyroMutation } from "./data.ts";
10
+ export { default as sampleHook } from "./examples/sample-hook.ts";
11
+ export { default as sampleHook2 } from "./examples/sample-hook-2.ts";
12
+ export type {
13
+ AdminContext,
14
+ HookResult,
15
+ LifecycleHook,
16
+ AuthUser,
17
+ TenantInfo,
18
+ } from "./types.ts";
19
+ export type { QueryOptions, QueryResult, MutationResult } from "./data.ts";
@@ -0,0 +1,81 @@
1
+ import type { AdminContext, HookResult, LifecycleHook } from "./types.ts";
2
+
3
+ type HookRegistry = {
4
+ ready: LifecycleHook[];
5
+ beforeDeploy: LifecycleHook[];
6
+ afterDeploy: ((ctx: AdminContext, result: HookResult) => void)[];
7
+ };
8
+
9
+ const registry: HookRegistry = {
10
+ ready: [],
11
+ beforeDeploy: [],
12
+ afterDeploy: [],
13
+ };
14
+
15
+ export function onAdminReady(hook: LifecycleHook) {
16
+ registry.ready.push(hook);
17
+ return () => {
18
+ registry.ready = registry.ready.filter((h) => h !== hook);
19
+ };
20
+ }
21
+
22
+ export function beforeDeploy(hook: LifecycleHook) {
23
+ registry.beforeDeploy.push(hook);
24
+ return () => {
25
+ registry.beforeDeploy = registry.beforeDeploy.filter((h) => h !== hook);
26
+ };
27
+ }
28
+
29
+ export function afterDeploy(
30
+ hook: (ctx: AdminContext, result: HookResult) => void,
31
+ ) {
32
+ registry.afterDeploy.push(hook);
33
+ return () => {
34
+ registry.afterDeploy = registry.afterDeploy.filter((h) => h !== hook);
35
+ };
36
+ }
37
+
38
+ export async function emitAdminReady(ctx: AdminContext): Promise<HookResult[]> {
39
+ const results: HookResult[] = [];
40
+ for (const hook of registry.ready) {
41
+ try {
42
+ const result = await hook(ctx);
43
+ if (result && typeof result === "object" && "success" in result) {
44
+ results.push(result);
45
+ }
46
+ } catch (error) {
47
+ results.push({ success: false, error: String(error) });
48
+ }
49
+ }
50
+ return results;
51
+ }
52
+
53
+ export async function emitBeforeDeploy(
54
+ ctx: AdminContext,
55
+ ): Promise<HookResult[]> {
56
+ const results: HookResult[] = [];
57
+ for (const hook of registry.beforeDeploy) {
58
+ try {
59
+ const result = await hook(ctx);
60
+ if (result && typeof result === "object" && "success" in result) {
61
+ results.push(result);
62
+ }
63
+ } catch (error) {
64
+ results.push({ success: false, error: String(error) });
65
+ }
66
+ }
67
+ return results;
68
+ }
69
+
70
+ export async function emitAfterDeploy(
71
+ ctx: AdminContext,
72
+ result: HookResult,
73
+ ): Promise<void> {
74
+ for (const hook of registry.afterDeploy) {
75
+ try {
76
+ await hook(ctx, result);
77
+ } catch {
78
+ // Silently ignore afterDeploy hook errors
79
+ }
80
+ }
81
+ }
@@ -0,0 +1,40 @@
1
+ export interface AuthUser {
2
+ id: string;
3
+ email: string;
4
+ role: string;
5
+ tenantId?: string;
6
+ }
7
+
8
+ export interface TenantInfo {
9
+ id: string;
10
+ slug: string;
11
+ name: string;
12
+ }
13
+
14
+ export interface AdminContext {
15
+ user: AuthUser | null;
16
+ tenant: TenantInfo | null;
17
+ config: Record<string, unknown>;
18
+ api: {
19
+ baseUrl: string;
20
+ token: string | null;
21
+ };
22
+ }
23
+
24
+ export interface HookResult {
25
+ success: boolean;
26
+ error?: string;
27
+ }
28
+
29
+ export type LifecycleHook = (
30
+ ctx: AdminContext,
31
+ ) => void | HookResult | Promise<void | HookResult>;
32
+
33
+ export type DeployHook = (
34
+ ctx: AdminContext,
35
+ ) => HookResult | Promise<HookResult>;
36
+
37
+ export type DeployResultHook = (
38
+ ctx: AdminContext,
39
+ result: HookResult,
40
+ ) => void | Promise<void>;
package/src/index.ts CHANGED
@@ -32,3 +32,81 @@ export {
32
32
  } from "./components/ui/Dropdown";
33
33
  export { Modal, ConfirmModal } from "./components/ui/Modal";
34
34
  export { SlidePanel } from "./components/ui/SlidePanel";
35
+
36
+ // Extensibility: Theme
37
+ export {
38
+ ThemeProvider as ExtThemeProvider,
39
+ LightThemeProvider as ExtLightThemeProvider,
40
+ DarkThemeProvider as ExtDarkThemeProvider,
41
+ useTheme as useExtTheme,
42
+ LIGHT_THEME,
43
+ DARK_THEME,
44
+ mergeThemes,
45
+ type ThemeMode as ExtThemeMode,
46
+ type KyroTheme,
47
+ type ThemeColors,
48
+ type ThemeTypography,
49
+ type ThemeSpacing,
50
+ type ThemeRadius,
51
+ type ThemeShadows,
52
+ type BlockThemeOverrides,
53
+ type FieldThemeOverrides,
54
+ } from "./theme/index";
55
+
56
+ // Extensibility: Hooks
57
+ export {
58
+ onAdminReady,
59
+ beforeDeploy,
60
+ afterDeploy,
61
+ emitAdminReady,
62
+ emitBeforeDeploy,
63
+ emitAfterDeploy,
64
+ useKyroQuery,
65
+ useKyroMutation,
66
+ type AdminContext,
67
+ type HookResult,
68
+ type LifecycleHook,
69
+ type AuthUser,
70
+ type TenantInfo,
71
+ type QueryOptions,
72
+ type QueryResult,
73
+ type MutationResult,
74
+ } from "./hooks/index.js";
75
+
76
+ // Extensibility: Plugins
77
+ export {
78
+ registerPlugin,
79
+ unregisterPlugin,
80
+ getPlugin,
81
+ getPlugins,
82
+ getPluginsWithHook,
83
+ type KyroPlugin,
84
+ } from "./plugins/index.js";
85
+
86
+ // Extensibility: Blocks
87
+ export {
88
+ registerBlock,
89
+ unregisterBlock,
90
+ getBlock,
91
+ getBlocks,
92
+ getBlocksByCategory,
93
+ useBlockRenderer,
94
+ type KyroBlock,
95
+ type BlockRenderProps,
96
+ } from "./blocks/index";
97
+
98
+ // Extensibility: Fields
99
+ export {
100
+ registerField,
101
+ unregisterField,
102
+ getField,
103
+ getFields,
104
+ getFieldByType,
105
+ useFieldRenderer,
106
+ type KyroField,
107
+ type FieldEditorProps,
108
+ } from "./fields/index";
109
+
110
+ // Astro Integration
111
+ export { kyroAdmin } from "./integration";
112
+ export type { KyroAdminOptions } from "./integration";
@@ -0,0 +1,52 @@
1
+ import type { AstroIntegration } from "astro";
2
+ import path from "path";
3
+ import fs from "fs";
4
+
5
+ export interface KyroAdminOptions {
6
+ basePath?: string;
7
+ configPath?: string;
8
+ disableAuth?: boolean;
9
+ }
10
+
11
+ export function kyroAdmin(options: KyroAdminOptions = {}): AstroIntegration {
12
+ const {
13
+ basePath = "/admin",
14
+ configPath = "kyro.config.ts",
15
+ disableAuth = false,
16
+ } = options;
17
+
18
+ return {
19
+ name: "@kyro-cms/admin",
20
+ hooks: {
21
+ "astro:config:setup": ({ config, updateConfig, logger }) => {
22
+ logger.info(`Kyro Admin mounted at ${basePath}`);
23
+
24
+ const resolvedConfig = path.resolve(config.root.pathname, configPath);
25
+ if (fs.existsSync(resolvedConfig)) {
26
+ logger.info(`Loaded config from ${configPath}`);
27
+ } else {
28
+ logger.warn(
29
+ `Config file not found at ${configPath}. Using defaults.`,
30
+ );
31
+ }
32
+
33
+ updateConfig({
34
+ vite: {
35
+ resolve: {
36
+ alias: {
37
+ "kyro:config": resolvedConfig,
38
+ },
39
+ },
40
+ define: {
41
+ __KYRO_ADMIN_BASE_PATH__: JSON.stringify(basePath),
42
+ __KYRO_ADMIN_AUTH_DISABLED__: JSON.stringify(disableAuth),
43
+ },
44
+ },
45
+ });
46
+ },
47
+ "astro:build:done": ({ logger }) => {
48
+ logger.info("Kyro Admin build complete");
49
+ },
50
+ },
51
+ };
52
+ }
@@ -1,6 +1,6 @@
1
1
  import type { APIRoute } from "astro";
2
- import { dataStore } from "../../../../../lib/dataStore";
3
- import { collections } from "../../../../../lib/config";
2
+ import { dataStore } from "../../../../lib/dataStore";
3
+ import { collections } from "../../../../lib/config";
4
4
 
5
5
  dataStore.initialize(collections);
6
6
 
@@ -1,6 +1,6 @@
1
1
  import type { APIRoute } from "astro";
2
- import { dataStore } from "../../../../../lib/dataStore";
3
- import { collections } from "../../../../../lib/config";
2
+ import { dataStore } from "../../../../lib/dataStore";
3
+ import { collections } from "../../../../lib/config";
4
4
 
5
5
  dataStore.initialize(collections);
6
6
 
@@ -1,5 +1,5 @@
1
1
  import type { APIRoute } from "astro";
2
- import { dataStore } from "../../../../../lib/dataStore";
2
+ import { dataStore } from "../../../../lib/dataStore";
3
3
 
4
4
  export const GET: APIRoute = async ({ params, url }) => {
5
5
  const { collection, id } = params;
@@ -1,6 +1,6 @@
1
1
  import type { APIRoute } from "astro";
2
- import { dataStore } from "../../../../lib/dataStore";
3
- import { collections } from "../../../../lib/config";
2
+ import { dataStore } from "../../../lib/dataStore";
3
+ import { collections } from "../../../lib/config";
4
4
  import { getAuthAdapter } from "../../../lib/db";
5
5
 
6
6
  dataStore.initialize(collections);