Skip to content

Commit 475efec

Browse files
committed
feat: add feature flags to devtools
1 parent 49c2945 commit 475efec

File tree

12 files changed

+220
-76
lines changed

12 files changed

+220
-76
lines changed

apps/test-bot/src/feature-flags/red-embed-color.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import { flag } from 'commandkit';
33
export const redEmbedColor = flag({
44
key: 'red-embed-color',
55
description: 'Red embed color',
6+
identify() {
7+
return { user: '123' };
8+
},
69
decide() {
710
return Math.random() < 0.5;
811
},

packages/commandkit/src/flags/feature-flags.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ export interface FlagRunner<E, R> {
7474
}
7575

7676
export class FeatureFlag<R, T> {
77-
public constructor(private options: FeatureFlagDefinition<R, T>) {
77+
public constructor(public readonly options: FeatureFlagDefinition<R, T>) {
7878
const FlagStore = getCommandKit(true).flags;
7979

8080
if (FlagStore.has(options.key)) {

packages/devtools-ui/src/api/client.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { ClientUser } from './structures/client-user';
33
import { EventManager } from './structures/events';
44
import { GuildManager } from './structures/guilds';
55
import { PluginManager } from './structures/plugins';
6+
import { FeatureFlag } from './types';
67

78
export class Client<Ready extends boolean = boolean> {
89
public api = axiosClient;
@@ -35,6 +36,13 @@ export class Client<Ready extends boolean = boolean> {
3536
return this._user;
3637
}
3738

39+
public async getFeatureFlags() {
40+
const { data } = await this.api.get<{ flags: FeatureFlag[] }>(
41+
'/feature-flags',
42+
);
43+
return data;
44+
}
45+
3846
public async login(username: string, password: string) {
3947
const { data } = await this.api.post('/auth/login', {
4048
username,

packages/devtools-ui/src/api/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,9 @@ export interface GuildResponse {
6161
bannerURL: string | null;
6262
}>;
6363
}
64+
65+
export interface FeatureFlag {
66+
key: string;
67+
description: string | null;
68+
hasIdentify: boolean;
69+
}

packages/devtools-ui/src/components/app-sidebar.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import * as React from 'react';
2-
import { BellIcon, PlugIcon, SquareTerminal, WrenchIcon } from 'lucide-react';
2+
import {
3+
BellIcon,
4+
PlugIcon,
5+
SquareTerminal,
6+
WrenchIcon,
7+
ScanEye,
8+
} from 'lucide-react';
39
import { VscGithub } from 'react-icons/vsc';
410
import { FaDiscord } from 'react-icons/fa';
511

@@ -50,6 +56,12 @@ const data = {
5056
icon: FaDiscord,
5157
isActive: false,
5258
},
59+
{
60+
title: 'Feature Flags',
61+
url: '/feature-flags',
62+
icon: ScanEye,
63+
isActive: false,
64+
},
5365
],
5466
navSecondary: [
5567
{

packages/devtools-ui/src/routeTree.gen.ts

Lines changed: 106 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -10,142 +10,174 @@
1010

1111
// Import Routes
1212

13-
import { Route as rootRoute } from './routes/__root';
14-
import { Route as PluginsImport } from './routes/plugins';
15-
import { Route as GuildsImport } from './routes/guilds';
16-
import { Route as EventsImport } from './routes/events';
17-
import { Route as CommandsImport } from './routes/commands';
18-
import { Route as IndexImport } from './routes/index';
13+
import { Route as rootRoute } from './routes/__root'
14+
import { Route as PluginsImport } from './routes/plugins'
15+
import { Route as GuildsImport } from './routes/guilds'
16+
import { Route as FeatureFlagsImport } from './routes/feature-flags'
17+
import { Route as EventsImport } from './routes/events'
18+
import { Route as CommandsImport } from './routes/commands'
19+
import { Route as IndexImport } from './routes/index'
1920

2021
// Create/Update Routes
2122

2223
const PluginsRoute = PluginsImport.update({
2324
id: '/plugins',
2425
path: '/plugins',
2526
getParentRoute: () => rootRoute,
26-
} as any);
27+
} as any)
2728

2829
const GuildsRoute = GuildsImport.update({
2930
id: '/guilds',
3031
path: '/guilds',
3132
getParentRoute: () => rootRoute,
32-
} as any);
33+
} as any)
34+
35+
const FeatureFlagsRoute = FeatureFlagsImport.update({
36+
id: '/feature-flags',
37+
path: '/feature-flags',
38+
getParentRoute: () => rootRoute,
39+
} as any)
3340

3441
const EventsRoute = EventsImport.update({
3542
id: '/events',
3643
path: '/events',
3744
getParentRoute: () => rootRoute,
38-
} as any);
45+
} as any)
3946

4047
const CommandsRoute = CommandsImport.update({
4148
id: '/commands',
4249
path: '/commands',
4350
getParentRoute: () => rootRoute,
44-
} as any);
51+
} as any)
4552

4653
const IndexRoute = IndexImport.update({
4754
id: '/',
4855
path: '/',
4956
getParentRoute: () => rootRoute,
50-
} as any);
57+
} as any)
5158

