flarecms 0.1.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 (110) hide show
  1. package/README.md +73 -0
  2. package/dist/auth/index.js +40 -0
  3. package/dist/cli/commands.js +389 -0
  4. package/dist/cli/index.js +403 -0
  5. package/dist/cli/mcp.js +209 -0
  6. package/dist/db/index.js +164 -0
  7. package/dist/index.js +17626 -0
  8. package/package.json +105 -0
  9. package/scripts/fix-api-paths.mjs +32 -0
  10. package/scripts/fix-imports.mjs +38 -0
  11. package/scripts/prefix-css.mjs +45 -0
  12. package/src/api/lib/cache.ts +45 -0
  13. package/src/api/lib/response.ts +40 -0
  14. package/src/api/middlewares/auth.ts +186 -0
  15. package/src/api/middlewares/cors.ts +10 -0
  16. package/src/api/middlewares/rbac.ts +85 -0
  17. package/src/api/routes/auth.ts +377 -0
  18. package/src/api/routes/collections.ts +205 -0
  19. package/src/api/routes/content.ts +175 -0
  20. package/src/api/routes/device.ts +160 -0
  21. package/src/api/routes/magic.ts +150 -0
  22. package/src/api/routes/mcp.ts +273 -0
  23. package/src/api/routes/oauth.ts +160 -0
  24. package/src/api/routes/settings.ts +43 -0
  25. package/src/api/routes/setup.ts +307 -0
  26. package/src/api/routes/tokens.ts +80 -0
  27. package/src/api/schemas/auth.ts +15 -0
  28. package/src/api/schemas/index.ts +51 -0
  29. package/src/api/schemas/tokens.ts +24 -0
  30. package/src/auth/index.ts +28 -0
  31. package/src/cli/commands.ts +217 -0
  32. package/src/cli/index.ts +21 -0
  33. package/src/cli/mcp.ts +210 -0
  34. package/src/cli/tests/cli.test.ts +40 -0
  35. package/src/cli/tests/create.test.ts +87 -0
  36. package/src/client/FlareAdminRouter.tsx +47 -0
  37. package/src/client/app.tsx +175 -0
  38. package/src/client/components/app-sidebar.tsx +227 -0
  39. package/src/client/components/collection-modal.tsx +215 -0
  40. package/src/client/components/content-list.tsx +247 -0
  41. package/src/client/components/dynamic-form.tsx +190 -0
  42. package/src/client/components/field-modal.tsx +221 -0
  43. package/src/client/components/settings/api-token-section.tsx +400 -0
  44. package/src/client/components/settings/general-section.tsx +224 -0
  45. package/src/client/components/settings/security-section.tsx +154 -0
  46. package/src/client/components/settings/seo-section.tsx +200 -0
  47. package/src/client/components/settings/signup-section.tsx +257 -0
  48. package/src/client/components/ui/accordion.tsx +78 -0
  49. package/src/client/components/ui/avatar.tsx +107 -0
  50. package/src/client/components/ui/badge.tsx +52 -0
  51. package/src/client/components/ui/button.tsx +60 -0
  52. package/src/client/components/ui/card.tsx +103 -0
  53. package/src/client/components/ui/checkbox.tsx +27 -0
  54. package/src/client/components/ui/collapsible.tsx +19 -0
  55. package/src/client/components/ui/dialog.tsx +162 -0
  56. package/src/client/components/ui/icon-picker.tsx +485 -0
  57. package/src/client/components/ui/icons-data.ts +8476 -0
  58. package/src/client/components/ui/input.tsx +20 -0
  59. package/src/client/components/ui/label.tsx +20 -0
  60. package/src/client/components/ui/popover.tsx +91 -0
  61. package/src/client/components/ui/select.tsx +204 -0
  62. package/src/client/components/ui/separator.tsx +23 -0
  63. package/src/client/components/ui/sheet.tsx +141 -0
  64. package/src/client/components/ui/sidebar.tsx +722 -0
  65. package/src/client/components/ui/skeleton.tsx +13 -0
  66. package/src/client/components/ui/sonner.tsx +47 -0
  67. package/src/client/components/ui/switch.tsx +30 -0
  68. package/src/client/components/ui/table.tsx +116 -0
  69. package/src/client/components/ui/tabs.tsx +80 -0
  70. package/src/client/components/ui/textarea.tsx +18 -0
  71. package/src/client/components/ui/tooltip.tsx +68 -0
  72. package/src/client/hooks/use-mobile.ts +19 -0
  73. package/src/client/index.css +149 -0
  74. package/src/client/index.ts +7 -0
  75. package/src/client/layouts/admin-layout.tsx +93 -0
  76. package/src/client/layouts/settings-layout.tsx +104 -0
  77. package/src/client/lib/api.ts +72 -0
  78. package/src/client/lib/utils.ts +6 -0
  79. package/src/client/main.tsx +10 -0
  80. package/src/client/pages/collection-detail.tsx +634 -0
  81. package/src/client/pages/collections.tsx +180 -0
  82. package/src/client/pages/dashboard.tsx +133 -0
  83. package/src/client/pages/device.tsx +66 -0
  84. package/src/client/pages/document-detail-page.tsx +139 -0
  85. package/src/client/pages/documents-page.tsx +103 -0
  86. package/src/client/pages/login.tsx +345 -0
  87. package/src/client/pages/settings.tsx +65 -0
  88. package/src/client/pages/setup.tsx +129 -0
  89. package/src/client/pages/signup.tsx +188 -0
  90. package/src/client/store/auth.ts +30 -0
  91. package/src/client/store/collections.ts +13 -0
  92. package/src/client/store/config.ts +12 -0
  93. package/src/client/store/fetcher.ts +30 -0
  94. package/src/client/store/router.ts +95 -0
  95. package/src/client/store/schema.ts +39 -0
  96. package/src/client/store/settings.ts +31 -0
  97. package/src/client/types.ts +34 -0
  98. package/src/db/dynamic.ts +70 -0
  99. package/src/db/index.ts +16 -0
  100. package/src/db/migrations/001_initial_schema.ts +57 -0
  101. package/src/db/migrations/002_auth_tables.ts +84 -0
  102. package/src/db/migrator.ts +61 -0
  103. package/src/db/schema.ts +142 -0
  104. package/src/index.ts +12 -0
  105. package/src/server/index.ts +66 -0
  106. package/src/types.ts +20 -0
  107. package/style.css.d.ts +8 -0
  108. package/tests/css.test.ts +21 -0
  109. package/tests/modular.test.ts +29 -0
  110. package/tsconfig.json +10 -0
