@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,269 @@
|
|
|
1
|
+
const articles = new Set(['van', 'von', 'der', 'den', 'de', 'la', 'les', 'le']);
|
|
2
|
+
|
|
3
|
+
export const getInitials = (name?: string | null): string => {
|
|
4
|
+
if (!name) return '';
|
|
5
|
+
|
|
6
|
+
// Replace tabs and newlines with spaces and trim
|
|
7
|
+
const cleanName = name.replace(/[\t\n]/g, ' ').trim();
|
|
8
|
+
if (!cleanName) return '';
|
|
9
|
+
|
|
10
|
+
// Split by spaces, hyphens, and apostrophes
|
|
11
|
+
const nameParts = cleanName.split(/[\s\-']+/).filter(Boolean);
|
|
12
|
+
if (nameParts.length === 0) return '';
|
|
13
|
+
|
|
14
|
+
// For single word names
|
|
15
|
+
if (nameParts.length === 1) {
|
|
16
|
+
return nameParts[0]!.charAt(0).toUpperCase();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const firstPart = nameParts[0]!.toLowerCase();
|
|
20
|
+
|
|
21
|
+
// Special case: if the name starts with a capitalized prefix like "Van", use it
|
|
22
|
+
if (nameParts[0]![0]?.toUpperCase() === nameParts[0]![0]) {
|
|
23
|
+
return (
|
|
24
|
+
nameParts[0]![0]! + nameParts[nameParts.length - 1]![0]
|
|
25
|
+
).toUpperCase();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Otherwise look for the first non-prefix word and last word
|
|
29
|
+
if (articles.has(firstPart)) {
|
|
30
|
+
const lastPart = nameParts[nameParts.length - 1]!;
|
|
31
|
+
return (nameParts[1] ? nameParts[1][0] : '') + lastPart[0]!.toUpperCase();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Otherwise use first and last word
|
|
35
|
+
return (
|
|
36
|
+
(nameParts?.[0]?.[0] ?? '') + (nameParts?.[nameParts.length - 1]?.[0] ?? '')
|
|
37
|
+
).toUpperCase();
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export function getAvatarPlaceholder(name: string) {
|
|
41
|
+
return `https://ui-avatars.com/api/?name=${encodeURIComponent(name)}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Generates a consistent fun name from a user ID
|
|
46
|
+
*/
|
|
47
|
+
export function generateFunName({
|
|
48
|
+
id,
|
|
49
|
+
locale = 'en',
|
|
50
|
+
}: {
|
|
51
|
+
id: string;
|
|
52
|
+
locale?: 'en' | 'vi' | string;
|
|
53
|
+
}): string {
|
|
54
|
+
// List of adjectives and animals for fun names
|
|
55
|
+
const adjectives = {
|
|
56
|
+
en: [
|
|
57
|
+
'Happy',
|
|
58
|
+
'Silly',
|
|
59
|
+
'Clever',
|
|
60
|
+
'Brave',
|
|
61
|
+
'Curious',
|
|
62
|
+
'Playful',
|
|
63
|
+
'Friendly',
|
|
64
|
+
'Gentle',
|
|
65
|
+
'Jolly',
|
|
66
|
+
'Witty',
|
|
67
|
+
'Mighty',
|
|
68
|
+
'Dazzling',
|
|
69
|
+
'Adventurous',
|
|
70
|
+
'Bouncy',
|
|
71
|
+
'Cheerful',
|
|
72
|
+
'Daring',
|
|
73
|
+
'Energetic',
|
|
74
|
+
'Fuzzy',
|
|
75
|
+
'Goofy',
|
|
76
|
+
'Hilarious',
|
|
77
|
+
'Intelligent',
|
|
78
|
+
'Jumpy',
|
|
79
|
+
'Kind',
|
|
80
|
+
'Lively',
|
|
81
|
+
'Magical',
|
|
82
|
+
'Noble',
|
|
83
|
+
'Optimistic',
|
|
84
|
+
'Quirky',
|
|
85
|
+
'Radiant',
|
|
86
|
+
'Sassy',
|
|
87
|
+
'Talented',
|
|
88
|
+
'Unique',
|
|
89
|
+
'Vibrant',
|
|
90
|
+
'Whimsical',
|
|
91
|
+
'Zealous',
|
|
92
|
+
'Adorable',
|
|
93
|
+
],
|
|
94
|
+
vi: [
|
|
95
|
+
'Vui Vẻ', // Happy
|
|
96
|
+
'Ngốc Nghếch', // Silly
|
|
97
|
+
'Thông Minh', // Clever
|
|
98
|
+
'Dũng Cảm', // Brave
|
|
99
|
+
'Tò Mò', // Curious
|
|
100
|
+
'Tinh Nghịch', // Playful
|
|
101
|
+
'Thân Thiện', // Friendly
|
|
102
|
+
'Dịu Dàng', // Gentle
|
|
103
|
+
'Hạnh Phúc', // Jolly
|
|
104
|
+
'Hóm Hỉnh', // Witty
|
|
105
|
+
'Mạnh Mẽ', // Mighty
|
|
106
|
+
'Lấp Lánh', // Dazzling
|
|
107
|
+
'Phiêu Lưu', // Adventurous
|
|
108
|
+
'Nhảy Nhót', // Bouncy
|
|
109
|
+
'Vui Tươi', // Cheerful
|
|
110
|
+
'Táo Bạo', // Daring
|
|
111
|
+
'Năng Động', // Energetic
|
|
112
|
+
'Xù Xì', // Fuzzy
|
|
113
|
+
'Ngớ Ngẩn', // Goofy
|
|
114
|
+
'Buồn Cười', // Hilarious
|
|
115
|
+
'Thông Tuệ', // Intelligent
|
|
116
|
+
'Nhảy Nhót', // Jumpy
|
|
117
|
+
'Tốt Bụng', // Kind
|
|
118
|
+
'Sống Động', // Lively
|
|
119
|
+
'Kỳ Diệu', // Magical
|
|
120
|
+
'Cao Quý', // Noble
|
|
121
|
+
'Lạc Quan', // Optimistic
|
|
122
|
+
'Kỳ Lạ', // Quirky
|
|
123
|
+
'Rực Rỡ', // Radiant
|
|
124
|
+
'Bướng Bỉnh', // Sassy
|
|
125
|
+
'Tài Năng', // Talented
|
|
126
|
+
'Độc Đáo', // Unique
|
|
127
|
+
'Sôi Động', // Vibrant
|
|
128
|
+
'Kỳ Quặc', // Whimsical
|
|
129
|
+
'Nhiệt Tình', // Zealous
|
|
130
|
+
'Đáng Yêu', // Adorable
|
|
131
|
+
],
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const animals = {
|
|
135
|
+
en: [
|
|
136
|
+
{ name: 'Octopus', emoji: '🐙' },
|
|
137
|
+
{ name: 'Cat', emoji: '🐱' },
|
|
138
|
+
{ name: 'Penguin', emoji: '🐧' },
|
|
139
|
+
{ name: 'Fox', emoji: '🦊' },
|
|
140
|
+
{ name: 'Panda', emoji: '🐼' },
|
|
141
|
+
{ name: 'Dolphin', emoji: '🐬' },
|
|
142
|
+
{ name: 'Koala', emoji: '🐨' },
|
|
143
|
+
{ name: 'Owl', emoji: '🦉' },
|
|
144
|
+
{ name: 'Tiger', emoji: '🐯' },
|
|
145
|
+
{ name: 'Rabbit', emoji: '🐰' },
|
|
146
|
+
{ name: 'Monkey', emoji: '🐵' },
|
|
147
|
+
{ name: 'Wolf', emoji: '🐺' },
|
|
148
|
+
{ name: 'Alligator', emoji: '🐊' },
|
|
149
|
+
{ name: 'Beaver', emoji: '🦫' },
|
|
150
|
+
{ name: 'Chameleon', emoji: '🦎' },
|
|
151
|
+
{ name: 'Duck', emoji: '🦆' },
|
|
152
|
+
{ name: 'Elephant', emoji: '🐘' },
|
|
153
|
+
{ name: 'Flamingo', emoji: '🦩' },
|
|
154
|
+
{ name: 'Giraffe', emoji: '🦒' },
|
|
155
|
+
{ name: 'Hedgehog', emoji: '🦔' },
|
|
156
|
+
{ name: 'Iguana', emoji: '🦎' },
|
|
157
|
+
{ name: 'Jellyfish', emoji: '🪼' },
|
|
158
|
+
{ name: 'Kangaroo', emoji: '🦘' },
|
|
159
|
+
{ name: 'Lion', emoji: '🦁' },
|
|
160
|
+
{ name: 'Meerkat', emoji: '🦝' },
|
|
161
|
+
{ name: 'Narwhal', emoji: '🦭' },
|
|
162
|
+
{ name: 'Otter', emoji: '🦦' },
|
|
163
|
+
{ name: 'Peacock', emoji: '🦚' },
|
|
164
|
+
{ name: 'Quokka', emoji: '🦘' },
|
|
165
|
+
{ name: 'Raccoon', emoji: '🦝' },
|
|
166
|
+
{ name: 'Sloth', emoji: '🦥' },
|
|
167
|
+
{ name: 'Turtle', emoji: '🐢' },
|
|
168
|
+
{ name: 'Unicorn', emoji: '🦄' },
|
|
169
|
+
{ name: 'Vulture', emoji: '🦅' },
|
|
170
|
+
{ name: 'Walrus', emoji: '🦭' },
|
|
171
|
+
{ name: 'Yak', emoji: '🐃' },
|
|
172
|
+
{ name: 'Zebra', emoji: '🦓' },
|
|
173
|
+
{ name: 'Badger', emoji: '🦡' },
|
|
174
|
+
{ name: 'Cheetah', emoji: '🐆' },
|
|
175
|
+
{ name: 'Dingo', emoji: '🐕' },
|
|
176
|
+
{ name: 'Ferret', emoji: '🦡' },
|
|
177
|
+
{ name: 'Gorilla', emoji: '🦍' },
|
|
178
|
+
],
|
|
179
|
+
vi: [
|
|
180
|
+
{ name: 'Bạch Tuộc', emoji: '🐙' },
|
|
181
|
+
{ name: 'Mèo', emoji: '🐱' },
|
|
182
|
+
{ name: 'Chim Cánh Cụt', emoji: '🐧' },
|
|
183
|
+
{ name: 'Cáo', emoji: '🦊' },
|
|
184
|
+
{ name: 'Gấu Trúc', emoji: '🐼' },
|
|
185
|
+
{ name: 'Cá Heo', emoji: '🐬' },
|
|
186
|
+
{ name: 'Gấu Koala', emoji: '🐨' },
|
|
187
|
+
{ name: 'Cú Mèo', emoji: '🦉' },
|
|
188
|
+
{ name: 'Hổ', emoji: '🐯' },
|
|
189
|
+
{ name: 'Thỏ', emoji: '🐰' },
|
|
190
|
+
{ name: 'Khỉ', emoji: '🐵' },
|
|
191
|
+
{ name: 'Sói', emoji: '🐺' },
|
|
192
|
+
{ name: 'Cá Sấu', emoji: '🐊' },
|
|
193
|
+
{ name: 'Hải Ly', emoji: '🦫' },
|
|
194
|
+
{ name: 'Tắc Kè', emoji: '🦎' },
|
|
195
|
+
{ name: 'Vịt', emoji: '🦆' },
|
|
196
|
+
{ name: 'Voi', emoji: '🐘' },
|
|
197
|
+
{ name: 'Hồng Hạc', emoji: '🦩' },
|
|
198
|
+
{ name: 'Hươu Cao Cổ', emoji: '🦒' },
|
|
199
|
+
{ name: 'Nhím', emoji: '🦔' },
|
|
200
|
+
{ name: 'Kỳ Đà', emoji: '🦎' },
|
|
201
|
+
{ name: 'Sứa', emoji: '🪼' },
|
|
202
|
+
{ name: 'Kangaroo', emoji: '🦘' },
|
|
203
|
+
{ name: 'Sư Tử', emoji: '🦁' },
|
|
204
|
+
{ name: 'Cầy Meerkat', emoji: '🦝' },
|
|
205
|
+
{ name: 'Kỳ Lân Biển', emoji: '🦭' },
|
|
206
|
+
{ name: 'Rái Cá', emoji: '🦦' },
|
|
207
|
+
{ name: 'Công', emoji: '🦚' },
|
|
208
|
+
{ name: 'Quokka', emoji: '🦘' },
|
|
209
|
+
{ name: 'Gấu Mèo', emoji: '🦝' },
|
|
210
|
+
{ name: 'Lười', emoji: '🦥' },
|
|
211
|
+
{ name: 'Rùa', emoji: '🐢' },
|
|
212
|
+
{ name: 'Kỳ Lân', emoji: '🦄' },
|
|
213
|
+
{ name: 'Kền Kền', emoji: '🦅' },
|
|
214
|
+
{ name: 'Hải Mã', emoji: '🦭' },
|
|
215
|
+
{ name: 'Bò Yak', emoji: '🐃' },
|
|
216
|
+
{ name: 'Ngựa Vằn', emoji: '🦓' },
|
|
217
|
+
{ name: 'Lửng', emoji: '🦡' },
|
|
218
|
+
{ name: 'Báo', emoji: '🐆' },
|
|
219
|
+
{ name: 'Chó Dingo', emoji: '🐕' },
|
|
220
|
+
{ name: 'Chồn', emoji: '🦡' },
|
|
221
|
+
{ name: 'Khỉ Đột', emoji: '🦍' },
|
|
222
|
+
],
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
// Improved hash function for more randomness
|
|
226
|
+
const hash = (str: string): number => {
|
|
227
|
+
let h1 = 0xdeadbeef;
|
|
228
|
+
let h2 = 0x41c6ce57;
|
|
229
|
+
|
|
230
|
+
for (let i = 0; i < str.length; i++) {
|
|
231
|
+
const char = str.charCodeAt(i);
|
|
232
|
+
h1 = Math.imul(h1 ^ char, 2654435761);
|
|
233
|
+
h2 = Math.imul(h2 ^ char, 1597334677);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
|
|
237
|
+
h1 = Math.imul(h1 ^ (h1 >>> 13), 3266489909);
|
|
238
|
+
|
|
239
|
+
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
|
|
240
|
+
h2 = Math.imul(h2 ^ (h2 >>> 13), 3266489909);
|
|
241
|
+
|
|
242
|
+
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
// Use the appropriate language lists
|
|
246
|
+
const selectedLocale = locale in adjectives ? locale : 'en';
|
|
247
|
+
const adjectivesList =
|
|
248
|
+
adjectives?.[selectedLocale as keyof typeof adjectives];
|
|
249
|
+
const animalsList = animals?.[selectedLocale as keyof typeof animals];
|
|
250
|
+
|
|
251
|
+
// Generate consistent indices
|
|
252
|
+
const combinedHash = hash(id);
|
|
253
|
+
const adjIndex = combinedHash % adjectivesList.length;
|
|
254
|
+
const animalIndex = (combinedHash * 31) % animalsList.length;
|
|
255
|
+
|
|
256
|
+
const adjective =
|
|
257
|
+
adjectivesList[adjIndex] || (locale === 'en' ? 'Happy' : 'Vui');
|
|
258
|
+
const animal = animalsList[animalIndex];
|
|
259
|
+
|
|
260
|
+
if (!animal) {
|
|
261
|
+
return locale === 'en'
|
|
262
|
+
? `${adjective} Mystery ❓`
|
|
263
|
+
: `${adjective} Kỳ Bí ❓`;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return locale === 'vi'
|
|
267
|
+
? `${animal.name} ${adjective} ${animal.emoji}`
|
|
268
|
+
: `${adjective} ${animal.name} ${animal.emoji}`;
|
|
269
|
+
}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
createTuturuuuNextConfig,
|
|
4
|
+
isTuturuuuNextCacheComponentsEnabled,
|
|
5
|
+
isTuturuuuNextReactCompilerEnabled,
|
|
6
|
+
resolveTuturuuuWebAppUrl,
|
|
7
|
+
TUTURUUU_NEXT_IMAGE_REMOTE_PATTERNS,
|
|
8
|
+
TUTURUUU_NEXT_OPTIMIZE_PACKAGE_IMPORTS,
|
|
9
|
+
trimTrailingSlashes,
|
|
10
|
+
} from './next-config';
|
|
11
|
+
|
|
12
|
+
describe('createTuturuuuNextConfig', () => {
|
|
13
|
+
it('applies shared local development defaults', () => {
|
|
14
|
+
const config = createTuturuuuNextConfig();
|
|
15
|
+
|
|
16
|
+
expect(config.allowedDevOrigins).toContain('tuturuuu.localhost');
|
|
17
|
+
expect(config.cacheComponents).toBe(true);
|
|
18
|
+
expect(config.images?.remotePatterns).toEqual(
|
|
19
|
+
TUTURUUU_NEXT_IMAGE_REMOTE_PATTERNS
|
|
20
|
+
);
|
|
21
|
+
expect(config.poweredByHeader).toBe(false);
|
|
22
|
+
expect(config.reactStrictMode).toBe(true);
|
|
23
|
+
expect(config.typescript?.ignoreBuildErrors).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('allows the exact worktree-prefixed Portless host for Next dev assets', () => {
|
|
27
|
+
const originalPortlessUrl = process.env.PORTLESS_URL;
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
process.env.PORTLESS_URL =
|
|
31
|
+
'https://zalo-qr-chat-setup.chat.tuturuuu.localhost';
|
|
32
|
+
|
|
33
|
+
const config = createTuturuuuNextConfig();
|
|
34
|
+
|
|
35
|
+
expect(config.allowedDevOrigins).toContain(
|
|
36
|
+
'zalo-qr-chat-setup.chat.tuturuuu.localhost'
|
|
37
|
+
);
|
|
38
|
+
} finally {
|
|
39
|
+
if (originalPortlessUrl === undefined) {
|
|
40
|
+
delete process.env.PORTLESS_URL;
|
|
41
|
+
} else {
|
|
42
|
+
process.env.PORTLESS_URL = originalPortlessUrl;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('dedupes optimized package imports while preserving app additions', () => {
|
|
48
|
+
const config = createTuturuuuNextConfig({
|
|
49
|
+
experimental: {
|
|
50
|
+
optimizePackageImports: ['lucide-react', '@tuturuuu/ui'],
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
expect(config.experimental?.optimizePackageImports).toEqual([
|
|
55
|
+
...TUTURUUU_NEXT_OPTIMIZE_PACKAGE_IMPORTS,
|
|
56
|
+
'@tuturuuu/ui',
|
|
57
|
+
]);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('dedupes image remote patterns while preserving app additions', () => {
|
|
61
|
+
const config = createTuturuuuNextConfig({
|
|
62
|
+
images: {
|
|
63
|
+
remotePatterns: [
|
|
64
|
+
...TUTURUUU_NEXT_IMAGE_REMOTE_PATTERNS,
|
|
65
|
+
{
|
|
66
|
+
protocol: 'https',
|
|
67
|
+
hostname: 'models.dev',
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
expect(config.images?.remotePatterns).toEqual([
|
|
74
|
+
...TUTURUUU_NEXT_IMAGE_REMOTE_PATTERNS,
|
|
75
|
+
{
|
|
76
|
+
protocol: 'https',
|
|
77
|
+
hostname: 'models.dev',
|
|
78
|
+
},
|
|
79
|
+
]);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('preserves app-specific config overrides', () => {
|
|
83
|
+
const config = createTuturuuuNextConfig({
|
|
84
|
+
cacheComponents: false,
|
|
85
|
+
reactCompiler: true,
|
|
86
|
+
transpilePackages: ['@tuturuuu/ui'],
|
|
87
|
+
experimental: {
|
|
88
|
+
cpus: 2,
|
|
89
|
+
},
|
|
90
|
+
typescript: {
|
|
91
|
+
ignoreBuildErrors: false,
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
expect(config.cacheComponents).toBe(false);
|
|
96
|
+
expect(config.reactCompiler).toBe(true);
|
|
97
|
+
expect(config.transpilePackages).toEqual(['@tuturuuu/ui']);
|
|
98
|
+
expect(config.experimental?.cpus).toBe(2);
|
|
99
|
+
expect(config.typescript?.ignoreBuildErrors).toBe(false);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('isTuturuuuNextCacheComponentsEnabled', () => {
|
|
104
|
+
it('keeps cache components enabled by default', () => {
|
|
105
|
+
expect(isTuturuuuNextCacheComponentsEnabled({})).toBe(true);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('honors explicit environment overrides', () => {
|
|
109
|
+
expect(
|
|
110
|
+
isTuturuuuNextCacheComponentsEnabled({
|
|
111
|
+
TUTURUUU_NEXT_CACHE_COMPONENTS: '0',
|
|
112
|
+
})
|
|
113
|
+
).toBe(false);
|
|
114
|
+
expect(
|
|
115
|
+
isTuturuuuNextCacheComponentsEnabled({
|
|
116
|
+
TUTURUUU_NEXT_CACHE_COMPONENTS: '1',
|
|
117
|
+
})
|
|
118
|
+
).toBe(true);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe('isTuturuuuNextReactCompilerEnabled', () => {
|
|
123
|
+
it('disables React Compiler by default for next dev', () => {
|
|
124
|
+
expect(
|
|
125
|
+
isTuturuuuNextReactCompilerEnabled({ NODE_ENV: 'development' })
|
|
126
|
+
).toBe(false);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('keeps React Compiler enabled by default outside next dev', () => {
|
|
130
|
+
expect(isTuturuuuNextReactCompilerEnabled({ NODE_ENV: 'production' })).toBe(
|
|
131
|
+
true
|
|
132
|
+
);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('honors explicit environment overrides', () => {
|
|
136
|
+
expect(
|
|
137
|
+
isTuturuuuNextReactCompilerEnabled({
|
|
138
|
+
NODE_ENV: 'production',
|
|
139
|
+
TUTURUUU_NEXT_REACT_COMPILER: '0',
|
|
140
|
+
})
|
|
141
|
+
).toBe(false);
|
|
142
|
+
expect(
|
|
143
|
+
isTuturuuuNextReactCompilerEnabled({
|
|
144
|
+
NODE_ENV: 'development',
|
|
145
|
+
TUTURUUU_NEXT_REACT_COMPILER: '1',
|
|
146
|
+
})
|
|
147
|
+
).toBe(true);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe('resolveTuturuuuWebAppUrl', () => {
|
|
152
|
+
it('trims configured web app origins', () => {
|
|
153
|
+
expect(
|
|
154
|
+
resolveTuturuuuWebAppUrl({
|
|
155
|
+
env: {
|
|
156
|
+
INTERNAL_WEB_API_ORIGIN: 'https://example.com///',
|
|
157
|
+
NODE_ENV: 'development',
|
|
158
|
+
},
|
|
159
|
+
})
|
|
160
|
+
).toBe('https://example.com');
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('uses the local Portless platform app in development', () => {
|
|
164
|
+
expect(
|
|
165
|
+
resolveTuturuuuWebAppUrl({
|
|
166
|
+
centralPort: 7903,
|
|
167
|
+
env: {
|
|
168
|
+
NODE_ENV: 'development',
|
|
169
|
+
},
|
|
170
|
+
})
|
|
171
|
+
).toBe('https://tuturuuu.localhost');
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('allows a custom local fallback URL', () => {
|
|
175
|
+
expect(
|
|
176
|
+
resolveTuturuuuWebAppUrl({
|
|
177
|
+
env: {
|
|
178
|
+
NODE_ENV: 'development',
|
|
179
|
+
},
|
|
180
|
+
localFallbackUrl: 'https://tuturuuu.localhost/',
|
|
181
|
+
})
|
|
182
|
+
).toBe('https://tuturuuu.localhost');
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('uses the production platform app in deployed environments', () => {
|
|
186
|
+
expect(
|
|
187
|
+
resolveTuturuuuWebAppUrl({
|
|
188
|
+
env: {
|
|
189
|
+
VERCEL_ENV: 'production',
|
|
190
|
+
},
|
|
191
|
+
})
|
|
192
|
+
).toBe('https://tuturuuu.com');
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('ignores known satellite NEXT_PUBLIC_APP_URL values when resolving the Web app URL', () => {
|
|
196
|
+
expect(
|
|
197
|
+
resolveTuturuuuWebAppUrl({
|
|
198
|
+
env: {
|
|
199
|
+
NEXT_PUBLIC_APP_URL: 'https://chat.tuturuuu.com',
|
|
200
|
+
NODE_ENV: 'production',
|
|
201
|
+
},
|
|
202
|
+
})
|
|
203
|
+
).toBe('https://tuturuuu.com');
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('keeps explicit Web origins authoritative over satellite app URLs', () => {
|
|
207
|
+
expect(
|
|
208
|
+
resolveTuturuuuWebAppUrl({
|
|
209
|
+
env: {
|
|
210
|
+
NEXT_PUBLIC_APP_URL: 'https://chat.tuturuuu.com',
|
|
211
|
+
WEB_APP_URL: 'https://web.internal.example.com/',
|
|
212
|
+
},
|
|
213
|
+
})
|
|
214
|
+
).toBe('https://web.internal.example.com');
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('allows platform NEXT_PUBLIC_APP_URL values as Web app URLs', () => {
|
|
218
|
+
expect(
|
|
219
|
+
resolveTuturuuuWebAppUrl({
|
|
220
|
+
env: {
|
|
221
|
+
NEXT_PUBLIC_APP_URL: 'https://tuturuuu.com/',
|
|
222
|
+
},
|
|
223
|
+
})
|
|
224
|
+
).toBe('https://tuturuuu.com');
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
describe('trimTrailingSlashes', () => {
|
|
229
|
+
it('removes only trailing slash characters', () => {
|
|
230
|
+
expect(trimTrailingSlashes('https://example.com/path///')).toBe(
|
|
231
|
+
'https://example.com/path'
|
|
232
|
+
);
|
|
233
|
+
});
|
|
234
|
+
});
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import type { NextConfig } from 'next';
|
|
2
|
+
import { resolveInternalAppUrl } from './app-url';
|
|
3
|
+
import { getLocalInternalAppUrl } from './internal-domains';
|
|
4
|
+
import { getTuturuuuPortlessAllowedDevOrigins } from './portless';
|
|
5
|
+
|
|
6
|
+
type Environment = Record<string, string | undefined>;
|
|
7
|
+
|
|
8
|
+
export const TUTURUUU_NEXT_OPTIMIZE_PACKAGE_IMPORTS = [
|
|
9
|
+
'@lucide/lab',
|
|
10
|
+
'@tuturuuu/icons',
|
|
11
|
+
'@tuturuuu/icons/lab',
|
|
12
|
+
'@tuturuuu/icons/lucide',
|
|
13
|
+
'lucide-react',
|
|
14
|
+
] as const;
|
|
15
|
+
|
|
16
|
+
type NextImageConfig = NonNullable<NextConfig['images']>;
|
|
17
|
+
type NextImageRemotePattern = NonNullable<
|
|
18
|
+
NextImageConfig['remotePatterns']
|
|
19
|
+
>[number];
|
|
20
|
+
|
|
21
|
+
export const TUTURUUU_NEXT_IMAGE_REMOTE_PATTERNS = [
|
|
22
|
+
{
|
|
23
|
+
protocol: 'http',
|
|
24
|
+
hostname: 'localhost',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
protocol: 'http',
|
|
28
|
+
hostname: '127.0.0.1',
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
protocol: 'https',
|
|
32
|
+
hostname: '**.supabase.co',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
protocol: 'https',
|
|
36
|
+
hostname: 'avatars.githubusercontent.com',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
protocol: 'https',
|
|
40
|
+
hostname: 'tuturuuu.com',
|
|
41
|
+
},
|
|
42
|
+
] satisfies NextImageRemotePattern[];
|
|
43
|
+
|
|
44
|
+
const TRUTHY_ENV_VALUES = new Set(['1', 'true', 'yes', 'on']);
|
|
45
|
+
const FALSY_ENV_VALUES = new Set(['0', 'false', 'no', 'off']);
|
|
46
|
+
|
|
47
|
+
function mergeStringArrays(
|
|
48
|
+
first: readonly string[] | undefined,
|
|
49
|
+
second: readonly string[] | undefined
|
|
50
|
+
) {
|
|
51
|
+
return Array.from(new Set([...(first ?? []), ...(second ?? [])]));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function getRemotePatternKey(pattern: NextImageRemotePattern) {
|
|
55
|
+
return pattern instanceof URL ? pattern.toString() : JSON.stringify(pattern);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function mergeRemotePatterns(
|
|
59
|
+
first: readonly NextImageRemotePattern[] | undefined,
|
|
60
|
+
second: readonly NextImageRemotePattern[] | undefined
|
|
61
|
+
) {
|
|
62
|
+
const merged = new Map<string, NextImageRemotePattern>();
|
|
63
|
+
|
|
64
|
+
for (const pattern of [...(first ?? []), ...(second ?? [])]) {
|
|
65
|
+
merged.set(getRemotePatternKey(pattern), pattern);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return Array.from(merged.values());
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function readBooleanEnvOverride(value: string | undefined) {
|
|
72
|
+
const normalizedValue = value?.trim().toLowerCase();
|
|
73
|
+
|
|
74
|
+
if (!normalizedValue) {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (TRUTHY_ENV_VALUES.has(normalizedValue)) {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (FALSY_ENV_VALUES.has(normalizedValue)) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function isTuturuuuNextReactCompilerEnabled(
|
|
90
|
+
env: Environment = process.env
|
|
91
|
+
) {
|
|
92
|
+
const override = readBooleanEnvOverride(env.TUTURUUU_NEXT_REACT_COMPILER);
|
|
93
|
+
|
|
94
|
+
if (override !== undefined) {
|
|
95
|
+
return override;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return env.NODE_ENV !== 'development';
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function isTuturuuuNextCacheComponentsEnabled(
|
|
102
|
+
env: Environment = process.env
|
|
103
|
+
) {
|
|
104
|
+
const override = readBooleanEnvOverride(env.TUTURUUU_NEXT_CACHE_COMPONENTS);
|
|
105
|
+
|
|
106
|
+
return override ?? true;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function createTuturuuuNextConfig(config: NextConfig = {}): NextConfig {
|
|
110
|
+
const experimentalConfig = config.experimental ?? {};
|
|
111
|
+
const imageConfig = config.images ?? {};
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
reactCompiler: isTuturuuuNextReactCompilerEnabled(),
|
|
115
|
+
reactStrictMode: true,
|
|
116
|
+
poweredByHeader: false,
|
|
117
|
+
...config,
|
|
118
|
+
cacheComponents:
|
|
119
|
+
config.cacheComponents ?? isTuturuuuNextCacheComponentsEnabled(),
|
|
120
|
+
allowedDevOrigins: mergeStringArrays(
|
|
121
|
+
getTuturuuuPortlessAllowedDevOrigins(),
|
|
122
|
+
config.allowedDevOrigins
|
|
123
|
+
),
|
|
124
|
+
images: {
|
|
125
|
+
...imageConfig,
|
|
126
|
+
remotePatterns: mergeRemotePatterns(
|
|
127
|
+
TUTURUUU_NEXT_IMAGE_REMOTE_PATTERNS,
|
|
128
|
+
imageConfig.remotePatterns
|
|
129
|
+
),
|
|
130
|
+
},
|
|
131
|
+
typescript: {
|
|
132
|
+
ignoreBuildErrors: true,
|
|
133
|
+
...config.typescript,
|
|
134
|
+
},
|
|
135
|
+
experimental: {
|
|
136
|
+
...experimentalConfig,
|
|
137
|
+
optimizePackageImports: mergeStringArrays(
|
|
138
|
+
TUTURUUU_NEXT_OPTIMIZE_PACKAGE_IMPORTS,
|
|
139
|
+
experimentalConfig.optimizePackageImports
|
|
140
|
+
),
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function trimTrailingSlashes(value: string) {
|
|
146
|
+
let end = value.length;
|
|
147
|
+
|
|
148
|
+
while (end > 0 && value.charCodeAt(end - 1) === 47) {
|
|
149
|
+
end -= 1;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return end === value.length ? value : value.slice(0, end);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function isTuturuuuNextDeployedEnvironment(
|
|
156
|
+
env: Environment = process.env
|
|
157
|
+
) {
|
|
158
|
+
return (
|
|
159
|
+
env.VERCEL === '1' ||
|
|
160
|
+
env.VERCEL_ENV === 'preview' ||
|
|
161
|
+
env.VERCEL_ENV === 'production' ||
|
|
162
|
+
env.NODE_ENV === 'production'
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function resolveNextPublicPlatformAppUrl(env: Environment) {
|
|
167
|
+
if (!env.NEXT_PUBLIC_APP_URL) {
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const resolvedUrl = resolveInternalAppUrl({
|
|
172
|
+
appName: 'platform',
|
|
173
|
+
candidates: [env.NEXT_PUBLIC_APP_URL],
|
|
174
|
+
fallback: '',
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
return resolvedUrl || undefined;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function resolveTuturuuuWebAppUrl({
|
|
181
|
+
centralPort,
|
|
182
|
+
env = process.env,
|
|
183
|
+
localFallbackUrl,
|
|
184
|
+
productionUrl = 'https://tuturuuu.com',
|
|
185
|
+
}: {
|
|
186
|
+
centralPort?: number | string;
|
|
187
|
+
env?: Environment;
|
|
188
|
+
localFallbackUrl?: string;
|
|
189
|
+
productionUrl?: string;
|
|
190
|
+
} = {}) {
|
|
191
|
+
const localCentralPort = centralPort ?? env.CENTRAL_PORT ?? 7803;
|
|
192
|
+
const localUrl =
|
|
193
|
+
localFallbackUrl ??
|
|
194
|
+
getLocalInternalAppUrl('platform', `http://localhost:${localCentralPort}`);
|
|
195
|
+
|
|
196
|
+
return trimTrailingSlashes(
|
|
197
|
+
env.INTERNAL_WEB_API_ORIGIN ||
|
|
198
|
+
env.WEB_APP_URL ||
|
|
199
|
+
env.NEXT_PUBLIC_WEB_APP_URL ||
|
|
200
|
+
resolveNextPublicPlatformAppUrl(env) ||
|
|
201
|
+
(isTuturuuuNextDeployedEnvironment(env) ? productionUrl : localUrl)
|
|
202
|
+
);
|
|
203
|
+
}
|