@tuturuuu/utils 0.25.4 → 0.26.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 +17 -0
- package/biome.json +1 -1
- package/package.json +4 -4
- package/src/common/metadata.test.ts +1 -1
- package/src/next-config.test.ts +36 -0
- package/src/next-config.ts +48 -6
- package/src/platform-release.test.ts +1 -1
- package/src/platform-release.ts +1 -1
- package/src/task-helper.ts +6 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.26.0](https://github.com/tutur3u/platform/compare/utils-v0.25.5...utils-v0.26.0) (2026-08-26)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **forms:** add landing page, per-form SEO, realtime collaboration, and embeds ([#5145](https://github.com/tutur3u/platform/issues/5145)) ([6896515](https://github.com/tutur3u/platform/commit/6896515b099b28bbebc873f98733879bf108ef89))
|
|
9
|
+
* **forms:** add six embed modes and a public embed SDK ([5d0b68b](https://github.com/tutur3u/platform/commit/5d0b68b3ca5ce3d4cd6cd119e2b0ad79b014aedd))
|
|
10
|
+
* **tu-do:** search board tasks by ticket identifier ([e81891c](https://github.com/tutur3u/platform/commit/e81891c7424b530322e3250d917636a988e278fe))
|
|
11
|
+
* **tu-do:** search board tasks by ticket identifier ([#5142](https://github.com/tutur3u/platform/issues/5142)) ([18c0e3e](https://github.com/tutur3u/platform/commit/18c0e3ea6a20522959a0b66ed8dfd5a5699daec4))
|
|
12
|
+
|
|
13
|
+
## [0.25.5](https://github.com/tutur3u/platform/compare/utils-v0.25.4...utils-v0.25.5) (2026-08-25)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Bug Fixes
|
|
17
|
+
|
|
18
|
+
* **contacts:** proxy feedbacks before dynamic users ([910b79e](https://github.com/tutur3u/platform/commit/910b79ef5a53e31b865cb1290e2f2b29d19c1702))
|
|
19
|
+
|
|
3
20
|
## [0.25.4](https://github.com/tutur3u/platform/compare/utils-v0.25.3...utils-v0.25.4) (2026-08-21)
|
|
4
21
|
|
|
5
22
|
|
package/biome.json
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tuturuuu/utils",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.26.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "https://github.com/tutur3u/platform",
|
|
@@ -19,12 +19,12 @@
|
|
|
19
19
|
"@dnd-kit/core": "^6.3.1",
|
|
20
20
|
"@noble/ciphers": "^2.3.0",
|
|
21
21
|
"@noble/hashes": "^2.3.0",
|
|
22
|
-
"@tanstack/react-query": "^5.
|
|
22
|
+
"@tanstack/react-query": "^5.102.1",
|
|
23
23
|
"@tiptap/core": "3.30.2",
|
|
24
24
|
"@tiptap/react": "3.30.2",
|
|
25
25
|
"@tuturuuu/google": "0.1.0",
|
|
26
26
|
"@tuturuuu/icons": "0.1.0",
|
|
27
|
-
"@tuturuuu/internal-api": "0.
|
|
27
|
+
"@tuturuuu/internal-api": "0.33.0",
|
|
28
28
|
"@tuturuuu/supabase": "0.5.1",
|
|
29
29
|
"@upstash/ratelimit": "^2.0.8",
|
|
30
30
|
"@upstash/redis": "^1.38.2",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"zod": "^4.4.3"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@tuturuuu/types": "0.
|
|
48
|
+
"@tuturuuu/types": "0.28.0",
|
|
49
49
|
"@tuturuuu/typescript-config": "0.1.2",
|
|
50
50
|
"@types/diff": "^8.0.0",
|
|
51
51
|
"@types/node": "^26.2.0",
|
|
@@ -326,7 +326,7 @@ describe('application metadata coverage', () => {
|
|
|
326
326
|
)
|
|
327
327
|
.map((entry) => resolve(entry.parentPath, entry.name));
|
|
328
328
|
|
|
329
|
-
expect(marketingLayouts).toHaveLength(
|
|
329
|
+
expect(marketingLayouts).toHaveLength(38);
|
|
330
330
|
for (const layoutPath of marketingLayouts) {
|
|
331
331
|
expect(readFileSync(layoutPath, 'utf8')).toMatch(/pathname:\s*['`]/);
|
|
332
332
|
}
|
package/src/next-config.test.ts
CHANGED
|
@@ -290,6 +290,11 @@ describe('createTuturuuuWebWorkspaceApiRewrites', () => {
|
|
|
290
290
|
destination:
|
|
291
291
|
'https://tuturuuu.com/api/v1/workspaces/:wsId/settings/permissions',
|
|
292
292
|
},
|
|
293
|
+
{
|
|
294
|
+
source: '/api/v1/workspaces/:wsId/users/feedbacks',
|
|
295
|
+
destination:
|
|
296
|
+
'https://tuturuuu.com/api/v1/workspaces/:wsId/users/feedbacks',
|
|
297
|
+
},
|
|
293
298
|
]);
|
|
294
299
|
});
|
|
295
300
|
});
|
|
@@ -495,4 +500,35 @@ describe('trimTrailingSlashes', () => {
|
|
|
495
500
|
'https://example.com/path'
|
|
496
501
|
);
|
|
497
502
|
});
|
|
503
|
+
|
|
504
|
+
it('excludes opted-in paths from the anti-framing rule', async () => {
|
|
505
|
+
const config = createTuturuuuNextConfig({
|
|
506
|
+
framablePathPatterns: ['embed/[^/]+'],
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
const headers = await config.headers?.();
|
|
510
|
+
const source = headers?.[0]?.source ?? '';
|
|
511
|
+
|
|
512
|
+
expect(source).toContain('embed/[^/]+(?:/|$)');
|
|
513
|
+
// The platform default must survive an app adding its own patterns.
|
|
514
|
+
expect(source).toContain('external-projects/assets/[^/]+/webgl(?:/|$)');
|
|
515
|
+
|
|
516
|
+
// The `source` is a path-to-regexp pattern, so assert the lookahead does
|
|
517
|
+
// what it claims: opted-in paths must not match the deny rule, and
|
|
518
|
+
// everything else still must.
|
|
519
|
+
const lookahead = new RegExp(`^${source.slice('/:path('.length, -1)}$`);
|
|
520
|
+
expect(lookahead.test('embed/abc123')).toBe(false);
|
|
521
|
+
expect(lookahead.test('embed/abc123/preview')).toBe(false);
|
|
522
|
+
expect(lookahead.test('embedded-elsewhere')).toBe(true);
|
|
523
|
+
expect(lookahead.test('f/abc123')).toBe(true);
|
|
524
|
+
expect(lookahead.test('personal/forms')).toBe(true);
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
it('does not leak framablePathPatterns into the Next config object', () => {
|
|
528
|
+
const config = createTuturuuuNextConfig({
|
|
529
|
+
framablePathPatterns: ['embed/[^/]+'],
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
expect(config).not.toHaveProperty('framablePathPatterns');
|
|
533
|
+
});
|
|
498
534
|
});
|
package/src/next-config.ts
CHANGED
|
@@ -16,6 +16,7 @@ export const TUTURUUU_NEXT_OPTIMIZE_PACKAGE_IMPORTS = [
|
|
|
16
16
|
export const TUTURUUU_WEB_WORKSPACE_API_RESERVED_PATHS = [
|
|
17
17
|
'/api/workspaces/invitations',
|
|
18
18
|
'/api/v1/workspaces/:wsId/settings/permissions',
|
|
19
|
+
'/api/v1/workspaces/:wsId/users/feedbacks',
|
|
19
20
|
] as const;
|
|
20
21
|
|
|
21
22
|
type NextImageConfig = NonNullable<NextConfig['images']>;
|
|
@@ -40,8 +41,31 @@ export const TUTURUUU_NEXT_IMAGE_REMOTE_PATTERNS = [
|
|
|
40
41
|
|
|
41
42
|
export const TUTURUUU_NEXT_IMAGE_MINIMUM_CACHE_TTL_SECONDS = 7 * 24 * 60 * 60;
|
|
42
43
|
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
/**
|
|
45
|
+
* Paths every app excludes from the anti-framing headers.
|
|
46
|
+
*
|
|
47
|
+
* Framing is denied by default across the platform; anything listed here has
|
|
48
|
+
* been deliberately designed to be embedded in a third-party page.
|
|
49
|
+
*/
|
|
50
|
+
const TUTURUUU_DEFAULT_FRAMABLE_PATTERNS = [
|
|
51
|
+
'api/v1/workspaces/[^/]+/external-projects/assets/[^/]+/webgl',
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Builds the anti-framing `source` as a negative lookahead over every framable
|
|
56
|
+
* path, so those paths simply never match the deny rule.
|
|
57
|
+
*
|
|
58
|
+
* A later permissive header would not reliably win: `X-Frame-Options` has no
|
|
59
|
+
* "allow from any origin" value, so the deny header has to be absent rather
|
|
60
|
+
* than overridden.
|
|
61
|
+
*/
|
|
62
|
+
function buildAntiFramingSource(framablePathPatterns: readonly string[]) {
|
|
63
|
+
const alternatives = framablePathPatterns
|
|
64
|
+
.map((pattern) => `${pattern}(?:/|$)`)
|
|
65
|
+
.join('|');
|
|
66
|
+
|
|
67
|
+
return `/:path((?!${alternatives}).*)`;
|
|
68
|
+
}
|
|
45
69
|
|
|
46
70
|
const TUTURUUU_ANTI_FRAMING_HEADERS = [
|
|
47
71
|
{
|
|
@@ -110,14 +134,32 @@ export function getTuturuuuNextOptimizePackageImports(
|
|
|
110
134
|
: mergeStringArrays(TUTURUUU_NEXT_OPTIMIZE_PACKAGE_IMPORTS, appImports);
|
|
111
135
|
}
|
|
112
136
|
|
|
113
|
-
export
|
|
137
|
+
export interface TuturuuuNextConfigOptions extends NextConfig {
|
|
138
|
+
/**
|
|
139
|
+
* Path patterns (no leading slash, regex fragments) that this app allows to
|
|
140
|
+
* be framed by third-party sites. Excluded from the platform-wide
|
|
141
|
+
* `frame-ancestors 'none'` / `X-Frame-Options: DENY` rule.
|
|
142
|
+
*
|
|
143
|
+
* Opt in per route, never per app: everything not listed here stays denied.
|
|
144
|
+
*/
|
|
145
|
+
framablePathPatterns?: readonly string[];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function createTuturuuuNextConfig(
|
|
149
|
+
config: TuturuuuNextConfigOptions = {}
|
|
150
|
+
): NextConfig {
|
|
114
151
|
const experimentalConfig = config.experimental ?? {};
|
|
115
152
|
const imageConfig = config.images ?? {};
|
|
153
|
+
const { framablePathPatterns, ...nextConfig } = config;
|
|
154
|
+
const antiFramingSource = buildAntiFramingSource([
|
|
155
|
+
...TUTURUUU_DEFAULT_FRAMABLE_PATTERNS,
|
|
156
|
+
...(framablePathPatterns ?? []),
|
|
157
|
+
]);
|
|
116
158
|
|
|
117
159
|
return {
|
|
118
160
|
reactStrictMode: true,
|
|
119
161
|
poweredByHeader: false,
|
|
120
|
-
...
|
|
162
|
+
...nextConfig,
|
|
121
163
|
reactCompiler: isTuturuuuNextReactCompilerEnabled(),
|
|
122
164
|
cacheComponents:
|
|
123
165
|
config.cacheComponents ?? isTuturuuuNextCacheComponentsEnabled(),
|
|
@@ -158,10 +200,10 @@ export function createTuturuuuNextConfig(config: NextConfig = {}): NextConfig {
|
|
|
158
200
|
async headers() {
|
|
159
201
|
return [
|
|
160
202
|
{
|
|
161
|
-
source:
|
|
203
|
+
source: antiFramingSource,
|
|
162
204
|
headers: TUTURUUU_ANTI_FRAMING_HEADERS,
|
|
163
205
|
},
|
|
164
|
-
...((await
|
|
206
|
+
...((await nextConfig.headers?.()) ?? []),
|
|
165
207
|
];
|
|
166
208
|
},
|
|
167
209
|
};
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
|
|
8
8
|
describe('platform release metadata', () => {
|
|
9
9
|
it('uses the centralized shared browser app version', () => {
|
|
10
|
-
expect(TUTURUUU_PLATFORM_VERSION).toBe('0.
|
|
10
|
+
expect(TUTURUUU_PLATFORM_VERSION).toBe('0.42.0'); // x-release-please-version
|
|
11
11
|
});
|
|
12
12
|
|
|
13
13
|
it('preserves the source timestamp in builtAt and derives a short hash', () => {
|
package/src/platform-release.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PLATFORM_BUILD_METADATA } from './generated/platform-build-metadata';
|
|
2
2
|
|
|
3
|
-
export const TUTURUUU_PLATFORM_VERSION = '0.
|
|
3
|
+
export const TUTURUUU_PLATFORM_VERSION = '0.42.0'; // x-release-please-version
|
|
4
4
|
|
|
5
5
|
export type PlatformBuildMetadataInput = {
|
|
6
6
|
/** ISO timestamp of the source commit used for the build, not deployment time. */
|
package/src/task-helper.ts
CHANGED
|
@@ -6,7 +6,12 @@ export * from './task-helper/optimistic-task-cache';
|
|
|
6
6
|
export * from './task-helper/personal-external-staging';
|
|
7
7
|
export * from './task-helper/recycle-bin';
|
|
8
8
|
export * from './task-helper/relationships';
|
|
9
|
-
export {
|
|
9
|
+
export {
|
|
10
|
+
getTaskIdentifierForSearch,
|
|
11
|
+
getTicketIdentifier,
|
|
12
|
+
isTicketIdentifierLikeQuery,
|
|
13
|
+
normalizeTaskSearchValue,
|
|
14
|
+
} from './task-helper/shared';
|
|
10
15
|
export * from './task-helper/sort-keys';
|
|
11
16
|
export * from './task-helper/task-hooks-basic';
|
|
12
17
|
export * from './task-helper/task-hooks-move';
|