@tuturuuu/utils 0.25.5 → 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 +10 -0
- package/package.json +3 -3
- package/src/common/metadata.test.ts +1 -1
- package/src/next-config.test.ts +31 -0
- package/src/next-config.ts +47 -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,15 @@
|
|
|
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
|
+
|
|
3
13
|
## [0.25.5](https://github.com/tutur3u/platform/compare/utils-v0.25.4...utils-v0.25.5) (2026-08-25)
|
|
4
14
|
|
|
5
15
|
|
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",
|
|
@@ -24,7 +24,7 @@
|
|
|
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
|
@@ -500,4 +500,35 @@ describe('trimTrailingSlashes', () => {
|
|
|
500
500
|
'https://example.com/path'
|
|
501
501
|
);
|
|
502
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
|
+
});
|
|
503
534
|
});
|
package/src/next-config.ts
CHANGED
|
@@ -41,8 +41,31 @@ export const TUTURUUU_NEXT_IMAGE_REMOTE_PATTERNS = [
|
|
|
41
41
|
|
|
42
42
|
export const TUTURUUU_NEXT_IMAGE_MINIMUM_CACHE_TTL_SECONDS = 7 * 24 * 60 * 60;
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
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
|
+
}
|
|
46
69
|
|
|
47
70
|
const TUTURUUU_ANTI_FRAMING_HEADERS = [
|
|
48
71
|
{
|
|
@@ -111,14 +134,32 @@ export function getTuturuuuNextOptimizePackageImports(
|
|
|
111
134
|
: mergeStringArrays(TUTURUUU_NEXT_OPTIMIZE_PACKAGE_IMPORTS, appImports);
|
|
112
135
|
}
|
|
113
136
|
|
|
114
|
-
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 {
|
|
115
151
|
const experimentalConfig = config.experimental ?? {};
|
|
116
152
|
const imageConfig = config.images ?? {};
|
|
153
|
+
const { framablePathPatterns, ...nextConfig } = config;
|
|
154
|
+
const antiFramingSource = buildAntiFramingSource([
|
|
155
|
+
...TUTURUUU_DEFAULT_FRAMABLE_PATTERNS,
|
|
156
|
+
...(framablePathPatterns ?? []),
|
|
157
|
+
]);
|
|
117
158
|
|
|
118
159
|
return {
|
|
119
160
|
reactStrictMode: true,
|
|
120
161
|
poweredByHeader: false,
|
|
121
|
-
...
|
|
162
|
+
...nextConfig,
|
|
122
163
|
reactCompiler: isTuturuuuNextReactCompilerEnabled(),
|
|
123
164
|
cacheComponents:
|
|
124
165
|
config.cacheComponents ?? isTuturuuuNextCacheComponentsEnabled(),
|
|
@@ -159,10 +200,10 @@ export function createTuturuuuNextConfig(config: NextConfig = {}): NextConfig {
|
|
|
159
200
|
async headers() {
|
|
160
201
|
return [
|
|
161
202
|
{
|
|
162
|
-
source:
|
|
203
|
+
source: antiFramingSource,
|
|
163
204
|
headers: TUTURUUU_ANTI_FRAMING_HEADERS,
|
|
164
205
|
},
|
|
165
|
-
...((await
|
|
206
|
+
...((await nextConfig.headers?.()) ?? []),
|
|
166
207
|
];
|
|
167
208
|
},
|
|
168
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';
|