@tuturuuu/utils 0.0.3 → 0.6.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.
- package/CHANGELOG.md +305 -0
- package/biome.json +5 -0
- package/jsr.json +8 -8
- package/package.json +63 -32
- package/src/__tests__/ai-temp-auth.test.ts +309 -0
- package/src/__tests__/api-proxy-guard.test.ts +1451 -0
- package/src/__tests__/app-url.test.ts +270 -0
- package/src/__tests__/avatar-url.test.ts +97 -0
- package/src/__tests__/color-helper.test.ts +179 -0
- package/src/__tests__/constants.test.ts +351 -0
- package/src/__tests__/crypto.test.ts +107 -0
- package/src/__tests__/date-helper.test.ts +408 -0
- package/src/__tests__/fixtures/task-description-full-featured.json +456 -0
- package/src/__tests__/format.test.ts +317 -0
- package/src/__tests__/html-sanitizer.test.ts +360 -0
- package/src/__tests__/interest-calculator.test.ts +336 -0
- package/src/__tests__/interest-detector.test.ts +222 -0
- package/src/__tests__/label-colors.test.ts +241 -0
- package/src/__tests__/name-helper.test.ts +158 -0
- package/src/__tests__/node-diff.test.ts +576 -0
- package/src/__tests__/notification-service.test.ts +210 -0
- package/src/__tests__/onboarding-helper.test.ts +331 -0
- package/src/__tests__/path-helper.test.ts +152 -0
- package/src/__tests__/permissions.test.tsx +81 -0
- package/src/__tests__/request-emoji-limit.test.ts +172 -0
- package/src/__tests__/search-helper.test.ts +51 -0
- package/src/__tests__/storage-display-name.test.ts +37 -0
- package/src/__tests__/storage-path.test.ts +238 -0
- package/src/__tests__/tag-utils.test.ts +205 -0
- package/src/__tests__/task-description-yjs-state.test.ts +581 -0
- package/src/__tests__/task-helper-board-api-routing.test.ts +94 -0
- package/src/__tests__/task-helper-create-task.test.ts +129 -0
- package/src/__tests__/task-helpers.test.ts +464 -0
- package/src/__tests__/task-overrides.test.ts +305 -0
- package/src/__tests__/task-reorder-cache.test.ts +74 -0
- package/src/__tests__/task-sort-keys.test.ts +36 -0
- package/src/__tests__/task-transformers.test.ts +62 -0
- package/src/__tests__/text-helper.test.ts +776 -0
- package/src/__tests__/time-helper.test.ts +70 -0
- package/src/__tests__/time-tracker-period.test.ts +55 -0
- package/src/__tests__/timezone.test.ts +117 -0
- package/src/__tests__/upstash-rest.test.ts +77 -0
- package/src/__tests__/uuid-helper.test.ts +133 -0
- package/src/__tests__/workspace-helper.test.ts +859 -0
- package/src/__tests__/workspace-limits.test.ts +255 -0
- package/src/__tests__/yjs-helper.test.ts +581 -0
- package/src/abuse-protection/__tests__/backend-rate-limit.test.ts +113 -0
- package/src/abuse-protection/__tests__/edge.test.ts +136 -0
- package/src/abuse-protection/__tests__/index.test.ts +562 -0
- package/src/abuse-protection/__tests__/reputation.test.ts +192 -0
- package/src/abuse-protection/backend-rate-limit.ts +44 -0
- package/src/abuse-protection/constants.ts +117 -0
- package/src/abuse-protection/edge.ts +223 -0
- package/src/abuse-protection/index.ts +1545 -0
- package/src/abuse-protection/reputation.ts +587 -0
- package/src/abuse-protection/types.ts +97 -0
- package/src/abuse-protection/user-agent.ts +124 -0
- package/src/abuse-protection/user-suspension.ts +231 -0
- package/src/ai-temp-auth.ts +315 -0
- package/src/api-proxy-guard.ts +965 -0
- package/src/app-url.ts +96 -0
- package/src/avatar-url.ts +64 -0
- package/src/break-duration.ts +84 -0
- package/src/calendar-auth-token.test.ts +37 -0
- package/src/calendar-auth-token.ts +19 -0
- package/src/calendar-sync-coordination.md +197 -0
- package/src/calendar-utils.test.ts +169 -0
- package/src/calendar-utils.ts +91 -0
- package/src/color-helper.ts +110 -0
- package/src/common/nextjs.tsx +99 -0
- package/src/common/scan.tsx +15 -0
- package/src/configs/reports.ts +160 -0
- package/src/constants.ts +85 -0
- package/src/crypto.ts +21 -0
- package/src/currencies.ts +97 -0
- package/src/date-helper.ts +313 -0
- package/src/editor/convert-to-task.ts +264 -0
- package/src/editor/index.ts +5 -0
- package/src/email/__tests__/client.test.ts +141 -0
- package/src/email/__tests__/validation.test.ts +46 -0
- package/src/email/client.ts +92 -0
- package/src/email/server.ts +128 -0
- package/src/email/validation.ts +11 -0
- package/src/encryption/__tests__/calendar-events.test.ts +411 -0
- package/src/encryption/__tests__/configuration.test.ts +114 -0
- package/src/encryption/__tests__/field-encryption.test.ts +232 -0
- package/src/encryption/__tests__/key-generation.test.ts +30 -0
- package/src/encryption/__tests__/performance-edge-cases.test.ts +187 -0
- package/src/encryption/__tests__/test-helpers.ts +22 -0
- package/src/encryption/__tests__/workspace-key-encryption.test.ts +129 -0
- package/src/encryption/encryption-service.ts +343 -0
- package/src/encryption/index.ts +25 -0
- package/src/encryption/types.ts +57 -0
- package/src/exchange-rates.ts +49 -0
- package/src/feature-flags/__tests__/feature-flags.test.ts +302 -0
- package/src/feature-flags/core.ts +322 -0
- package/src/feature-flags/data.ts +16 -0
- package/src/feature-flags/default.ts +18 -0
- package/src/feature-flags/index.ts +7 -0
- package/src/feature-flags/requestable-features.ts +79 -0
- package/src/feature-flags/types.ts +4 -0
- package/src/fetcher.ts +2 -0
- package/src/finance/index.ts +4 -0
- package/src/finance/interest-calculator.ts +456 -0
- package/src/finance/interest-detector.ts +141 -0
- package/src/finance/transform-invoice-results.ts +219 -0
- package/src/finance/wallet-permissions.test.ts +169 -0
- package/src/finance/wallet-permissions.ts +82 -0
- package/src/format.ts +120 -1
- package/src/generated/platform-build-metadata.ts +11 -0
- package/src/hooks/use-platform.ts +64 -0
- package/src/html-sanitizer.ts +155 -0
- package/src/internal-domains.ts +497 -0
- package/src/keyboard-preset.ts +109 -0
- package/src/label-colors.ts +213 -0
- package/src/launchable-apps.test.ts +126 -0
- package/src/launchable-apps.ts +490 -0
- package/src/name-helper.ts +269 -0
- package/src/next-config.test.ts +234 -0
- package/src/next-config.ts +203 -0
- package/src/node-diff.ts +375 -0
- package/src/notification-service.ts +379 -0
- package/src/nova/scores/__tests__/calculate.test.ts +254 -0
- package/src/nova/scores/calculate.ts +132 -0
- package/src/nova/submissions/check-permission.ts +132 -0
- package/src/onboarding-helper.ts +213 -0
- package/src/path-helper.ts +93 -0
- package/src/permissions.tsx +1170 -0
- package/src/plan-helpers.test.ts +188 -0
- package/src/plan-helpers.ts +80 -0
- package/src/platform-release.test.ts +74 -0
- package/src/platform-release.ts +155 -0
- package/src/portless.ts +124 -0
- package/src/priority-styles.ts +42 -0
- package/src/request-emoji-limit.ts +335 -0
- package/src/search-helper.ts +18 -0
- package/src/search.test.ts +89 -0
- package/src/search.ts +355 -0
- package/src/storage-display-name.ts +30 -0
- package/src/storage-path.ts +147 -0
- package/src/tag-utils.ts +159 -0
- package/src/task/reorder.ts +245 -0
- package/src/task/transformers.ts +149 -0
- package/src/task-date-timezone.ts +133 -0
- package/src/task-description-content.ts +240 -0
- package/src/task-helper/board.ts +193 -0
- package/src/task-helper/bulk-actions.ts +564 -0
- package/src/task-helper/personal-external-staging.ts +21 -0
- package/src/task-helper/recycle-bin.ts +202 -0
- package/src/task-helper/relationships.ts +346 -0
- package/src/task-helper/shared.ts +109 -0
- package/src/task-helper/sort-keys.ts +337 -0
- package/src/task-helper/task-hooks-basic.ts +342 -0
- package/src/task-helper/task-hooks-move.ts +264 -0
- package/src/task-helper/task-operations.ts +278 -0
- package/src/task-helper.ts +12 -0
- package/src/task-helpers.ts +241 -0
- package/src/task-list-status.ts +62 -0
- package/src/task-overrides.ts +82 -0
- package/src/task-snapshot.ts +374 -0
- package/src/text-diff.ts +81 -0
- package/src/text-helper.ts +537 -0
- package/src/time-helper.ts +63 -0
- package/src/time-tracker-period.ts +73 -0
- package/src/timeblock-helper.ts +418 -0
- package/src/timezone.ts +190 -0
- package/src/timezones.json +1271 -0
- package/src/upstash-rest.ts +56 -0
- package/src/user-helper.ts +296 -0
- package/src/uuid-helper.ts +11 -0
- package/src/workspace-handle.ts +10 -0
- package/src/workspace-helper.ts +1408 -0
- package/src/workspace-limits.ts +68 -0
- package/src/yjs-helper.ts +217 -0
- package/src/yjs-task-description.ts +81 -0
- package/tsconfig.json +3 -5
- package/tsconfig.typecheck.json +33 -0
- package/vitest.config.ts +36 -0
- package/dist/index.d.ts +0 -8
- package/dist/index.js +0 -2
- package/dist/index.js.map +0 -1
- package/dist/index.mjs +0 -2
- package/dist/index.mjs.map +0 -1
- package/eslint.config.mjs +0 -20
- package/rollup.config.js +0 -41
- package/src/index.ts +0 -1
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { FEATURE_GROUPS } from '../core';
|
|
3
|
+
import { FEATURE_FLAGS } from '../data';
|
|
4
|
+
import type { FeatureFlag, FeatureFlagMap } from '../types';
|
|
5
|
+
|
|
6
|
+
describe('FEATURE_FLAGS', () => {
|
|
7
|
+
describe('AI-related flags', () => {
|
|
8
|
+
it('should have ENABLE_AI flag', () => {
|
|
9
|
+
expect(FEATURE_FLAGS.ENABLE_AI).toBe('ENABLE_AI');
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should have ENABLE_AI_ONLY flag', () => {
|
|
13
|
+
expect(FEATURE_FLAGS.ENABLE_AI_ONLY).toBe('ENABLE_AI_ONLY');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should have ENABLE_CHAT flag', () => {
|
|
17
|
+
expect(FEATURE_FLAGS.ENABLE_CHAT).toBe('ENABLE_CHAT');
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('Education-related flags', () => {
|
|
22
|
+
it('should have ENABLE_EDUCATION flag', () => {
|
|
23
|
+
expect(FEATURE_FLAGS.ENABLE_EDUCATION).toBe('ENABLE_EDUCATION');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should have ENABLE_QUIZZES flag', () => {
|
|
27
|
+
expect(FEATURE_FLAGS.ENABLE_QUIZZES).toBe('ENABLE_QUIZZES');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should have ENABLE_CHALLENGES flag', () => {
|
|
31
|
+
expect(FEATURE_FLAGS.ENABLE_CHALLENGES).toBe('ENABLE_CHALLENGES');
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe('Document-related flags', () => {
|
|
36
|
+
it('should have ENABLE_DOCS flag', () => {
|
|
37
|
+
expect(FEATURE_FLAGS.ENABLE_DOCS).toBe('ENABLE_DOCS');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should have ENABLE_DRIVE flag', () => {
|
|
41
|
+
expect(FEATURE_FLAGS.ENABLE_DRIVE).toBe('ENABLE_DRIVE');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should have ENABLE_SLIDES flag', () => {
|
|
45
|
+
expect(FEATURE_FLAGS.ENABLE_SLIDES).toBe('ENABLE_SLIDES');
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('Task-related flags', () => {
|
|
50
|
+
it('should have ENABLE_TASKS flag', () => {
|
|
51
|
+
expect(FEATURE_FLAGS.ENABLE_TASKS).toBe('ENABLE_TASKS');
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('Workspace-related flags', () => {
|
|
56
|
+
it('should have DISABLE_INVITE flag', () => {
|
|
57
|
+
expect(FEATURE_FLAGS.DISABLE_INVITE).toBe('DISABLE_INVITE');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should have PREVENT_WORKSPACE_DELETION flag', () => {
|
|
61
|
+
expect(FEATURE_FLAGS.PREVENT_WORKSPACE_DELETION).toBe(
|
|
62
|
+
'PREVENT_WORKSPACE_DELETION'
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should have ENABLE_AVATAR flag', () => {
|
|
67
|
+
expect(FEATURE_FLAGS.ENABLE_AVATAR).toBe('ENABLE_AVATAR');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should have ENABLE_LOGO flag', () => {
|
|
71
|
+
expect(FEATURE_FLAGS.ENABLE_LOGO).toBe('ENABLE_LOGO');
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe('flag count and structure', () => {
|
|
76
|
+
it('should have exactly 14 feature flags', () => {
|
|
77
|
+
const flags = Object.keys(FEATURE_FLAGS);
|
|
78
|
+
expect(flags).toHaveLength(14);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should have all unique values', () => {
|
|
82
|
+
const values = Object.values(FEATURE_FLAGS);
|
|
83
|
+
const uniqueValues = new Set(values);
|
|
84
|
+
expect(uniqueValues.size).toBe(values.length);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should have keys matching values (self-referential)', () => {
|
|
88
|
+
for (const [key, value] of Object.entries(FEATURE_FLAGS)) {
|
|
89
|
+
expect(key).toBe(value);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe('FEATURE_GROUPS', () => {
|
|
96
|
+
describe('AI_FEATURES group', () => {
|
|
97
|
+
it('should include ENABLE_AI, ENABLE_CHAT, ENABLE_TASKS', () => {
|
|
98
|
+
expect(FEATURE_GROUPS.AI_FEATURES).toContain(FEATURE_FLAGS.ENABLE_AI);
|
|
99
|
+
expect(FEATURE_GROUPS.AI_FEATURES).toContain(FEATURE_FLAGS.ENABLE_CHAT);
|
|
100
|
+
expect(FEATURE_GROUPS.AI_FEATURES).toContain(FEATURE_FLAGS.ENABLE_TASKS);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should have exactly 3 flags', () => {
|
|
104
|
+
expect(FEATURE_GROUPS.AI_FEATURES).toHaveLength(3);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe('EDUCATION_FEATURES group', () => {
|
|
109
|
+
it('should include education-related flags', () => {
|
|
110
|
+
expect(FEATURE_GROUPS.EDUCATION_FEATURES).toContain(
|
|
111
|
+
FEATURE_FLAGS.ENABLE_EDUCATION
|
|
112
|
+
);
|
|
113
|
+
expect(FEATURE_GROUPS.EDUCATION_FEATURES).toContain(
|
|
114
|
+
FEATURE_FLAGS.ENABLE_QUIZZES
|
|
115
|
+
);
|
|
116
|
+
expect(FEATURE_GROUPS.EDUCATION_FEATURES).toContain(
|
|
117
|
+
FEATURE_FLAGS.ENABLE_CHALLENGES
|
|
118
|
+
);
|
|
119
|
+
expect(FEATURE_GROUPS.EDUCATION_FEATURES).toContain(
|
|
120
|
+
FEATURE_FLAGS.ENABLE_AI
|
|
121
|
+
);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should have exactly 4 flags', () => {
|
|
125
|
+
expect(FEATURE_GROUPS.EDUCATION_FEATURES).toHaveLength(4);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe('DOCUMENT_FEATURES group', () => {
|
|
130
|
+
it('should include document-related flags', () => {
|
|
131
|
+
expect(FEATURE_GROUPS.DOCUMENT_FEATURES).toContain(
|
|
132
|
+
FEATURE_FLAGS.ENABLE_DOCS
|
|
133
|
+
);
|
|
134
|
+
expect(FEATURE_GROUPS.DOCUMENT_FEATURES).toContain(
|
|
135
|
+
FEATURE_FLAGS.ENABLE_DRIVE
|
|
136
|
+
);
|
|
137
|
+
expect(FEATURE_GROUPS.DOCUMENT_FEATURES).toContain(
|
|
138
|
+
FEATURE_FLAGS.ENABLE_SLIDES
|
|
139
|
+
);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('should have exactly 3 flags', () => {
|
|
143
|
+
expect(FEATURE_GROUPS.DOCUMENT_FEATURES).toHaveLength(3);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
describe('WORKSPACE_FEATURES group', () => {
|
|
148
|
+
it('should include workspace customization flags', () => {
|
|
149
|
+
expect(FEATURE_GROUPS.WORKSPACE_FEATURES).toContain(
|
|
150
|
+
FEATURE_FLAGS.ENABLE_AVATAR
|
|
151
|
+
);
|
|
152
|
+
expect(FEATURE_GROUPS.WORKSPACE_FEATURES).toContain(
|
|
153
|
+
FEATURE_FLAGS.ENABLE_LOGO
|
|
154
|
+
);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('should have exactly 2 flags', () => {
|
|
158
|
+
expect(FEATURE_GROUPS.WORKSPACE_FEATURES).toHaveLength(2);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe('group integrity', () => {
|
|
163
|
+
it('all groups should only contain valid feature flags', () => {
|
|
164
|
+
const allFlags = Object.values(FEATURE_FLAGS);
|
|
165
|
+
|
|
166
|
+
for (const [, group] of Object.entries(FEATURE_GROUPS)) {
|
|
167
|
+
for (const flag of group) {
|
|
168
|
+
expect(allFlags).toContain(flag);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
describe('FeatureFlag type', () => {
|
|
176
|
+
it('should accept valid feature flag keys', () => {
|
|
177
|
+
// Type checking tests
|
|
178
|
+
const flag1: FeatureFlag = 'ENABLE_AI';
|
|
179
|
+
const flag2: FeatureFlag = 'ENABLE_EDUCATION';
|
|
180
|
+
const flag3: FeatureFlag = 'ENABLE_DOCS';
|
|
181
|
+
|
|
182
|
+
expect(flag1).toBe('ENABLE_AI');
|
|
183
|
+
expect(flag2).toBe('ENABLE_EDUCATION');
|
|
184
|
+
expect(flag3).toBe('ENABLE_DOCS');
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
describe('FeatureFlagMap type', () => {
|
|
189
|
+
it('should create valid feature flag map', () => {
|
|
190
|
+
const flagMap: FeatureFlagMap = {
|
|
191
|
+
ENABLE_AI: true,
|
|
192
|
+
ENABLE_EDUCATION: false,
|
|
193
|
+
ENABLE_QUIZZES: true,
|
|
194
|
+
ENABLE_CHALLENGES: false,
|
|
195
|
+
ENABLE_AI_ONLY: false,
|
|
196
|
+
ENABLE_CHAT: true,
|
|
197
|
+
ENABLE_TASKS: true,
|
|
198
|
+
ENABLE_DOCS: false,
|
|
199
|
+
ENABLE_DRIVE: false,
|
|
200
|
+
ENABLE_SLIDES: false,
|
|
201
|
+
DISABLE_INVITE: false,
|
|
202
|
+
PREVENT_WORKSPACE_DELETION: true,
|
|
203
|
+
ENABLE_AVATAR: true,
|
|
204
|
+
ENABLE_LOGO: true,
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
expect(flagMap.ENABLE_AI).toBe(true);
|
|
208
|
+
expect(flagMap.ENABLE_EDUCATION).toBe(false);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('should enforce boolean values', () => {
|
|
212
|
+
const flagMap: Partial<FeatureFlagMap> = {
|
|
213
|
+
ENABLE_AI: true,
|
|
214
|
+
ENABLE_EDUCATION: false,
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
expect(typeof flagMap.ENABLE_AI).toBe('boolean');
|
|
218
|
+
expect(typeof flagMap.ENABLE_EDUCATION).toBe('boolean');
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
describe('Feature flag usage patterns', () => {
|
|
223
|
+
describe('checking multiple flags', () => {
|
|
224
|
+
it('should support checking if all flags are enabled', () => {
|
|
225
|
+
const flagMap: FeatureFlagMap = {
|
|
226
|
+
ENABLE_AI: true,
|
|
227
|
+
ENABLE_EDUCATION: true,
|
|
228
|
+
ENABLE_QUIZZES: true,
|
|
229
|
+
ENABLE_CHALLENGES: true,
|
|
230
|
+
ENABLE_AI_ONLY: false,
|
|
231
|
+
ENABLE_CHAT: true,
|
|
232
|
+
ENABLE_TASKS: true,
|
|
233
|
+
ENABLE_DOCS: true,
|
|
234
|
+
ENABLE_DRIVE: true,
|
|
235
|
+
ENABLE_SLIDES: true,
|
|
236
|
+
DISABLE_INVITE: false,
|
|
237
|
+
PREVENT_WORKSPACE_DELETION: false,
|
|
238
|
+
ENABLE_AVATAR: true,
|
|
239
|
+
ENABLE_LOGO: true,
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const aiFeatures: FeatureFlag[] = [
|
|
243
|
+
'ENABLE_AI',
|
|
244
|
+
'ENABLE_CHAT',
|
|
245
|
+
'ENABLE_TASKS',
|
|
246
|
+
];
|
|
247
|
+
const allAIEnabled = aiFeatures.every((flag) => flagMap[flag]);
|
|
248
|
+
expect(allAIEnabled).toBe(true);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('should support checking if any flag is enabled', () => {
|
|
252
|
+
const flagMap: FeatureFlagMap = {
|
|
253
|
+
ENABLE_AI: false,
|
|
254
|
+
ENABLE_EDUCATION: false,
|
|
255
|
+
ENABLE_QUIZZES: false,
|
|
256
|
+
ENABLE_CHALLENGES: true, // Only this is enabled
|
|
257
|
+
ENABLE_AI_ONLY: false,
|
|
258
|
+
ENABLE_CHAT: false,
|
|
259
|
+
ENABLE_TASKS: false,
|
|
260
|
+
ENABLE_DOCS: false,
|
|
261
|
+
ENABLE_DRIVE: false,
|
|
262
|
+
ENABLE_SLIDES: false,
|
|
263
|
+
DISABLE_INVITE: false,
|
|
264
|
+
PREVENT_WORKSPACE_DELETION: false,
|
|
265
|
+
ENABLE_AVATAR: false,
|
|
266
|
+
ENABLE_LOGO: false,
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const educationFeatures: FeatureFlag[] = [
|
|
270
|
+
'ENABLE_EDUCATION',
|
|
271
|
+
'ENABLE_QUIZZES',
|
|
272
|
+
'ENABLE_CHALLENGES',
|
|
273
|
+
];
|
|
274
|
+
const anyEducationEnabled = educationFeatures.some(
|
|
275
|
+
(flag) => flagMap[flag]
|
|
276
|
+
);
|
|
277
|
+
expect(anyEducationEnabled).toBe(true);
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
describe('negative flags (DISABLE_*)', () => {
|
|
282
|
+
it('should handle DISABLE_INVITE flag logic', () => {
|
|
283
|
+
const flagMap: Partial<FeatureFlagMap> = {
|
|
284
|
+
DISABLE_INVITE: true,
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
// When DISABLE_INVITE is true, invites should be disabled
|
|
288
|
+
const invitesEnabled = !flagMap.DISABLE_INVITE;
|
|
289
|
+
expect(invitesEnabled).toBe(false);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('should handle PREVENT_WORKSPACE_DELETION flag logic', () => {
|
|
293
|
+
const flagMap: Partial<FeatureFlagMap> = {
|
|
294
|
+
PREVENT_WORKSPACE_DELETION: true,
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
// When PREVENT_WORKSPACE_DELETION is true, deletion should be prevented
|
|
298
|
+
const canDelete = !flagMap.PREVENT_WORKSPACE_DELETION;
|
|
299
|
+
expect(canDelete).toBe(false);
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
});
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { verifySecret } from '../workspace-helper';
|
|
2
|
+
import { FEATURE_FLAGS } from './data';
|
|
3
|
+
import type { FeatureFlag, FeatureFlagMap } from './types';
|
|
4
|
+
|
|
5
|
+
export async function requireFeatureFlags(
|
|
6
|
+
wsId: string,
|
|
7
|
+
{
|
|
8
|
+
requiredFlags = [],
|
|
9
|
+
forceAdmin = false,
|
|
10
|
+
}: {
|
|
11
|
+
requiredFlags: FeatureFlag[];
|
|
12
|
+
forceAdmin?: boolean;
|
|
13
|
+
}
|
|
14
|
+
): Promise<{ featureFlags: FeatureFlagMap; missingFlags: FeatureFlag[] }> {
|
|
15
|
+
const featureFlags = await getFeatureFlags(wsId, forceAdmin);
|
|
16
|
+
const missingFlags = requiredFlags.filter((flag) => !featureFlags[flag]);
|
|
17
|
+
return {
|
|
18
|
+
featureFlags,
|
|
19
|
+
missingFlags,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get all feature flags for a workspace
|
|
25
|
+
* @param wsId - Workspace ID
|
|
26
|
+
* @param forceAdmin - Whether to use admin client (default: true for feature flags)
|
|
27
|
+
* @returns Promise<FeatureFlags>
|
|
28
|
+
*/
|
|
29
|
+
export async function getFeatureFlags(
|
|
30
|
+
wsId: string,
|
|
31
|
+
forceAdmin: boolean = true
|
|
32
|
+
): Promise<FeatureFlagMap> {
|
|
33
|
+
const [
|
|
34
|
+
ENABLE_AI,
|
|
35
|
+
ENABLE_EDUCATION,
|
|
36
|
+
ENABLE_QUIZZES,
|
|
37
|
+
ENABLE_CHALLENGES,
|
|
38
|
+
ENABLE_AI_ONLY,
|
|
39
|
+
ENABLE_CHAT,
|
|
40
|
+
ENABLE_TASKS,
|
|
41
|
+
ENABLE_DOCS,
|
|
42
|
+
ENABLE_DRIVE,
|
|
43
|
+
ENABLE_SLIDES,
|
|
44
|
+
DISABLE_INVITE,
|
|
45
|
+
PREVENT_WORKSPACE_DELETION,
|
|
46
|
+
ENABLE_AVATAR,
|
|
47
|
+
ENABLE_LOGO,
|
|
48
|
+
] = await Promise.all([
|
|
49
|
+
verifySecret({
|
|
50
|
+
forceAdmin,
|
|
51
|
+
wsId,
|
|
52
|
+
name: FEATURE_FLAGS.ENABLE_AI,
|
|
53
|
+
value: 'true',
|
|
54
|
+
}),
|
|
55
|
+
verifySecret({
|
|
56
|
+
forceAdmin,
|
|
57
|
+
wsId,
|
|
58
|
+
name: FEATURE_FLAGS.ENABLE_EDUCATION,
|
|
59
|
+
value: 'true',
|
|
60
|
+
}),
|
|
61
|
+
verifySecret({
|
|
62
|
+
forceAdmin,
|
|
63
|
+
wsId,
|
|
64
|
+
name: FEATURE_FLAGS.ENABLE_QUIZZES,
|
|
65
|
+
value: 'true',
|
|
66
|
+
}),
|
|
67
|
+
verifySecret({
|
|
68
|
+
forceAdmin,
|
|
69
|
+
wsId,
|
|
70
|
+
name: FEATURE_FLAGS.ENABLE_CHALLENGES,
|
|
71
|
+
value: 'true',
|
|
72
|
+
}),
|
|
73
|
+
verifySecret({
|
|
74
|
+
forceAdmin,
|
|
75
|
+
wsId,
|
|
76
|
+
name: FEATURE_FLAGS.ENABLE_AI_ONLY,
|
|
77
|
+
value: 'true',
|
|
78
|
+
}),
|
|
79
|
+
verifySecret({
|
|
80
|
+
forceAdmin,
|
|
81
|
+
wsId,
|
|
82
|
+
name: FEATURE_FLAGS.ENABLE_CHAT,
|
|
83
|
+
value: 'true',
|
|
84
|
+
}),
|
|
85
|
+
verifySecret({
|
|
86
|
+
forceAdmin,
|
|
87
|
+
wsId,
|
|
88
|
+
name: FEATURE_FLAGS.ENABLE_TASKS,
|
|
89
|
+
value: 'true',
|
|
90
|
+
}),
|
|
91
|
+
verifySecret({
|
|
92
|
+
forceAdmin,
|
|
93
|
+
wsId,
|
|
94
|
+
name: FEATURE_FLAGS.ENABLE_DOCS,
|
|
95
|
+
value: 'true',
|
|
96
|
+
}),
|
|
97
|
+
verifySecret({
|
|
98
|
+
forceAdmin,
|
|
99
|
+
wsId,
|
|
100
|
+
name: FEATURE_FLAGS.ENABLE_DRIVE,
|
|
101
|
+
value: 'true',
|
|
102
|
+
}),
|
|
103
|
+
verifySecret({
|
|
104
|
+
forceAdmin,
|
|
105
|
+
wsId,
|
|
106
|
+
name: FEATURE_FLAGS.ENABLE_SLIDES,
|
|
107
|
+
value: 'true',
|
|
108
|
+
}),
|
|
109
|
+
verifySecret({
|
|
110
|
+
forceAdmin,
|
|
111
|
+
wsId,
|
|
112
|
+
name: FEATURE_FLAGS.DISABLE_INVITE,
|
|
113
|
+
value: 'true',
|
|
114
|
+
}),
|
|
115
|
+
verifySecret({
|
|
116
|
+
forceAdmin,
|
|
117
|
+
wsId,
|
|
118
|
+
name: FEATURE_FLAGS.PREVENT_WORKSPACE_DELETION,
|
|
119
|
+
value: 'true',
|
|
120
|
+
}),
|
|
121
|
+
verifySecret({
|
|
122
|
+
forceAdmin,
|
|
123
|
+
wsId,
|
|
124
|
+
name: FEATURE_FLAGS.ENABLE_AVATAR,
|
|
125
|
+
value: 'true',
|
|
126
|
+
}),
|
|
127
|
+
verifySecret({
|
|
128
|
+
forceAdmin,
|
|
129
|
+
wsId,
|
|
130
|
+
name: FEATURE_FLAGS.ENABLE_LOGO,
|
|
131
|
+
value: 'true',
|
|
132
|
+
}),
|
|
133
|
+
]);
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
ENABLE_AI,
|
|
137
|
+
ENABLE_EDUCATION,
|
|
138
|
+
ENABLE_QUIZZES,
|
|
139
|
+
ENABLE_CHALLENGES,
|
|
140
|
+
ENABLE_AI_ONLY,
|
|
141
|
+
ENABLE_CHAT,
|
|
142
|
+
ENABLE_TASKS,
|
|
143
|
+
ENABLE_DOCS,
|
|
144
|
+
ENABLE_DRIVE,
|
|
145
|
+
ENABLE_SLIDES,
|
|
146
|
+
DISABLE_INVITE,
|
|
147
|
+
PREVENT_WORKSPACE_DELETION,
|
|
148
|
+
ENABLE_AVATAR,
|
|
149
|
+
ENABLE_LOGO,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Get a specific feature flag
|
|
155
|
+
* @param wsId - Workspace ID
|
|
156
|
+
* @param flagName - Feature flag name
|
|
157
|
+
* @param forceAdmin - Whether to use admin client (default: true for feature flags)
|
|
158
|
+
* @returns Promise<boolean>
|
|
159
|
+
*/
|
|
160
|
+
export async function getFeatureFlag(
|
|
161
|
+
wsId: string,
|
|
162
|
+
flagName: FeatureFlag,
|
|
163
|
+
forceAdmin: boolean = true
|
|
164
|
+
): Promise<boolean> {
|
|
165
|
+
return verifySecret({
|
|
166
|
+
forceAdmin,
|
|
167
|
+
wsId,
|
|
168
|
+
name: FEATURE_FLAGS[flagName],
|
|
169
|
+
value: 'true',
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Check if multiple feature flags are enabled
|
|
175
|
+
* @param wsId - Workspace ID
|
|
176
|
+
* @param flagNames - Array of feature flag names
|
|
177
|
+
* @param forceAdmin - Whether to use admin client (default: true for feature flags)
|
|
178
|
+
* @returns Promise<boolean>
|
|
179
|
+
*/
|
|
180
|
+
export async function areFeatureFlagsEnabled(
|
|
181
|
+
wsId: string,
|
|
182
|
+
flagNames: readonly FeatureFlag[],
|
|
183
|
+
forceAdmin: boolean = true
|
|
184
|
+
): Promise<boolean> {
|
|
185
|
+
const checks = await Promise.all(
|
|
186
|
+
flagNames.map((flagName) =>
|
|
187
|
+
verifySecret({
|
|
188
|
+
forceAdmin,
|
|
189
|
+
wsId,
|
|
190
|
+
name: FEATURE_FLAGS[flagName],
|
|
191
|
+
value: 'true',
|
|
192
|
+
})
|
|
193
|
+
)
|
|
194
|
+
);
|
|
195
|
+
return checks.every(Boolean);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Check if any of the specified feature flags are enabled
|
|
200
|
+
* @param wsId - Workspace ID
|
|
201
|
+
* @param flagNames - Array of feature flag names
|
|
202
|
+
* @param forceAdmin - Whether to use admin client (default: true for feature flags)
|
|
203
|
+
* @returns Promise<boolean>
|
|
204
|
+
*/
|
|
205
|
+
export async function isAnyFeatureFlagEnabled(
|
|
206
|
+
wsId: string,
|
|
207
|
+
flagNames: readonly FeatureFlag[],
|
|
208
|
+
forceAdmin: boolean = true
|
|
209
|
+
): Promise<boolean> {
|
|
210
|
+
const checks = await Promise.all(
|
|
211
|
+
flagNames.map((flagName) =>
|
|
212
|
+
verifySecret({
|
|
213
|
+
forceAdmin,
|
|
214
|
+
wsId,
|
|
215
|
+
name: FEATURE_FLAGS[flagName],
|
|
216
|
+
value: 'true',
|
|
217
|
+
})
|
|
218
|
+
)
|
|
219
|
+
);
|
|
220
|
+
return checks.some(Boolean);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Common feature flag combinations
|
|
224
|
+
export const FEATURE_GROUPS = {
|
|
225
|
+
AI_FEATURES: [
|
|
226
|
+
FEATURE_FLAGS.ENABLE_AI,
|
|
227
|
+
FEATURE_FLAGS.ENABLE_CHAT,
|
|
228
|
+
FEATURE_FLAGS.ENABLE_TASKS,
|
|
229
|
+
] as const,
|
|
230
|
+
EDUCATION_FEATURES: [
|
|
231
|
+
FEATURE_FLAGS.ENABLE_EDUCATION,
|
|
232
|
+
FEATURE_FLAGS.ENABLE_QUIZZES,
|
|
233
|
+
FEATURE_FLAGS.ENABLE_CHALLENGES,
|
|
234
|
+
FEATURE_FLAGS.ENABLE_AI,
|
|
235
|
+
] as const,
|
|
236
|
+
DOCUMENT_FEATURES: [
|
|
237
|
+
FEATURE_FLAGS.ENABLE_DOCS,
|
|
238
|
+
FEATURE_FLAGS.ENABLE_DRIVE,
|
|
239
|
+
FEATURE_FLAGS.ENABLE_SLIDES,
|
|
240
|
+
] as const,
|
|
241
|
+
WORKSPACE_FEATURES: [
|
|
242
|
+
FEATURE_FLAGS.ENABLE_AVATAR,
|
|
243
|
+
FEATURE_FLAGS.ENABLE_LOGO,
|
|
244
|
+
] as const,
|
|
245
|
+
} as const;
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Check if all AI features are enabled
|
|
249
|
+
* @param wsId - Workspace ID
|
|
250
|
+
* @param forceAdmin - Whether to use admin client (default: true for feature flags)
|
|
251
|
+
* @returns Promise<boolean>
|
|
252
|
+
*/
|
|
253
|
+
export async function areAIFeaturesEnabled(
|
|
254
|
+
wsId: string,
|
|
255
|
+
forceAdmin: boolean = true
|
|
256
|
+
): Promise<boolean> {
|
|
257
|
+
return areFeatureFlagsEnabled(wsId, FEATURE_GROUPS.AI_FEATURES, forceAdmin);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Check if all education features are enabled
|
|
262
|
+
* @param wsId - Workspace ID
|
|
263
|
+
* @param forceAdmin - Whether to use admin client (default: true for feature flags)
|
|
264
|
+
* @returns Promise<boolean>
|
|
265
|
+
*/
|
|
266
|
+
export async function areEducationFeaturesEnabled(
|
|
267
|
+
wsId: string,
|
|
268
|
+
forceAdmin: boolean = true
|
|
269
|
+
): Promise<boolean> {
|
|
270
|
+
return areFeatureFlagsEnabled(
|
|
271
|
+
wsId,
|
|
272
|
+
FEATURE_GROUPS.EDUCATION_FEATURES,
|
|
273
|
+
forceAdmin
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Check if all document features are enabled
|
|
279
|
+
* @param wsId - Workspace ID
|
|
280
|
+
* @param forceAdmin - Whether to use admin client (default: true for feature flags)
|
|
281
|
+
* @returns Promise<boolean>
|
|
282
|
+
*/
|
|
283
|
+
export async function areDocumentFeaturesEnabled(
|
|
284
|
+
wsId: string,
|
|
285
|
+
forceAdmin: boolean = true
|
|
286
|
+
): Promise<boolean> {
|
|
287
|
+
return areFeatureFlagsEnabled(
|
|
288
|
+
wsId,
|
|
289
|
+
FEATURE_GROUPS.DOCUMENT_FEATURES,
|
|
290
|
+
forceAdmin
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Check if any AI features are enabled
|
|
296
|
+
* @param wsId - Workspace ID
|
|
297
|
+
* @param forceAdmin - Whether to use admin client (default: true for feature flags)
|
|
298
|
+
* @returns Promise<boolean>
|
|
299
|
+
*/
|
|
300
|
+
export async function isAnyAIFeatureEnabled(
|
|
301
|
+
wsId: string,
|
|
302
|
+
forceAdmin: boolean = true
|
|
303
|
+
): Promise<boolean> {
|
|
304
|
+
return isAnyFeatureFlagEnabled(wsId, FEATURE_GROUPS.AI_FEATURES, forceAdmin);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Check if any education features are enabled
|
|
309
|
+
* @param wsId - Workspace ID
|
|
310
|
+
* @param forceAdmin - Whether to use admin client (default: true for feature flags)
|
|
311
|
+
* @returns Promise<boolean>
|
|
312
|
+
*/
|
|
313
|
+
export async function isAnyEducationFeatureEnabled(
|
|
314
|
+
wsId: string,
|
|
315
|
+
forceAdmin: boolean = true
|
|
316
|
+
): Promise<boolean> {
|
|
317
|
+
return isAnyFeatureFlagEnabled(
|
|
318
|
+
wsId,
|
|
319
|
+
FEATURE_GROUPS.EDUCATION_FEATURES,
|
|
320
|
+
forceAdmin
|
|
321
|
+
);
|
|
322
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const FEATURE_FLAGS = {
|
|
2
|
+
ENABLE_AI: 'ENABLE_AI',
|
|
3
|
+
ENABLE_EDUCATION: 'ENABLE_EDUCATION',
|
|
4
|
+
ENABLE_QUIZZES: 'ENABLE_QUIZZES',
|
|
5
|
+
ENABLE_CHALLENGES: 'ENABLE_CHALLENGES',
|
|
6
|
+
ENABLE_AI_ONLY: 'ENABLE_AI_ONLY',
|
|
7
|
+
ENABLE_CHAT: 'ENABLE_CHAT',
|
|
8
|
+
ENABLE_TASKS: 'ENABLE_TASKS',
|
|
9
|
+
ENABLE_DOCS: 'ENABLE_DOCS',
|
|
10
|
+
ENABLE_DRIVE: 'ENABLE_DRIVE',
|
|
11
|
+
ENABLE_SLIDES: 'ENABLE_SLIDES',
|
|
12
|
+
DISABLE_INVITE: 'DISABLE_INVITE',
|
|
13
|
+
PREVENT_WORKSPACE_DELETION: 'PREVENT_WORKSPACE_DELETION',
|
|
14
|
+
ENABLE_AVATAR: 'ENABLE_AVATAR',
|
|
15
|
+
ENABLE_LOGO: 'ENABLE_LOGO',
|
|
16
|
+
} as const;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { FeatureFlagMap } from './types';
|
|
2
|
+
|
|
3
|
+
export const DEFAULT_FEATURE_FLAGS: FeatureFlagMap = {
|
|
4
|
+
ENABLE_AI: false,
|
|
5
|
+
ENABLE_EDUCATION: false,
|
|
6
|
+
ENABLE_QUIZZES: false,
|
|
7
|
+
ENABLE_CHALLENGES: false,
|
|
8
|
+
ENABLE_AI_ONLY: false,
|
|
9
|
+
ENABLE_CHAT: false,
|
|
10
|
+
ENABLE_TASKS: false,
|
|
11
|
+
ENABLE_DOCS: false,
|
|
12
|
+
ENABLE_DRIVE: false,
|
|
13
|
+
ENABLE_SLIDES: false,
|
|
14
|
+
DISABLE_INVITE: false,
|
|
15
|
+
PREVENT_WORKSPACE_DELETION: false,
|
|
16
|
+
ENABLE_AVATAR: false,
|
|
17
|
+
ENABLE_LOGO: false,
|
|
18
|
+
};
|