5259
// Populate the FileRoutesByPath interface
5360

5461
declare module '@tanstack/react-router' {
5562
interface FileRoutesByPath {
5663
'/': {
57-
id: '/';
58-
path: '/';
59-
fullPath: '/';
60-
preLoaderRoute: typeof IndexImport;
61-
parentRoute: typeof rootRoute;
62-
};
64+
id: '/'
65+
path: '/'
66+
fullPath: '/'
67+
preLoaderRoute: typeof IndexImport
68+
parentRoute: typeof rootRoute
69+
}
6370
'/commands': {
64-
id: '/commands';
65-
path: '/commands';
66-
fullPath: '/commands';
67-
preLoaderRoute: typeof CommandsImport;
68-
parentRoute: typeof rootRoute;
69-
};
71+
id: '/commands'
72+
path: '/commands'
73+
fullPath: '/commands'
74+
preLoaderRoute: typeof CommandsImport
75+
parentRoute: typeof rootRoute
76+
}
7077
'/events': {
71-
id: '/events';
72-
path: '/events';
73-
fullPath: '/events';
74-
preLoaderRoute: typeof EventsImport;
75-
parentRoute: typeof rootRoute;
76-
};
78+
id: '/events'
79+
path: '/events'
80+
fullPath: '/events'
81+
preLoaderRoute: typeof EventsImport
82+
parentRoute: typeof rootRoute
83+
}
84+
'/feature-flags': {
85+
id: '/feature-flags'
86+
path: '/feature-flags'
87+
fullPath: '/feature-flags'
88+
preLoaderRoute: typeof FeatureFlagsImport
89+
parentRoute: typeof rootRoute
90+
}
7791
'/guilds': {
78-
id: '/guilds';
79-
path: '/guilds';
80-
fullPath: '/guilds';
81-
preLoaderRoute: typeof GuildsImport;
82-
parentRoute: typeof rootRoute;
83-
};
92+
id: '/guilds'
93+
path: '/guilds'
94+
fullPath: '/guilds'
95+
preLoaderRoute: typeof GuildsImport
96+
parentRoute: typeof rootRoute
97+
}
8498
'/plugins': {
85-
id: '/plugins';
86-
path: '/plugins';
87-
fullPath: '/plugins';
88-
preLoaderRoute: typeof PluginsImport;
89-
parentRoute: typeof rootRoute;
90-
};
99+
id: '/plugins'
100+
path: '/plugins'
101+
fullPath: '/plugins'
102+
preLoaderRoute: typeof PluginsImport
103+
parentRoute: typeof rootRoute
104+
}
91105
}
92106
}
93107

94108
// Create and export the route tree
95109

96110
export interface FileRoutesByFullPath {
97-
'/': typeof IndexRoute;
98-
'/commands': typeof CommandsRoute;
99-
'/events': typeof EventsRoute;
100-
'/guilds': typeof GuildsRoute;
101-
'/plugins': typeof PluginsRoute;
111+
'/': typeof IndexRoute
112+
'/commands': typeof CommandsRoute
113+
'/events': typeof EventsRoute
114+
'/feature-flags': typeof FeatureFlagsRoute
115+
'/guilds': typeof GuildsRoute
116+
'/plugins': typeof PluginsRoute
102117
}
103118

104119
export interface FileRoutesByTo {
105-
'/': typeof IndexRoute;
106-
'/commands': typeof CommandsRoute;
107-
'/events': typeof EventsRoute;
108-
'/guilds': typeof GuildsRoute;
109-
'/plugins': typeof PluginsRoute;
120+
'/': typeof IndexRoute
121+
'/commands': typeof CommandsRoute
122+
'/events': typeof EventsRoute
123+
'/feature-flags': typeof FeatureFlagsRoute
124+
'/guilds': typeof GuildsRoute
125+
'/plugins': typeof PluginsRoute
110126
}
111127

