@tuturuuu/utils 0.0.2 → 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.
Files changed (186) hide show
  1. package/CHANGELOG.md +305 -0
  2. package/biome.json +5 -0
  3. package/jsr.json +8 -8
  4. package/package.json +63 -32
  5. package/src/__tests__/ai-temp-auth.test.ts +309 -0
  6. package/src/__tests__/api-proxy-guard.test.ts +1451 -0
  7. package/src/__tests__/app-url.test.ts +270 -0
  8. package/src/__tests__/avatar-url.test.ts +97 -0
  9. package/src/__tests__/color-helper.test.ts +179 -0
  10. package/src/__tests__/constants.test.ts +351 -0
  11. package/src/__tests__/crypto.test.ts +107 -0
  12. package/src/__tests__/date-helper.test.ts +408 -0
  13. package/src/__tests__/fixtures/task-description-full-featured.json +456 -0
  14. package/src/__tests__/format.test.ts +317 -0
  15. package/src/__tests__/html-sanitizer.test.ts +360 -0
  16. package/src/__tests__/interest-calculator.test.ts +336 -0
  17. package/src/__tests__/interest-detector.test.ts +222 -0
  18. package/src/__tests__/label-colors.test.ts +241 -0
  19. package/src/__tests__/name-helper.test.ts +158 -0
  20. package/src/__tests__/node-diff.test.ts +576 -0
  21. package/src/__tests__/notification-service.test.ts +210 -0
  22. package/src/__tests__/onboarding-helper.test.ts +331 -0
  23. package/src/__tests__/path-helper.test.ts +152 -0
  24. package/src/__tests__/permissions.test.tsx +81 -0
  25. package/src/__tests__/request-emoji-limit.test.ts +172 -0
  26. package/src/__tests__/search-helper.test.ts +51 -0
  27. package/src/__tests__/storage-display-name.test.ts +37 -0
  28. package/src/__tests__/storage-path.test.ts +238 -0
  29. package/src/__tests__/tag-utils.test.ts +205 -0
  30. package/src/__tests__/task-description-yjs-state.test.ts +581 -0
  31. package/src/__tests__/task-helper-board-api-routing.test.ts +94 -0
  32. package/src/__tests__/task-helper-create-task.test.ts +129 -0
  33. package/src/__tests__/task-helpers.test.ts +464 -0
  34. package/src/__tests__/task-overrides.test.ts +305 -0
  35. package/src/__tests__/task-reorder-cache.test.ts +74 -0
  36. package/src/__tests__/task-sort-keys.test.ts +36 -0
  37. package/src/__tests__/task-transformers.test.ts +62 -0
  38. package/src/__tests__/text-helper.test.ts +776 -0
  39. package/src/__tests__/time-helper.test.ts +70 -0
  40. package/src/__tests__/time-tracker-period.test.ts +55 -0
  41. package/src/__tests__/timezone.test.ts +117 -0
  42. package/src/__tests__/upstash-rest.test.ts +77 -0
  43. package/src/__tests__/uuid-helper.test.ts +133 -0
  44. package/src/__tests__/workspace-helper.test.ts +859 -0
  45. package/src/__tests__/workspace-limits.test.ts +255 -0
  46. package/src/__tests__/yjs-helper.test.ts +581 -0
  47. package/src/abuse-protection/__tests__/backend-rate-limit.test.ts +113 -0
  48. package/src/abuse-protection/__tests__/edge.test.ts +136 -0
  49. package/src/abuse-protection/__tests__/index.test.ts +562 -0
  50. package/src/abuse-protection/__tests__/reputation.test.ts +192 -0
  51. package/src/abuse-protection/backend-rate-limit.ts +44 -0
  52. package/src/abuse-protection/constants.ts +117 -0
  53. package/src/abuse-protection/edge.ts +223 -0
  54. package/src/abuse-protection/index.ts +1545 -0
  55. package/src/abuse-protection/reputation.ts +587 -0
  56. package/src/abuse-protection/types.ts +97 -0
  57. package/src/abuse-protection/user-agent.ts +124 -0
  58. package/src/abuse-protection/user-suspension.ts +231 -0
  59. package/src/ai-temp-auth.ts +315 -0
  60. package/src/api-proxy-guard.ts +965 -0
  61. package/src/app-url.ts +96 -0
  62. package/src/avatar-url.ts +64 -0
  63. package/src/break-duration.ts +84 -0
  64. package/src/calendar-auth-token.test.ts +37 -0
  65. package/src/calendar-auth-token.ts +19 -0
  66. package/src/calendar-sync-coordination.md +197 -0
  67. package/src/calendar-utils.test.ts +169 -0
  68. package/src/calendar-utils.ts +91 -0
  69. package/src/color-helper.ts +110 -0
  70. package/src/common/nextjs.tsx +99 -0
  71. package/src/common/scan.tsx +15 -0
  72. package/src/configs/reports.ts +160 -0
  73. package/src/constants.ts +85 -0
  74. package/src/crypto.ts +21 -0
  75. package/src/currencies.ts +97 -0
  76. package/src/date-helper.ts +313 -0
  77. package/src/editor/convert-to-task.ts +264 -0
  78. package/src/editor/index.ts +5 -0
  79. package/src/email/__tests__/client.test.ts +141 -0
  80. package/src/email/__tests__/validation.test.ts +46 -0
  81. package/src/email/client.ts +92 -0
  82. package/src/email/server.ts +128 -0
  83. package/src/email/validation.ts +11 -0
  84. package/src/encryption/__tests__/calendar-events.test.ts +411 -0
  85. package/src/encryption/__tests__/configuration.test.ts +114 -0
  86. package/src/encryption/__tests__/field-encryption.test.ts +232 -0
  87. package/src/encryption/__tests__/key-generation.test.ts +30 -0
  88. package/src/encryption/__tests__/performance-edge-cases.test.ts +187 -0
  89. package/src/encryption/__tests__/test-helpers.ts +22 -0
  90. package/src/encryption/__tests__/workspace-key-encryption.test.ts +129 -0
  91. package/src/encryption/encryption-service.ts +343 -0
  92. package/src/encryption/index.ts +25 -0
  93. package/src/encryption/types.ts +57 -0
  94. package/src/exchange-rates.ts +49 -0
  95. package/src/feature-flags/__tests__/feature-flags.test.ts +302 -0
  96. package/src/feature-flags/core.ts +322 -0
  97. package/src/feature-flags/data.ts +16 -0
  98. package/src/feature-flags/default.ts +18 -0
  99. package/src/feature-flags/index.ts +7 -0
  100. package/src/feature-flags/requestable-features.ts +79 -0
  101. package/src/feature-flags/types.ts +4 -0
  102. package/src/fetcher.ts +2 -0
  103. package/src/finance/index.ts +4 -0
  104. package/src/finance/interest-calculator.ts +456 -0
  105. package/src/finance/interest-detector.ts +141 -0
  106. package/src/finance/transform-invoice-results.ts +219 -0
  107. package/src/finance/wallet-permissions.test.ts +169 -0
  108. package/src/finance/wallet-permissions.ts +82 -0
  109. package/src/format.ts +122 -3
  110. package/src/generated/platform-build-metadata.ts +11 -0
  111. package/src/hooks/use-platform.ts +64 -0
  112. package/src/html-sanitizer.ts +155 -0
  113. package/src/internal-domains.ts +497 -0
  114. package/src/keyboard-preset.ts +109 -0
  115. package/src/label-colors.ts +213 -0
  116. package/src/launchable-apps.test.ts +126 -0
  117. package/src/launchable-apps.ts +490 -0
  118. package/src/name-helper.ts +269 -0
  119. package/src/next-config.test.ts +234 -0
  120. package/src/next-config.ts +203 -0
  121. package/src/node-diff.ts +375 -0
  122. package/src/notification-service.ts +379 -0
  123. package/src/nova/scores/__tests__/calculate.test.ts +254 -0
  124. package/src/nova/scores/calculate.ts +132 -0
  125. package/src/nova/submissions/check-permission.ts +132 -0
  126. package/src/onboarding-helper.ts +213 -0
  127. package/src/path-helper.ts +93 -0
  128. package/src/permissions.tsx +1170 -0
  129. package/src/plan-helpers.test.ts +188 -0
  130. package/src/plan-helpers.ts +80 -0
  131. package/src/platform-release.test.ts +74 -0
  132. package/src/platform-release.ts +155 -0
  133. package/src/portless.ts +124 -0
  134. package/src/priority-styles.ts +42 -0
  135. package/src/request-emoji-limit.ts +335 -0
  136. package/src/search-helper.ts +18 -0
  137. package/src/search.test.ts +89 -0
  138. package/src/search.ts +355 -0
  139. package/src/storage-display-name.ts +30 -0
  140. package/src/storage-path.ts +147 -0
  141. package/src/tag-utils.ts +159 -0
  142. package/src/task/reorder.ts +245 -0
  143. package/src/task/transformers.ts +149 -0
  144. package/src/task-date-timezone.ts +133 -0
  145. package/src/task-description-content.ts +240 -0
  146. package/src/task-helper/board.ts +193 -0
  147. package/src/task-helper/bulk-actions.ts +564 -0
  148. package/src/task-helper/personal-external-staging.ts +21 -0
  149. package/src/task-helper/recycle-bin.ts +202 -0
  150. package/src/task-helper/relationships.ts +346 -0
  151. package/src/task-helper/shared.ts +109 -0
  152. package/src/task-helper/sort-keys.ts +337 -0
  153. package/src/task-helper/task-hooks-basic.ts +342 -0
  154. package/src/task-helper/task-hooks-move.ts +264 -0
  155. package/src/task-helper/task-operations.ts +278 -0
  156. package/src/task-helper.ts +12 -0
  157. package/src/task-helpers.ts +241 -0
  158. package/src/task-list-status.ts +62 -0
  159. package/src/task-overrides.ts +82 -0
  160. package/src/task-snapshot.ts +374 -0
  161. package/src/text-diff.ts +81 -0
  162. package/src/text-helper.ts +537 -0
  163. package/src/time-helper.ts +63 -0
  164. package/src/time-tracker-period.ts +73 -0
  165. package/src/timeblock-helper.ts +418 -0
  166. package/src/timezone.ts +190 -0
  167. package/src/timezones.json +1271 -0
  168. package/src/upstash-rest.ts +56 -0
  169. package/src/user-helper.ts +296 -0
  170. package/src/uuid-helper.ts +11 -0
  171. package/src/workspace-handle.ts +10 -0
  172. package/src/workspace-helper.ts +1408 -0
  173. package/src/workspace-limits.ts +68 -0
  174. package/src/yjs-helper.ts +217 -0
  175. package/src/yjs-task-description.ts +81 -0
  176. package/tsconfig.json +3 -5
  177. package/tsconfig.typecheck.json +33 -0
  178. package/vitest.config.ts +36 -0
  179. package/dist/index.d.ts +0 -8
  180. package/dist/index.js +0 -2
  181. package/dist/index.js.map +0 -1
  182. package/dist/index.mjs +0 -2
  183. package/dist/index.mjs.map +0 -1
  184. package/eslint.config.mjs +0 -20
  185. package/rollup.config.js +0 -41
  186. package/src/index.ts +0 -1