@@ -0,0 +1,84 @@
1
+ import { Kysely, sql } from 'kysely';
2
+
3
+ export async function up(db: Kysely<any>): Promise<void> {
4
+ // Core Sessions
5
+ await db.schema
6
+ .createTable('fc_sessions')
7
+ .ifNotExists()
8
+ .addColumn('id', 'text', (col) => col.primaryKey())
9
+ .addColumn('user_id', 'text', (col) => col.notNull())
10
+ .addColumn('expires_at', 'text', (col) => col.notNull())
11
+ .execute();
12
+
13
+ // Biometric Passkeys
14
+ await db.schema
15
+ .createTable('fc_passkeys')
16
+ .ifNotExists()
17
+ .addColumn('id', 'text', (col) => col.primaryKey())
18
+ .addColumn('user_id', 'text', (col) => col.notNull())
19
+ .addColumn('name', 'text')
20
+ .addColumn('public_key', 'text', (col) => col.notNull())
21
+ .addColumn('counter', 'integer', (col) => col.notNull())
22
+ .addColumn('device_type', 'text', (col) => col.notNull())
23
+ .addColumn('backed_up', 'integer', (col) => col.notNull())
24
+ .addColumn('transports', 'text')
25
+ .addColumn('created_at', 'text', (col) => col.defaultTo(sql`CURRENT_TIMESTAMP`))
26
+ .addColumn('last_used_at', 'text')
27
+ .execute();
28
+
29
+ // API Token Management
30
+ await db.schema
31
+ .createTable('fc_api_tokens')
32
+ .ifNotExists()
33
+ .addColumn('id', 'text', (col) => col.primaryKey())
34
+ .addColumn('user_id', 'text', (col) => col.notNull())
35
+ .addColumn('name', 'text', (col) => col.notNull())
36
+ .addColumn('hash', 'text', (col) => col.notNull())
37
+ .addColumn('scopes', 'text', (col) => col.notNull())
38
+ .addColumn('expires_at', 'text')
39
+ .addColumn('last_used_at', 'text')
40
+ .addColumn('created_at', 'text', (col) => col.defaultTo(sql`CURRENT_TIMESTAMP`))
41
+ .execute();
42
+
43
+ // OAuth Providers Linkage
44
+ await db.schema
45
+ .createTable('fc_oauth_accounts')
46
+ .ifNotExists()
47
+ .addColumn('provider_id', 'text', (col) => col.notNull())
48
+ .addColumn('provider_user_id', 'text', (col) => col.notNull())
49
+ .addColumn('user_id', 'text', (col) => col.notNull())
50
+ .addPrimaryKeyConstraint('pk_oauth_accounts', ['provider_id', 'provider_user_id'])
51
+ .execute();
52
+
53
+ // Email/OTP Verification Tokens
54
+ await db.schema
55
+ .createTable('fc_verification_tokens')
56
+ .ifNotExists()
57
+ .addColumn('identifier', 'text', (col) => col.notNull())
58
+ .addColumn('token', 'text', (col) => col.notNull())
59
+ .addColumn('expires_at', 'text', (col) => col.notNull())
60
+ .addPrimaryKeyConstraint('pk_verification_tokens', ['identifier', 'token'])
61
+ .execute();
62
+
63
+ // Device Code Authorization (IoT/TV flows)
64
+ await db.schema
65
+ .createTable('fc_device_codes')
66
+ .ifNotExists()
67
+ .addColumn('device_code', 'text', (col) => col.primaryKey())
68
+ .addColumn('user_code', 'text', (col) => col.notNull().unique())
69
+ .addColumn('client_id', 'text', (col) => col.notNull())
70
+ .addColumn('user_id', 'text')
71
+ .addColumn('scopes', 'text', (col) => col.notNull())
72
+ .addColumn('expires_at', 'text', (col) => col.notNull())
73
+ .addColumn('created_at', 'text', (col) => col.defaultTo(sql`CURRENT_TIMESTAMP`))
74
+ .execute();
75
+ }
76
+
77
+ export async function down(db: Kysely<any>): Promise<void> {
78
+ await db.schema.dropTable('fc_sessions').ifExists().execute();
79
+ await db.schema.dropTable('fc_passkeys').ifExists().execute();
80
+ await db.schema.dropTable('fc_api_tokens').ifExists().execute();
81
+ await db.schema.dropTable('fc_oauth_accounts').ifExists().execute();
82
+ await db.schema.dropTable('fc_verification_tokens').ifExists().execute();
83
+ await db.schema.dropTable('fc_device_codes').ifExists().execute();
84
+ }
@@ -0,0 +1,61 @@
1
+ import { sql, type Migration } from 'kysely';
2
+ import type { FlareDb } from './index';
3
+
4
+ import * as initialSchema from './migrations/001_initial_schema';
5
+ import * as authTables from './migrations/002_auth_tables';
6
+
7
+ const STATIC_MIGRATIONS: Record<string, any> = {
8
+ '001_initial_schema': initialSchema,
9
+ '002_auth_tables': authTables,
10
+ };
11
+
12
+ /**
13
+ * Manual D1-safe migration runner.
14
+ * We avoid Kysely's built-in Migrator because it uses introspection (sqlite_master)
15
+ * which Cloudflare D1 restricts, causing SQLITE_AUTH errors.
16
+ */
17
+ export async function runMigrations(db: FlareDb) {
18
+ // 1. Ensure migrations table exists
19
+ await sql`
20
+ CREATE TABLE IF NOT EXISTS _fc_migrations (
21
+ name TEXT PRIMARY KEY,
22
+ executed_at TEXT DEFAULT CURRENT_TIMESTAMP
23
+ )
24
+ `.execute(db);
25
+
26
+ // 2. Get executed migrations
27
+ const executed = await db
28
+ .selectFrom('_fc_migrations' as any)
29
+ .select('name')
30
+ .execute();
31
+ const executedNames = new Set(executed.map((m: any) => m.name));
32
+
33
+ const results: { name: string; status: 'Success' | 'Skipped' | 'Error' }[] = [];
34
+
35
+ // 3. Run missing migrations in order
36
+ const migrationNames = Object.keys(STATIC_MIGRATIONS).sort();
37
+
38
+ for (const name of migrationNames) {
39
+ if (executedNames.has(name)) {
40
+ results.push({ name, status: 'Skipped' });
41
+ continue;
42
+ }
43
+
44
+ try {
45
+ const migration = STATIC_MIGRATIONS[name];
46
+ await migration.up(db);
47
+
48
+ await db
49
+ .insertInto('_fc_migrations' as any)
50
+ .values({ name })
51
+ .execute();
52
+
53
+ results.push({ name, status: 'Success' });
54
+ } catch (err) {
55
+ console.error(`Migration ${name} failed:`, err);
56
+ throw err;
57
+ }
58
+ }
59
+
60
+ return results;
61
+ }
@@ -0,0 +1,142 @@
1
+ import type { ColumnType, Generated, Selectable, Insertable, Updateable } from 'kysely';
2
+
3
+ export interface Database {
4
+ options: OptionsTable;
5
+ fc_collections: CollectionsTable;
6
+ fc_fields: FieldsTable;
7
+ fc_users: UsersTable;
8
+ fc_passkeys: PasskeysTable;
9
+ fc_sessions: SessionsTable;
10
+ fc_api_tokens: ApiTokensTable;
11
+ fc_oauth_accounts: OAuthAccountsTable;
12
+ fc_verification_tokens: VerificationTokensTable;
13
+ fc_device_codes: DeviceCodesTable;
14
+ // This helps typescript understand that we can have arbitrary string tables for dynamic content
15
+ [tableName: string]: any;
16
+ }
17
+
18
+ export interface OptionsTable {
19
+ name: string;
20
+ value: string;
21
+ }
22
+
23
+ export type Option = Selectable<OptionsTable>;
24
+ export type NewOption = Insertable<OptionsTable>;
25
+
26
+ export interface CollectionsTable {
27
+ id: string;
28
+ slug: string;
29
+ label: string;
30
+ label_singular: string | null;
31
+ description: string | null;
32
+ icon: string | null;
33
+ is_public: ColumnType<number, number | undefined, number>;
34
+ features: string | null; // JSON array of strings
35
+ url_pattern: string | null;
36
+ created_at: ColumnType<string, string | undefined, never>;
37
+ updated_at: ColumnType<string, string | undefined, string>;
38
+ }
39
+
40
+ export type Collection = Selectable<CollectionsTable>;
41
+ export type NewCollection = Insertable<CollectionsTable>;
42
+ export type CollectionUpdate = Updateable<CollectionsTable>;
43
+
44
+ export interface FieldsTable {
45
+ id: string;
46
+ collection_id: string;
47
+ label: string;
48
+ slug: string;
49
+ type: string;
50
+ required: ColumnType<number, number | undefined, number>; // sqlite boolean
51
+ created_at: ColumnType<string, string | undefined, never>;
52
+ }
53
+
54
+ export type Field = Selectable<FieldsTable>;
55
+ export type NewField = Insertable<FieldsTable>;
56
+
57
+ export interface UsersTable {
58
+ id: string;
59
+ email: string;
60
+ password: string | null; // Nullable if passkey only
61
+ role: string;
62
+ disabled: ColumnType<number, number | undefined, number>;
63
+ created_at: ColumnType<string, string | undefined, never>;
64
+ updated_at: ColumnType<string, string | undefined, string>;
65
+ }
66
+
67
+ export type User = Selectable<UsersTable>;
68
+ export type NewUser = Insertable<UsersTable>;
69
+ export type UserUpdate = Updateable<UsersTable>;
70
+
71
+ export interface PasskeysTable {
72
+ id: string; // credentialID base64url encoded
73
+ user_id: string;
74
+ name: string | null; // User-defined alias
75
+ public_key: string; // base64url encoded
76
+ counter: number;
77
+ device_type: string;
78
+ backed_up: number;
79
+ transports: string | null;
80
+ created_at: ColumnType<string, string | undefined, never>;
81
+ last_used_at: string | null;
82
+ }
83
+
84
+ export type Passkey = Selectable<PasskeysTable>;
85
+ export type NewPasskey = Insertable<PasskeysTable>;
86
+ export type PasskeyUpdate = Updateable<PasskeysTable>;
87
+
88
+ export interface SessionsTable {
89
+ id: string;
90
+ user_id: string;
91
+ expires_at: string;
92
+ }
93
+
94
+ export type Session = Selectable<SessionsTable>;
95
+ export type NewSession = Insertable<SessionsTable>;
96
+ export type SessionUpdate = Updateable<SessionsTable>;
97
+
98
+ export interface ApiTokensTable {
99
+ id: string; // Token prefix (ec_pat_...)
100
+ user_id: string;
101
+ name: string;
102
+ hash: string; // SHA-256 hashed secret
103
+ scopes: string; // JSON string (array or structured object)
104
+ expires_at: string | null;
105
+ last_used_at: string | null;
106
+ created_at: ColumnType<string, string | undefined, never>;
107
+ }
108
+
109
+ export type ApiToken = Selectable<ApiTokensTable>;
110
+ export type NewApiToken = Insertable<ApiTokensTable>;
111
+
112
+ export interface OAuthAccountsTable {
113
+ provider_id: string;
114
+ provider_user_id: string;
115
+ user_id: string;
116
+ }
117
+
118
+ export type OAuthAccount = Selectable<OAuthAccountsTable>;
119
+ export type NewOAuthAccount = Insertable<OAuthAccountsTable>;
120
+
121
+ export interface VerificationTokensTable {
122
+ identifier: string; // Email
123
+ token: string; // Hashed token
124
+ expires_at: string;
125
+ }
126
+
127
+ export type VerificationToken = Selectable<VerificationTokensTable>;
128
+ export type NewVerificationToken = Insertable<VerificationTokensTable>;
129
+
130
+ export interface DeviceCodesTable {
131
+ device_code: string;
132
+ user_code: string;
133
+ client_id: string;
134
+ user_id: string | null; // Null until user approves
135
+ scopes: string; // JSON string array
136
+ expires_at: string;
137
+ created_at: ColumnType<string, string | undefined, never>;
138
+ }
139
+
140
+ export type DeviceCode = Selectable<DeviceCodesTable>;
141
+ export type NewDeviceCode = Insertable<DeviceCodesTable>;
142
+ export type DeviceCodeUpdate = Updateable<DeviceCodesTable>;
package/src/index.ts ADDED
@@ -0,0 +1,12 @@
1
+ export * from './auth';
2
+ export * from './db';
3
+ export * from './server';
4
+ export * from './client';
5
+ export * from './types';
6
+
7
+ import { createFlareAPI } from './server';
8
+
9
+ /**
10
+ * @deprecated Use createFlareAPI from 'flarecms/server' for better modularity.
11
+ */
12
+ export const flarecms = createFlareAPI;
@@ -0,0 +1,66 @@
1
+ import { Hono } from 'hono';
2
+ import { corsMiddleware } from '../api/middlewares/cors';
3
+ import { setupMiddleware, authMiddleware } from '../api/middlewares/auth';
4
+ import { authRoutes } from '../api/routes/auth';
5
+ import { setupRoutes } from '../api/routes/setup';
6
+ import { collectionsRoutes } from '../api/routes/collections';
7
+ import { contentRoutes } from '../api/routes/content';
8
+ import { tokenRoutes } from '../api/routes/tokens';
9
+ import { deviceRoutes } from '../api/routes/device';
10
+ import { magicRoutes } from '../api/routes/magic';
11
+ import { oauthRoutes } from '../api/routes/oauth';
12
+ import { settingsRoutes } from '../api/routes/settings';
13
+ import { mcpRoutes } from '../api/routes/mcp';
14
+ import { apiResponse } from '../api/lib/response';
15
+ import type { Bindings, Variables } from '../types';
16
+
17
+ /**
18
+ * Creates the modular FlareCMS API router.
19
+ * This can be mounted into any Hono application.
20
+ *
21
+ * @example
22
+ * app.route('/api', createFlareAPI({ base: '/admin' }));
23
+ */
24
+ export function createFlareAPI(options: { base?: string } = {}) {
25
+ const base = options.base || '/admin';
26
+ const api = new Hono<{ Bindings: Bindings; Variables: Variables }>();
27
+
28
+ // Middlewares
29
+ api.use('*', corsMiddleware);
30
+ api.use('*', async (c, next) => {
31
+ // Collect reserved slugs from base and standard system paths
32
+ const adminPrefix = base.replace(/^\//, '') || 'admin';
33
+ c.set('reservedSlugs', [
34
+ adminPrefix,
35
+ 'api',
36
+ 'setup',
37
+ 'auth',
38
+ 'login',
39
+ 'signup',
40
+ 'collections',
41
+ 'users',
42
+ 'settings',
43
+ 'oauth',
44
+ ]);
45
+ await next();
46
+ });
47
+
48
+ api.use('/*', setupMiddleware);
49
+ api.use('/*', authMiddleware);
50
+
51
+ // Routes
52
+ api.route('/auth', authRoutes);
53
+ api.route('/setup', setupRoutes);
54
+ api.route('/collections', collectionsRoutes);
55
+ api.route('/content', contentRoutes);
56
+ api.route('/tokens', tokenRoutes);
57
+ api.route('/device', deviceRoutes);
58
+ api.route('/magic', magicRoutes);
59
+ api.route('/oauth', oauthRoutes);
60
+ api.route('/settings', settingsRoutes);
61
+ api.route('/mcp', mcpRoutes);
62
+
63
+ api.get('/health', (c) => apiResponse.ok(c, { status: 'ok' }));
64
+
65
+ return api;
66
+ }
package/src/types.ts ADDED
@@ -0,0 +1,20 @@
1
+ import {
2
+ type D1Database,
3
+ type KVNamespace,
4
+ type Fetcher,
5
+ } from '@cloudflare/workers-types';
6
+
7
+ export type Bindings = {
8
+ DB: D1Database;
9
+ KV: KVNamespace;
10
+ ASSETS: Fetcher;
11
+ AUTH_SECRET: string;
12
+ GITHUB_CLIENT_ID?: string;
13
+ GITHUB_CLIENT_SECRET?: string;
14
+ };
15
+
16
+ export type Variables = {
17
+ user: any;
18
+ scopes: string[];
19
+ reservedSlugs: string[];
20
+ };
package/style.css.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Type declarations for the FlareCMS prefixed stylesheet.
3
+ * This allows importing the CSS file as a side-effect in TypeScript projects.
4
+ */
5
+ declare module 'flarecms/style.css' {
6
+ const content: any;
7
+ export default content;
8
+ }
@@ -0,0 +1,21 @@
1
+ import { expect, test, describe } from "bun:test";
2
+ import fs from "node:fs";
3
+
4
+ describe("CSS Modularization", () => {
5
+ test("CSS is built with flare-cms prefix", () => {
6
+ const css = fs.readFileSync("./dist/style.css", "utf8");
7
+ // Verify prefix exists
8
+ expect(css).toContain(".flare-cms");
9
+ });
10
+
11
+ test("Global tags are scoped under flare-cms", () => {
12
+ const css = fs.readFileSync("./dist/style.css", "utf8");
13
+
14
+ // html/body are converted to .flare-cms
15
+ expect(css).toContain(".flare-cms {");
16
+
17
+ // Verify that utility classes are prefixed with the scope
18
+ // Example: .flare-cms .bg-background
19
+ expect(css).toContain(".flare-cms .bg-background");
20
+ });
21
+ });
@@ -0,0 +1,29 @@
1
+ import { expect, test, describe } from "bun:test";
2
+ import { createFlareAPI } from "../src/server";
3
+ import { Hono } from "hono";
4
+
5
+ describe("Modular API", () => {
6
+ test("createFlareAPI returns a Hono instance", () => {
7
+ const api = createFlareAPI();
8
+ expect(api).toBeInstanceOf(Hono);
9
+ });
10
+
11
+ test("API has health check route", async () => {
12
+ const api = createFlareAPI();
13
+ const res = await api.request("/health");
14
+ expect(res.status).toBe(200);
15
+ const data: any = await res.json();
16
+ expect(data.data.status).toBe("ok");
17
+ });
18
+
19
+ test("API injects reservedSlugs middleware", async () => {
20
+ const api = createFlareAPI({ base: '/custom-admin' });
21
+ api.get('/test-context', (c) => {
22
+ return c.json({ slugs: c.get('reservedSlugs') });
23
+ });
24
+ const res = await api.request("/test-context");
25
+ const data: any = await res.json();
26
+ expect(data.slugs).toContain('custom-admin');
27
+ expect(data.slugs).toContain('api');
28
+ });
29
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "baseUrl": ".",
5
+ "paths": {
6
+ "@/*": ["./src/client/*"]
7
+ }
8
+ },
9
+ "include": ["src"]
10
+ }