@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 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.25.5",
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.32.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.27.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(37);
329
+ expect(marketingLayouts).toHaveLength(38);
330
330
  for (const layoutPath of marketingLayouts) {
331
331
  expect(readFileSync(layoutPath, 'utf8')).toMatch(/pathname:\s*['`]/);
332
332
  }
@@ -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
  });
@@ -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
- const TUTURUUU_ANTI_FRAMING_SOURCE =
45
- '/:path((?!api/v1/workspaces/[^/]+/external-projects/assets/[^/]+/webgl(?:/|$)).*)';
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 function createTuturuuuNextConfig(config: NextConfig = {}): NextConfig {
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
- ...config,
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: TUTURUUU_ANTI_FRAMING_SOURCE,
203
+ source: antiFramingSource,
163
204
  headers: TUTURUUU_ANTI_FRAMING_HEADERS,
164
205
  },
165
- ...((await config.headers?.()) ?? []),
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.41.0'); // x-release-please-version
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', () => {
@@ -1,6 +1,6 @@
1
1
  import { PLATFORM_BUILD_METADATA } from './generated/platform-build-metadata';
2
2
 
3
- export const TUTURUUU_PLATFORM_VERSION = '0.41.0'; // x-release-please-version
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. */
@@ -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 { getTicketIdentifier } from './task-helper/shared';
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';