112128
export interface FileRoutesById {
113-
__root__: typeof rootRoute;
114-
'/': typeof IndexRoute;
115-
'/commands': typeof CommandsRoute;
116-
'/events': typeof EventsRoute;
117-
'/guilds': typeof GuildsRoute;
118-
'/plugins': typeof PluginsRoute;
129+
__root__: typeof rootRoute
130+
'/': typeof IndexRoute
131+
'/commands': typeof CommandsRoute
132+
'/events': typeof EventsRoute
133+
'/feature-flags': typeof FeatureFlagsRoute
134+
'/guilds': typeof GuildsRoute
135+
'/plugins': typeof PluginsRoute
119136
}
120137

121138
export interface FileRouteTypes {
122-
fileRoutesByFullPath: FileRoutesByFullPath;
123-
fullPaths: '/' | '/commands' | '/events' | '/guilds' | '/plugins';
124-
fileRoutesByTo: FileRoutesByTo;
125-
to: '/' | '/commands' | '/events' | '/guilds' | '/plugins';
126-
id: '__root__' | '/' | '/commands' | '/events' | '/guilds' | '/plugins';
127-
fileRoutesById: FileRoutesById;
139+
fileRoutesByFullPath: FileRoutesByFullPath
140+
fullPaths:
141+
| '/'
142+
| '/commands'
143+
| '/events'
144+
| '/feature-flags'
145+
| '/guilds'
146+
| '/plugins'
147+
fileRoutesByTo: FileRoutesByTo
148+
to: '/' | '/commands' | '/events' | '/feature-flags' | '/guilds' | '/plugins'
149+
id:
150+
| '__root__'
151+
| '/'
152+
| '/commands'
153+
| '/events'
154+
| '/feature-flags'
155+
| '/guilds'
156+
| '/plugins'
157+
fileRoutesById: FileRoutesById
128158
}
129159

130160
export interface RootRouteChildren {
131-
IndexRoute: typeof IndexRoute;
132-
CommandsRoute: typeof CommandsRoute;
133-
EventsRoute: typeof EventsRoute;
134-
GuildsRoute: typeof GuildsRoute;
135-
PluginsRoute: typeof PluginsRoute;
161+
IndexRoute: typeof IndexRoute
162+
CommandsRoute: typeof CommandsRoute
163+
EventsRoute: typeof EventsRoute
164+
FeatureFlagsRoute: typeof FeatureFlagsRoute
165+
GuildsRoute: typeof GuildsRoute
166+
PluginsRoute: typeof PluginsRoute
136167
}
137168

138169
const rootRouteChildren: RootRouteChildren = {
139170
IndexRoute: IndexRoute,
140171
CommandsRoute: CommandsRoute,
141172
EventsRoute: EventsRoute,
173+
FeatureFlagsRoute: FeatureFlagsRoute,
142174
GuildsRoute: GuildsRoute,
143175
PluginsRoute: PluginsRoute,
144-
};
176+
}
145177

146178
export const routeTree = rootRoute
147179
._addFileChildren(rootRouteChildren)
148-
._addFileTypes<FileRouteTypes>();
180+
._addFileTypes<FileRouteTypes>()
149181

150182
/* ROUTE_MANIFEST_START
151183
{
@@ -156,6 +188,7 @@ export const routeTree = rootRoute
156188
"/",
157189
"/commands",
158190
"/events",
191+
"/feature-flags",
159192
"/guilds",
160193
"/plugins"
161194
]
@@ -169,6 +202,9 @@ export const routeTree = rootRoute
169202
"/events": {
170203
"filePath": "events.tsx"
171204
},
205+
"/feature-flags": {
206+
"filePath": "feature-flags.tsx"
207+
},
172208
"/guilds": {
173209
"filePath": "guilds.tsx"
174210
},

packages/devtools-ui/src/routes/commands.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ function CommandHierarchy() {
220220
}
221221

222222
return (
223-
<div className="container mx-auto p-4">
223+
<div className="container mx-auto py-4">
224224
<h1 className="text-2xl font-bold mb-6">Commands</h1>
225225

226226
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">

0 commit comments

Comments
 (0)