@@ -0,0 +1,490 @@
1
+ import { toWorkspaceSlug } from './constants';
2
+ import { getTuturuuuPortlessAppOrigin } from './portless';
3
+
4
+ export const LAUNCHABLE_APP_CATEGORIES = [
5
+ 'core',
6
+ 'productivity',
7
+ 'content',
8
+ 'operations',
9
+ 'learning',
10
+ 'developer',
11
+ 'ai',
12
+ ] as const;
13
+
14
+ export type LaunchableAppCategory = (typeof LAUNCHABLE_APP_CATEGORIES)[number];
15
+
16
+ export type LaunchableWorkspace = {
17
+ guest_landing_path?: string | null;
18
+ id: string;
19
+ name?: string | null;
20
+ personal?: boolean | null;
21
+ };
22
+
23
+ export type LaunchableAppWorkspacePathResolver = (
24
+ workspace: LaunchableWorkspace
25
+ ) => string;
26
+
27
+ export type LaunchableApp = {
28
+ aliases: readonly string[];
29
+ appRoot: string;
30
+ category: LaunchableAppCategory;
31
+ defaultPath: string;
32
+ localhostOrigin?: string;
33
+ packageName: string;
34
+ portlessApp: Parameters<typeof getTuturuuuPortlessAppOrigin>[0];
35
+ productionUrl: string;
36
+ slug: string;
37
+ title: string;
38
+ workspacePathResolver?: LaunchableAppWorkspacePathResolver;
39
+ };
40
+
41
+ const workspaceRootPath: LaunchableAppWorkspacePathResolver = (workspace) =>
42
+ `/${toWorkspaceSlug(workspace.id, { personal: Boolean(workspace.personal) })}`;
43
+
44
+ const workspaceTasksPath: LaunchableAppWorkspacePathResolver = (workspace) =>
45
+ `${workspaceRootPath(workspace)}/tasks`;
46
+
47
+ const meetWorkspacePath: LaunchableAppWorkspacePathResolver = (workspace) =>
48
+ `/workspace/${toWorkspaceSlug(workspace.id, {
49
+ personal: Boolean(workspace.personal),
50
+ })}`;
51
+
52
+ export const LAUNCHABLE_APPS = [
53
+ {
54
+ aliases: ['Tuturuuu', 'Dashboard', 'Workspace'],
55
+ appRoot: 'apps/web',
56
+ category: 'core',
57
+ defaultPath: '/',
58
+ localhostOrigin: 'http://localhost:7803',
59
+ packageName: '@tuturuuu/web',
60
+ portlessApp: 'platform',
61
+ productionUrl: 'https://tuturuuu.com',
62
+ slug: 'platform',
63
+ title: 'Platform',
64
+ workspacePathResolver: workspaceRootPath,
65
+ },
66
+ {
67
+ aliases: ['App Launcher', 'Gateway', 'Apps Gateway'],
68
+ appRoot: 'apps/apps',
69
+ category: 'core',
70
+ defaultPath: '/',
71
+ localhostOrigin: 'http://localhost:7818',
72
+ packageName: '@tuturuuu/apps',
73
+ portlessApp: 'apps',
74
+ productionUrl: 'https://apps.tuturuuu.com',
75
+ slug: 'apps',
76
+ title: 'Apps',
77
+ },
78
+ {
79
+ aliases: ['Schedule', 'Events'],
80
+ appRoot: 'apps/calendar',
81
+ category: 'productivity',
82
+ defaultPath: '/personal',
83
+ localhostOrigin: 'http://localhost:7806',
84
+ packageName: '@tuturuuu/calendar',
85
+ portlessApp: 'calendar',
86
+ productionUrl: 'https://calendar.tuturuuu.com',
87
+ slug: 'calendar',
88
+ title: 'Calendar',
89
+ workspacePathResolver: workspaceRootPath,
90
+ },
91
+ {
92
+ aliases: ['Chat', 'Messages', 'Direct Messages'],
93
+ appRoot: 'apps/chat',
94
+ category: 'productivity',
95
+ defaultPath: '/',
96
+ localhostOrigin: 'http://localhost:7821',
97
+ packageName: '@tuturuuu/chat',
98
+ portlessApp: 'chat',
99
+ productionUrl: 'https://chat.tuturuuu.com',
100
+ slug: 'chat',
101
+ title: 'Chat',
102
+ workspacePathResolver: workspaceRootPath,
103
+ },
104
+ {
105
+ aliases: ['Content', 'CMS'],
106
+ appRoot: 'apps/cms',
107
+ category: 'content',
108
+ defaultPath: '/personal',
109
+ localhostOrigin: 'http://localhost:7811',
110
+ packageName: '@tuturuuu/cms',
111
+ portlessApp: 'cms',
112
+ productionUrl: 'https://cms.tuturuuu.com',
113
+ slug: 'cms',
114
+ title: 'CMS',
115
+ workspacePathResolver: workspaceRootPath,
116
+ },
117
+ {
118
+ aliases: ['Files', 'Storage'],
119
+ appRoot: 'apps/drive',
120
+ category: 'productivity',
121
+ defaultPath: '/personal',
122
+ localhostOrigin: 'http://localhost:7817',
123
+ packageName: '@tuturuuu/drive',
124
+ portlessApp: 'drive',
125
+ productionUrl: 'https://drive.tuturuuu.com',
126
+ slug: 'drive',
127
+ title: 'Drive',
128
+ workspacePathResolver: workspaceRootPath,
129
+ },
130
+ {
131
+ aliases: ['External Projects', 'WebGL'],
132
+ appRoot: 'apps/external',
133
+ category: 'developer',
134
+ defaultPath: '/',
135
+ packageName: '@tuturuuu/external',
136
+ portlessApp: 'external',
137
+ productionUrl: 'https://external.tuturuuu.com',
138
+ slug: 'external',
139
+ title: 'External',
140
+ },
141
+ {
142
+ aliases: ['Money', 'Wallets', 'Invoices'],
143
+ appRoot: 'apps/finance',
144
+ category: 'operations',
145
+ defaultPath: '/personal',
146
+ localhostOrigin: 'http://localhost:7808',
147
+ packageName: '@tuturuuu/finance',
148
+ portlessApp: 'finance',
149
+ productionUrl: 'https://finance.tuturuuu.com',
150
+ slug: 'finance',
151
+ title: 'Finance',
152
+ workspacePathResolver: workspaceRootPath,
153
+ },
154
+ {
155
+ aliases: ['Simulation', 'Voxel'],
156
+ appRoot: 'apps/hive',
157
+ category: 'ai',
158
+ defaultPath: '/',
159
+ localhostOrigin: 'http://localhost:7814',
160
+ packageName: '@tuturuuu/hive',
161
+ portlessApp: 'hive',
162
+ productionUrl: 'https://hive.tuturuuu.com',
163
+ slug: 'hive',
164
+ title: 'Hive',
165
+ },
166
+ {
167
+ aliases: ['Warehouse', 'Stock'],
168
+ appRoot: 'apps/inventory',
169
+ category: 'operations',
170
+ defaultPath: '/personal',
171
+ localhostOrigin: 'http://localhost:7815',
172
+ packageName: '@tuturuuu/inventory',
173
+ portlessApp: 'inventory',
174
+ productionUrl: 'https://inventory.tuturuuu.com',
175
+ slug: 'inventory',
176
+ title: 'Inventory',
177
+ workspacePathResolver: workspaceRootPath,
178
+ },
179
+ {
180
+ aliases: ['Store', 'Shop', 'Cart'],
181
+ appRoot: 'apps/storefront',
182
+ category: 'operations',
183
+ defaultPath: '/store/demo',
184
+ localhostOrigin: 'http://localhost:7822',
185
+ packageName: '@tuturuuu/storefront',
186
+ portlessApp: 'storefront',
187
+ productionUrl: 'https://storefront.tuturuuu.com',
188
+ slug: 'storefront',
189
+ title: 'Storefront',
190
+ },
191
+ {
192
+ aliases: ['Study', 'Student'],
193
+ appRoot: 'apps/learn',
194
+ category: 'learning',
195
+ defaultPath: '/dashboard',
196
+ localhostOrigin: 'http://localhost:7812',
197
+ packageName: '@tuturuuu/learn',
198
+ portlessApp: 'learn',
199
+ productionUrl: 'https://learn.tuturuuu.com',
200
+ slug: 'learn',
201
+ title: 'Learn',
202
+ },
203
+ {
204
+ aliases: ['Email', 'Inbox'],
205
+ appRoot: 'apps/mail',
206
+ category: 'productivity',
207
+ defaultPath: '/personal',
208
+ localhostOrigin: 'http://localhost:7820',
209
+ packageName: '@tuturuuu/mail',
210
+ portlessApp: 'mail',
211
+ productionUrl: 'https://mail.tuturuuu.com',
212
+ slug: 'mail',
213
+ title: 'Mail',
214
+ workspacePathResolver: workspaceRootPath,
215
+ },
216
+ {
217
+ aliases: ['Meeting', 'Video'],
218
+ appRoot: 'apps/meet',
219
+ category: 'productivity',
220
+ defaultPath: '/',
221
+ localhostOrigin: 'http://localhost:7807',
222
+ packageName: '@tuturuuu/meet',
223
+ portlessApp: 'meet',
224
+ productionUrl: 'https://meet.tuturuuu.com',
225
+ slug: 'meet',
226
+ title: 'Meet',
227
+ workspacePathResolver: meetWorkspacePath,
228
+ },
229
+ {
230
+ aliases: ['Mind Map', 'Canvas'],
231
+ appRoot: 'apps/mind',
232
+ category: 'ai',
233
+ defaultPath: '/dashboard',
234
+ localhostOrigin: 'http://localhost:7816',
235
+ packageName: '@tuturuuu/mind',
236
+ portlessApp: 'mind',
237
+ productionUrl: 'https://mind.tuturuuu.com',
238
+ slug: 'mind',
239
+ title: 'Mind',
240
+ workspacePathResolver: workspaceRootPath,
241
+ },
242
+ {
243
+ aliases: ['Prompt', 'Challenges'],
244
+ appRoot: 'apps/nova',
245
+ category: 'ai',
246
+ defaultPath: '/',
247
+ localhostOrigin: 'http://localhost:7805',
248
+ packageName: '@tuturuuu/nova',
249
+ portlessApp: 'nova',
250
+ productionUrl: 'https://nova.ai.vn',
251
+ slug: 'nova',
252
+ title: 'Nova',
253
+ },
254
+ {
255
+ aliases: ['Labs', 'Sandbox'],
256
+ appRoot: 'apps/playground',
257
+ category: 'developer',
258
+ defaultPath: '/',
259
+ packageName: '@tuturuuu/playground',
260
+ portlessApp: 'playground',
261
+ productionUrl: 'https://playground.tuturuuu.com',
262
+ slug: 'playground',
263
+ title: 'Playground',
264
+ },
265
+ {
266
+ aliases: ['QR Code', 'Generator'],
267
+ appRoot: 'apps/qr',
268
+ category: 'developer',
269
+ defaultPath: '/',
270
+ localhostOrigin: 'http://localhost:7819',
271
+ packageName: '@tuturuuu/qr',
272
+ portlessApp: 'qr',
273
+ productionUrl: 'https://qr.tuturuuu.com',
274
+ slug: 'qr',
275
+ title: 'QR',
276
+ },
277
+ {
278
+ aliases: ['Review', 'Flashcards'],
279
+ appRoot: 'apps/rewise',
280
+ category: 'learning',
281
+ defaultPath: '/',
282
+ localhostOrigin: 'http://localhost:7804',
283
+ packageName: '@tuturuuu/rewise',
284
+ portlessApp: 'rewise',
285
+ productionUrl: 'https://rewise.me',
286
+ slug: 'rewise',
287
+ title: 'Rewise',
288
+ },
289
+ {
290
+ aliases: ['Links', 'URL Shortener'],
291
+ appRoot: 'apps/shortener',
292
+ category: 'developer',
293
+ defaultPath: '/',
294
+ packageName: '@tuturuuu/shortener',
295
+ portlessApp: 'shortener',
296
+ productionUrl: 'https://shortener.tuturuuu.com',
297
+ slug: 'shortener',
298
+ title: 'Shortener',
299
+ },
300
+ {
301
+ aliases: ['Todos', 'Kanban', 'Projects'],
302
+ appRoot: 'apps/tasks',
303
+ category: 'productivity',
304
+ defaultPath: '/personal/tasks',
305
+ localhostOrigin: 'http://localhost:7809',
306
+ packageName: '@tuturuuu/tasks',
307
+ portlessApp: 'tasks',
308
+ productionUrl: 'https://tasks.tuturuuu.com',
309
+ slug: 'tasks',
310
+ title: 'Tasks',
311
+ workspacePathResolver: workspaceTasksPath,
312
+ },
313
+ {
314
+ aliases: ['Classes', 'Tutoring'],
315
+ appRoot: 'apps/teach',
316
+ category: 'learning',
317
+ defaultPath: '/personal',
318
+ localhostOrigin: 'http://localhost:7813',
319
+ packageName: '@tuturuuu/teach',
320
+ portlessApp: 'teach',
321
+ productionUrl: 'https://teach.tuturuuu.com',
322
+ slug: 'teach',
323
+ title: 'Teach',
324
+ workspacePathResolver: workspaceRootPath,
325
+ },
326
+ {
327
+ aliases: ['Time Tracking', 'Timer'],
328
+ appRoot: 'apps/track',
329
+ category: 'productivity',
330
+ defaultPath: '/personal',
331
+ localhostOrigin: 'http://localhost:7810',
332
+ packageName: '@tuturuuu/track',
333
+ portlessApp: 'track',
334
+ productionUrl: 'https://track.tuturuuu.com',
335
+ slug: 'track',
336
+ title: 'Track',
337
+ workspacePathResolver: workspaceRootPath,
338
+ },
339
+ ] as const satisfies readonly LaunchableApp[];
340
+
341
+ export type LaunchableAppSlug = (typeof LAUNCHABLE_APPS)[number]['slug'];
342
+
343
+ export type LaunchableAppEnvironment =
344
+ | 'auto'
345
+ | 'localhost'
346
+ | 'portless'
347
+ | 'production';
348
+
349
+ export function getLaunchableApp(slug: string) {
350
+ return LAUNCHABLE_APPS.find((app) => app.slug === slug) ?? null;
351
+ }
352
+
353
+ export function getLaunchableAppByTitle(value?: string | null) {
354
+ const normalized = value?.trim().toLowerCase();
355
+
356
+ if (!normalized) return null;
357
+
358
+ return (
359
+ LAUNCHABLE_APPS.find(
360
+ (app) =>
361
+ app.slug === normalized ||
362
+ app.title.toLowerCase() === normalized ||
363
+ app.aliases.some((alias) => alias.toLowerCase() === normalized)
364
+ ) ?? null
365
+ );
366
+ }
367
+
368
+ function getAutoEnvironment(currentOrigin?: string): LaunchableAppEnvironment {
369
+ if (currentOrigin?.includes('tuturuuu.localhost')) return 'portless';
370
+ if (currentOrigin?.includes('localhost')) return 'localhost';
371
+
372
+ if (typeof window !== 'undefined') {
373
+ if (window.location.hostname.endsWith('tuturuuu.localhost')) {
374
+ return 'portless';
375
+ }
376
+
377
+ if (window.location.hostname === 'localhost') {
378
+ return 'localhost';
379
+ }
380
+ }
381
+
382
+ return process.env.NODE_ENV === 'production' ? 'production' : 'portless';
383
+ }
384
+
385
+ export function getLaunchableAppOrigin(
386
+ app: LaunchableApp,
387
+ {
388
+ currentOrigin,
389
+ environment = 'auto',
390
+ }: {
391
+ currentOrigin?: string;
392
+ environment?: LaunchableAppEnvironment;
393
+ } = {}
394
+ ) {
395
+ const resolvedEnvironment =
396
+ environment === 'auto' ? getAutoEnvironment(currentOrigin) : environment;
397
+
398
+ if (resolvedEnvironment === 'production') return app.productionUrl;
399
+ if (resolvedEnvironment === 'localhost') {
400
+ return app.localhostOrigin ?? getTuturuuuPortlessAppOrigin(app.portlessApp);
401
+ }
402
+
403
+ return getTuturuuuPortlessAppOrigin(app.portlessApp);
404
+ }
405
+
406
+ function trimTrailingSlashes(value: string) {
407
+ let end = value.length;
408
+
409
+ while (end > 0 && value.charCodeAt(end - 1) === 47) {
410
+ end -= 1;
411
+ }
412
+
413
+ return end === value.length ? value : value.slice(0, end);
414
+ }
415
+
416
+ function normalizePath(path: string) {
417
+ const trimmed = path.trim();
418
+
419
+ if (!trimmed) return '/';
420
+ return trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
421
+ }
422
+
423
+ export function resolveLaunchableAppPath({
424
+ app,
425
+ path,
426
+ workspace,
427
+ workspacePathResolver,
428
+ }: {
429
+ app: LaunchableApp;
430
+ path?: string | null;
431
+ workspace?: LaunchableWorkspace | null;
432
+ workspacePathResolver?: LaunchableAppWorkspacePathResolver;
433
+ }) {
434
+ if (path) return normalizePath(path);
435
+
436
+ if (workspace) {
437
+ const resolver = workspacePathResolver ?? app.workspacePathResolver;
438
+ if (resolver) return normalizePath(resolver(workspace));
439
+ }
440
+
441
+ return normalizePath(app.defaultPath);
442
+ }
443
+
444
+ export function resolveLaunchableAppUrl({
445
+ app,
446
+ currentOrigin,
447
+ environment = 'auto',
448
+ path,
449
+ searchParams,
450
+ workspace,
451
+ workspacePathResolver,
452
+ }: {
453
+ app: LaunchableApp;
454
+ currentOrigin?: string;
455
+ environment?: LaunchableAppEnvironment;
456
+ path?: string | null;
457
+ searchParams?: Record<string, string | string[] | undefined>;
458
+ workspace?: LaunchableWorkspace | null;
459
+ workspacePathResolver?: LaunchableAppWorkspacePathResolver;
460
+ }) {
461
+ const url = new URL(
462
+ getLaunchableAppOrigin(app, { currentOrigin, environment })
463
+ );
464
+ const resolvedPath = resolveLaunchableAppPath({
465
+ app,
466
+ path,
467
+ workspace,
468
+ workspacePathResolver,
469
+ });
470
+
471
+ url.pathname =
472
+ resolvedPath === '/'
473
+ ? trimTrailingSlashes(url.pathname) || '/'
474
+ : `${trimTrailingSlashes(url.pathname)}${resolvedPath}`;
475
+
476
+ for (const [key, value] of Object.entries(searchParams ?? {})) {
477
+ if (Array.isArray(value)) {
478
+ for (const item of value) {
479
+ url.searchParams.append(key, item);
480
+ }
481
+ continue;
482
+ }
483
+
484
+ if (value !== undefined) {
485
+ url.searchParams.set(key, value);
486
+ }
487
+ }
488
+
489
+ return url.toString();
490
+ }