includio-cms 0.13.2 → 0.13.3

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 (45) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/ROADMAP.md +12 -2
  3. package/dist/admin/api/replace.js +4 -0
  4. package/dist/admin/api/rest/middleware/apiKey.js +7 -1
  5. package/dist/admin/api/upload.js +4 -0
  6. package/dist/admin/client/collection/collection-entries.svelte +8 -4
  7. package/dist/admin/client/collection/grid-view.svelte +1 -1
  8. package/dist/admin/client/entry/entry-header.svelte +37 -44
  9. package/dist/admin/client/entry/entry-header.svelte.d.ts +1 -2
  10. package/dist/admin/client/entry/entry-version.svelte +9 -3
  11. package/dist/admin/client/entry/entry.svelte +20 -1
  12. package/dist/admin/components/fields/seo-field.svelte +30 -16
  13. package/dist/admin/remote/entry.remote.js +3 -4
  14. package/dist/admin/state/content-language.svelte.d.ts +0 -3
  15. package/dist/admin/state/content-language.svelte.js +7 -11
  16. package/dist/admin/utils/entryLabel.js +2 -3
  17. package/dist/cms/runtime/api.d.ts +5 -0
  18. package/dist/cms/runtime/types.d.ts +13 -8
  19. package/dist/core/cms.js +3 -0
  20. package/dist/core/fields/layoutUtils.d.ts +2 -2
  21. package/dist/core/fields/layoutUtils.js +3 -10
  22. package/dist/core/server/entries/operations/get.js +2 -2
  23. package/dist/core/server/media/mimeBlocklist.d.ts +1 -0
  24. package/dist/core/server/media/mimeBlocklist.js +31 -0
  25. package/dist/paraglide/messages/_index.d.ts +3 -36
  26. package/dist/paraglide/messages/_index.js +3 -71
  27. package/dist/paraglide/messages/hello_world.d.ts +5 -0
  28. package/dist/paraglide/messages/hello_world.js +33 -0
  29. package/dist/paraglide/messages/login_hello.d.ts +16 -0
  30. package/dist/paraglide/messages/login_hello.js +34 -0
  31. package/dist/paraglide/messages/login_please_login.d.ts +16 -0
  32. package/dist/paraglide/messages/login_please_login.js +34 -0
  33. package/dist/sveltekit/server/handle.js +8 -0
  34. package/dist/updates/0.13.3/index.d.ts +2 -0
  35. package/dist/updates/0.13.3/index.js +21 -0
  36. package/dist/updates/index.js +2 -1
  37. package/package.json +1 -1
  38. package/dist/admin/utils/translationStatus.d.ts +0 -17
  39. package/dist/admin/utils/translationStatus.js +0 -133
  40. package/dist/demo/reset.d.ts +0 -1
  41. package/dist/demo/reset.js +0 -26
  42. package/dist/paraglide/messages/en.d.ts +0 -5
  43. package/dist/paraglide/messages/en.js +0 -14
  44. package/dist/paraglide/messages/pl.d.ts +0 -5
  45. package/dist/paraglide/messages/pl.js +0 -14
@@ -1,14 +1,15 @@
1
- import type { FlatImageFieldData, FlatVideoFieldData, StructuredContentDoc } from 'includio-cms/types';
1
+ import type { ImageFieldData, VideoFieldData, StructuredContentDoc } from 'includio-cms/types';
2
2
  export type SingleSlug = "settings" | "image-showcase";
3
3
  export interface Settings {
4
4
  _id: string;
5
5
  _slug: string;
6
6
  _type: string;
7
7
  _publishedAt: Date | null;
8
+ _url?: string;
8
9
  siteName: string;
9
10
  description?: string;
10
- logo?: FlatImageFieldData | FlatVideoFieldData | null;
11
- favicon?: FlatImageFieldData | FlatVideoFieldData | null;
11
+ logo?: ImageFieldData | VideoFieldData | null;
12
+ favicon?: ImageFieldData | VideoFieldData | null;
12
13
  socialLinks?: ({
13
14
  _slug: 'socialLink';
14
15
  platform: string;
@@ -25,7 +26,8 @@ export interface ImageShowcase {
25
26
  _slug: string;
26
27
  _type: string;
27
28
  _publishedAt: Date | null;
28
- photo?: FlatImageFieldData | FlatVideoFieldData | null;
29
+ _url?: string;
30
+ photo?: ImageFieldData | VideoFieldData | null;
29
31
  }
30
32
  export type SingleEntryMap = {
31
33
  settings: Settings;
@@ -37,13 +39,14 @@ export interface BlogPost {
37
39
  _slug: string;
38
40
  _type: string;
39
41
  _publishedAt: Date | null;
42
+ _url?: string;
40
43
  title: string;
41
44
  slug?: string;
42
- cover?: FlatImageFieldData | FlatVideoFieldData | null;
45
+ cover?: ImageFieldData | VideoFieldData | null;
43
46
  rating: number;
44
47
  category?: string;
45
48
  publishedAt?: string;
46
- thumbnail?: FlatImageFieldData | FlatVideoFieldData | null;
49
+ thumbnail?: ImageFieldData | VideoFieldData | null;
47
50
  content?: StructuredContentDoc;
48
51
  tags?: string[];
49
52
  seo: {
@@ -65,6 +68,7 @@ export interface Project {
65
68
  _slug: string;
66
69
  _type: string;
67
70
  _publishedAt: Date | null;
71
+ _url?: string;
68
72
  title: string;
69
73
  description?: string;
70
74
  status?: string;
@@ -72,7 +76,7 @@ export interface Project {
72
76
  url?: {
73
77
  url: string;
74
78
  };
75
- image?: FlatImageFieldData | FlatVideoFieldData | null;
79
+ image?: ImageFieldData | VideoFieldData | null;
76
80
  techStack?: ({
77
81
  _slug: 'tech';
78
82
  name: string;
@@ -92,6 +96,7 @@ export interface ArrayTest {
92
96
  _slug: string;
93
97
  _type: string;
94
98
  _publishedAt: Date | null;
99
+ _url?: string;
95
100
  name: string;
96
101
  tags?: string[];
97
102
  localizedTags?: Record<string, string>[];
@@ -104,7 +109,7 @@ export interface ArrayTest {
104
109
  body?: StructuredContentDoc;
105
110
  } | {
106
111
  _slug: 'image-block';
107
- image: FlatImageFieldData | FlatVideoFieldData;
112
+ image: ImageFieldData | VideoFieldData;
108
113
  caption?: string;
109
114
  })[];
110
115
  highlights?: ({
package/dist/core/cms.js CHANGED
@@ -51,6 +51,9 @@ export class CMS {
51
51
  };
52
52
  });
53
53
  this.languages = config.languages || [];
54
+ if (this.languages.length === 0) {
55
+ throw new Error('CMS config must include at least one language.');
56
+ }
54
57
  this.apiKeys = config.apiKeys || [];
55
58
  if (config.plugins) {
56
59
  this.plugins = config.plugins;
@@ -25,8 +25,8 @@ export declare function collectAllLeafPaths(fields: Field[], prefix?: string): s
25
25
  export declare function getDistributedObjectSlugs(nodes: LayoutNode[], fields: Field[]): Set<string>;
26
26
  /**
27
27
  * Build SuperForm-compatible path for a dot-notation field reference.
28
- * 'hero.title' → 'hero.data.title'
29
- * 'hero.contact.email' → 'hero.data.contact.data.email'
28
+ * 'hero.title' → 'hero.title'
29
+ * 'hero.contact.email' → 'hero.contact.email'
30
30
  * 'title' → 'title' (no change for top-level)
31
31
  */
32
32
  export declare function buildFormPath(dotPath: string): string;
@@ -98,19 +98,12 @@ export function getDistributedObjectSlugs(nodes, fields) {
98
98
  }
99
99
  /**
100
100
  * Build SuperForm-compatible path for a dot-notation field reference.
101
- * 'hero.title' → 'hero.data.title'
102
- * 'hero.contact.email' → 'hero.data.contact.data.email'
101
+ * 'hero.title' → 'hero.title'
102
+ * 'hero.contact.email' → 'hero.contact.email'
103
103
  * 'title' → 'title' (no change for top-level)
104
104
  */
105
105
  export function buildFormPath(dotPath) {
106
- const parts = dotPath.split('.');
107
- if (parts.length <= 1)
108
- return dotPath;
109
- const result = [parts[0]];
110
- for (let i = 1; i < parts.length; i++) {
111
- result.push('data', parts[i]);
112
- }
113
- return result.join('.');
106
+ return dotPath;
114
107
  }
115
108
  /** Count columns expected by a ratio string */
116
109
  function columnCount(ratio) {
@@ -1,4 +1,5 @@
1
1
  import { getCMS } from '../../../cms.js';
2
+ import { getAtPath } from '../../../../admin/utils/objectPath.js';
2
3
  import { populateEntryData } from '../../fields/populateEntry.js';
3
4
  import { getFieldsFromConfig } from '../../../fields/layoutUtils.js';
4
5
  import { getEntrySlugPath, getSlugFromEntryData, getEntryPath } from '../../fields/slugResolver.js';
@@ -303,8 +304,7 @@ export const getEntryLabels = async (options) => {
303
304
  const latestVersion = publishedVersion ?? sorted[0] ?? null;
304
305
  let label = entry.id;
305
306
  if (entryAdminTitle && latestVersion) {
306
- const titleData = latestVersion.data[entryAdminTitle];
307
- // Data is now flat — titleData is the string directly
307
+ const titleData = getAtPath(latestVersion.data, entryAdminTitle);
308
308
  if (typeof titleData === 'string') {
309
309
  label = titleData || entry.id;
310
310
  }
@@ -0,0 +1 @@
1
+ export declare function isBlockedMimeType(mimeType: string, fileName?: string): boolean;
@@ -0,0 +1,31 @@
1
+ const BLOCKED_MIME_TYPES = new Set([
2
+ 'application/x-msdownload',
3
+ 'application/x-executable',
4
+ 'application/x-msdos-program',
5
+ 'application/x-sh',
6
+ 'application/x-shellscript',
7
+ 'application/x-bat',
8
+ 'application/x-msi',
9
+ 'application/java-archive',
10
+ 'application/x-httpd-php',
11
+ 'text/x-php',
12
+ 'application/hta'
13
+ ]);
14
+ const BLOCKED_EXTENSIONS = new Set([
15
+ '.exe', '.bat', '.cmd', '.com', '.msi', '.scr', '.pif',
16
+ '.sh', '.bash', '.csh', '.ksh',
17
+ '.php', '.php3', '.php4', '.php5', '.phtml',
18
+ '.jsp', '.asp', '.aspx',
19
+ '.jar', '.class',
20
+ '.hta', '.vbs', '.vbe', '.wsf', '.wsh', '.ps1'
21
+ ]);
22
+ export function isBlockedMimeType(mimeType, fileName) {
23
+ if (BLOCKED_MIME_TYPES.has(mimeType.toLowerCase()))
24
+ return true;
25
+ if (fileName) {
26
+ const ext = fileName.slice(fileName.lastIndexOf('.')).toLowerCase();
27
+ if (BLOCKED_EXTENSIONS.has(ext))
28
+ return true;
29
+ }
30
+ return false;
31
+ }
@@ -1,36 +1,3 @@
1
- export function hello_world(inputs: {
2
- name: NonNullable<unknown>;
3
- }, options?: {
4
- locale?: "en" | "pl";
5
- }): string;
6
- /**
7
- * This function has been compiled by [Paraglide JS](https://inlang.com/m/gerre34r).
8
- *
9
- * - Changing this function will be over-written by the next build.
10
- *
11
- * - If you want to change the translations, you can either edit the source files e.g. `en.json`, or
12
- * use another inlang app like [Fink](https://inlang.com/m/tdozzpar) or the [VSCode extension Sherlock](https://inlang.com/m/r7kp499g).
13
- *
14
- * @param {{}} inputs
15
- * @param {{ locale?: "en" | "pl" }} options
16
- * @returns {string}
17
- */
18
- declare function login_hello(inputs?: {}, options?: {
19
- locale?: "en" | "pl";
20
- }): string;
21
- /**
22
- * This function has been compiled by [Paraglide JS](https://inlang.com/m/gerre34r).
23
- *
24
- * - Changing this function will be over-written by the next build.
25
- *
26
- * - If you want to change the translations, you can either edit the source files e.g. `en.json`, or
27
- * use another inlang app like [Fink](https://inlang.com/m/tdozzpar) or the [VSCode extension Sherlock](https://inlang.com/m/r7kp499g).
28
- *
29
- * @param {{}} inputs
30
- * @param {{ locale?: "en" | "pl" }} options
31
- * @returns {string}
32
- */
33
- declare function login_please_login(inputs?: {}, options?: {
34
- locale?: "en" | "pl";
35
- }): string;
36
- export { login_hello as login.hello, login_please_login as login.please_login };
1
+ export * from "./hello_world.js";
2
+ export * from "./login_hello.js";
3
+ export * from "./login_please_login.js";
@@ -1,72 +1,4 @@
1
1
  /* eslint-disable */
2
- import { getLocale, trackMessageCall, experimentalMiddlewareLocaleSplitting, isServer } from "../runtime.js"
3
- import * as en from "./en.js"
4
- import * as pl from "./pl.js"
5
- /**
6
- * This function has been compiled by [Paraglide JS](https://inlang.com/m/gerre34r).
7
- *
8
- * - Changing this function will be over-written by the next build.
9
- *
10
- * - If you want to change the translations, you can either edit the source files e.g. `en.json`, or
11
- * use another inlang app like [Fink](https://inlang.com/m/tdozzpar) or the [VSCode extension Sherlock](https://inlang.com/m/r7kp499g).
12
- *
13
- * @param {{ name: NonNullable<unknown> }} inputs
14
- * @param {{ locale?: "en" | "pl" }} options
15
- * @returns {string}
16
- */
17
- /* @__NO_SIDE_EFFECTS__ */
18
- export const hello_world = (inputs, options = {}) => {
19
- if (experimentalMiddlewareLocaleSplitting && isServer === false) {
20
- return /** @type {any} */ (globalThis).__paraglide_ssr.hello_world(inputs)
21
- }
22
- const locale = options.locale ?? getLocale()
23
- trackMessageCall("hello_world", locale)
24
- if (locale === "en") return en.hello_world(inputs)
25
- return pl.hello_world(inputs)
26
- };
27
- /**
28
- * This function has been compiled by [Paraglide JS](https://inlang.com/m/gerre34r).
29
- *
30
- * - Changing this function will be over-written by the next build.
31
- *
32
- * - If you want to change the translations, you can either edit the source files e.g. `en.json`, or
33
- * use another inlang app like [Fink](https://inlang.com/m/tdozzpar) or the [VSCode extension Sherlock](https://inlang.com/m/r7kp499g).
34
- *
35
- * @param {{}} inputs
36
- * @param {{ locale?: "en" | "pl" }} options
37
- * @returns {string}
38
- */
39
- /* @__NO_SIDE_EFFECTS__ */
40
- const login_hello = (inputs = {}, options = {}) => {
41
- if (experimentalMiddlewareLocaleSplitting && isServer === false) {
42
- return /** @type {any} */ (globalThis).__paraglide_ssr.login_hello(inputs)
43
- }
44
- const locale = options.locale ?? getLocale()
45
- trackMessageCall("login_hello", locale)
46
- if (locale === "en") return en.login_hello(inputs)
47
- return pl.login_hello(inputs)
48
- };
49
- export { login_hello as "login.hello" }
50
- /**
51
- * This function has been compiled by [Paraglide JS](https://inlang.com/m/gerre34r).
52
- *
53
- * - Changing this function will be over-written by the next build.
54
- *
55
- * - If you want to change the translations, you can either edit the source files e.g. `en.json`, or
56
- * use another inlang app like [Fink](https://inlang.com/m/tdozzpar) or the [VSCode extension Sherlock](https://inlang.com/m/r7kp499g).
57
- *
58
- * @param {{}} inputs
59
- * @param {{ locale?: "en" | "pl" }} options
60
- * @returns {string}
61
- */
62
- /* @__NO_SIDE_EFFECTS__ */
63
- const login_please_login = (inputs = {}, options = {}) => {
64
- if (experimentalMiddlewareLocaleSplitting && isServer === false) {
65
- return /** @type {any} */ (globalThis).__paraglide_ssr.login_please_login(inputs)
66
- }
67
- const locale = options.locale ?? getLocale()
68
- trackMessageCall("login_please_login", locale)
69
- if (locale === "en") return en.login_please_login(inputs)
70
- return pl.login_please_login(inputs)
71
- };
72
- export { login_please_login as "login.please_login" }
2
+ export * from './hello_world.js'
3
+ export * from './login_hello.js'
4
+ export * from './login_please_login.js'
@@ -0,0 +1,5 @@
1
+ export function hello_world(inputs: {
2
+ name: NonNullable<unknown>;
3
+ }, options?: {
4
+ locale?: "en" | "pl";
5
+ }): string;
@@ -0,0 +1,33 @@
1
+ /* eslint-disable */
2
+ import { getLocale, trackMessageCall, experimentalMiddlewareLocaleSplitting, isServer } from '../runtime.js';
3
+
4
+ const en_hello_world = /** @type {(inputs: { name: NonNullable<unknown> }) => string} */ (i) => {
5
+ return `Hello, ${i.name} from en!`
6
+ };
7
+
8
+ const pl_hello_world = /** @type {(inputs: { name: NonNullable<unknown> }) => string} */ (i) => {
9
+ return `Hello, ${i.name} from pl!`
10
+ };
11
+
12
+ /**
13
+ * This function has been compiled by [Paraglide JS](https://inlang.com/m/gerre34r).
14
+ *
15
+ * - Changing this function will be over-written by the next build.
16
+ *
17
+ * - If you want to change the translations, you can either edit the source files e.g. `en.json`, or
18
+ * use another inlang app like [Fink](https://inlang.com/m/tdozzpar) or the [VSCode extension Sherlock](https://inlang.com/m/r7kp499g).
19
+ *
20
+ * @param {{ name: NonNullable<unknown> }} inputs
21
+ * @param {{ locale?: "en" | "pl" }} options
22
+ * @returns {string}
23
+ */
24
+ /* @__NO_SIDE_EFFECTS__ */
25
+ export const hello_world = (inputs, options = {}) => {
26
+ if (experimentalMiddlewareLocaleSplitting && isServer === false) {
27
+ return /** @type {any} */ (globalThis).__paraglide_ssr.hello_world(inputs)
28
+ }
29
+ const locale = options.locale ?? getLocale()
30
+ trackMessageCall("hello_world", locale)
31
+ if (locale === "en") return en_hello_world(inputs)
32
+ return pl_hello_world(inputs)
33
+ };
@@ -0,0 +1,16 @@
1
+ export { login_hello as login.hello };
2
+ /**
3
+ * This function has been compiled by [Paraglide JS](https://inlang.com/m/gerre34r).
4
+ *
5
+ * - Changing this function will be over-written by the next build.
6
+ *
7
+ * - If you want to change the translations, you can either edit the source files e.g. `en.json`, or
8
+ * use another inlang app like [Fink](https://inlang.com/m/tdozzpar) or the [VSCode extension Sherlock](https://inlang.com/m/r7kp499g).
9
+ *
10
+ * @param {{}} inputs
11
+ * @param {{ locale?: "en" | "pl" }} options
12
+ * @returns {string}
13
+ */
14
+ declare function login_hello(inputs?: {}, options?: {
15
+ locale?: "en" | "pl";
16
+ }): string;
@@ -0,0 +1,34 @@
1
+ /* eslint-disable */
2
+ import { getLocale, trackMessageCall, experimentalMiddlewareLocaleSplitting, isServer } from '../runtime.js';
3
+
4
+ const en_login_hello = /** @type {(inputs: {}) => string} */ () => {
5
+ return `Welcome back`
6
+ };
7
+
8
+ const pl_login_hello = /** @type {(inputs: {}) => string} */ () => {
9
+ return `Witaj ponownie`
10
+ };
11
+
12
+ /**
13
+ * This function has been compiled by [Paraglide JS](https://inlang.com/m/gerre34r).
14
+ *
15
+ * - Changing this function will be over-written by the next build.
16
+ *
17
+ * - If you want to change the translations, you can either edit the source files e.g. `en.json`, or
18
+ * use another inlang app like [Fink](https://inlang.com/m/tdozzpar) or the [VSCode extension Sherlock](https://inlang.com/m/r7kp499g).
19
+ *
20
+ * @param {{}} inputs
21
+ * @param {{ locale?: "en" | "pl" }} options
22
+ * @returns {string}
23
+ */
24
+ /* @__NO_SIDE_EFFECTS__ */
25
+ const login_hello = (inputs = {}, options = {}) => {
26
+ if (experimentalMiddlewareLocaleSplitting && isServer === false) {
27
+ return /** @type {any} */ (globalThis).__paraglide_ssr.login_hello(inputs)
28
+ }
29
+ const locale = options.locale ?? getLocale()
30
+ trackMessageCall("login_hello", locale)
31
+ if (locale === "en") return en_login_hello(inputs)
32
+ return pl_login_hello(inputs)
33
+ };
34
+ export { login_hello as "login.hello" }
@@ -0,0 +1,16 @@
1
+ export { login_please_login as login.please_login };
2
+ /**
3
+ * This function has been compiled by [Paraglide JS](https://inlang.com/m/gerre34r).
4
+ *
5
+ * - Changing this function will be over-written by the next build.
6
+ *
7
+ * - If you want to change the translations, you can either edit the source files e.g. `en.json`, or
8
+ * use another inlang app like [Fink](https://inlang.com/m/tdozzpar) or the [VSCode extension Sherlock](https://inlang.com/m/r7kp499g).
9
+ *
10
+ * @param {{}} inputs
11
+ * @param {{ locale?: "en" | "pl" }} options
12
+ * @returns {string}
13
+ */
14
+ declare function login_please_login(inputs?: {}, options?: {
15
+ locale?: "en" | "pl";
16
+ }): string;
@@ -0,0 +1,34 @@
1
+ /* eslint-disable */
2
+ import { getLocale, trackMessageCall, experimentalMiddlewareLocaleSplitting, isServer } from '../runtime.js';
3
+
4
+ const en_login_please_login = /** @type {(inputs: {}) => string} */ () => {
5
+ return `Login to your account`
6
+ };
7
+
8
+ const pl_login_please_login = /** @type {(inputs: {}) => string} */ () => {
9
+ return `Zaloguj się na swoje konto`
10
+ };
11
+
12
+ /**
13
+ * This function has been compiled by [Paraglide JS](https://inlang.com/m/gerre34r).
14
+ *
15
+ * - Changing this function will be over-written by the next build.
16
+ *
17
+ * - If you want to change the translations, you can either edit the source files e.g. `en.json`, or
18
+ * use another inlang app like [Fink](https://inlang.com/m/tdozzpar) or the [VSCode extension Sherlock](https://inlang.com/m/r7kp499g).
19
+ *
20
+ * @param {{}} inputs
21
+ * @param {{ locale?: "en" | "pl" }} options
22
+ * @returns {string}
23
+ */
24
+ /* @__NO_SIDE_EFFECTS__ */
25
+ const login_please_login = (inputs = {}, options = {}) => {
26
+ if (experimentalMiddlewareLocaleSplitting && isServer === false) {
27
+ return /** @type {any} */ (globalThis).__paraglide_ssr.login_please_login(inputs)
28
+ }
29
+ const locale = options.locale ?? getLocale()
30
+ trackMessageCall("login_please_login", locale)
31
+ if (locale === "en") return en_login_please_login(inputs)
32
+ return pl_login_please_login(inputs)
33
+ };
34
+ export { login_please_login as "login.please_login" }
@@ -65,5 +65,13 @@ export function includioCMS(cmsConfig) {
65
65
  handles.push(handleAuth);
66
66
  }
67
67
  handles.push(adminGuard);
68
+ handles.push(securityHeaders);
68
69
  return handles;
69
70
  }
71
+ const securityHeaders = async ({ event, resolve }) => {
72
+ const response = await resolve(event);
73
+ response.headers.set('X-Content-Type-Options', 'nosniff');
74
+ response.headers.set('X-Frame-Options', 'DENY');
75
+ response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
76
+ return response;
77
+ };
@@ -0,0 +1,2 @@
1
+ import type { CmsUpdate } from '../index.js';
2
+ export declare const update: CmsUpdate;
@@ -0,0 +1,21 @@
1
+ export const update = {
2
+ version: '0.13.3',
3
+ date: '2026-03-23',
4
+ description: 'Security hardening, per-language content fixes',
5
+ features: [
6
+ 'Timing-safe API key comparison (prevents timing attacks)',
7
+ 'MIME type blocklist on upload and file replace endpoints',
8
+ 'Security headers: X-Content-Type-Options, X-Frame-Options, Referrer-Policy',
9
+ 'CMS constructor validates that at least one language is configured'
10
+ ],
11
+ fixes: [
12
+ 'Form submission rate limiting applies to all requests (not just multipart uploads)',
13
+ 'Form submission uses SvelteKit getClientAddress() instead of spoofable x-forwarded-for header',
14
+ 'Removed demo reset endpoint with hardcoded fallback secret',
15
+ 'Added .catch() handlers for unhandled promise rejections in admin components',
16
+ 'Per-language content language state and entry label fixes'
17
+ ],
18
+ breakingChanges: [
19
+ 'CMS config with empty languages array now throws an error at initialization'
20
+ ]
21
+ };
@@ -35,7 +35,8 @@ import { update as update0120 } from './0.12.0/index.js';
35
35
  import { update as update0130 } from './0.13.0/index.js';
36
36
  import { update as update0131 } from './0.13.1/index.js';
37
37
  import { update as update0132 } from './0.13.2/index.js';
38
- export const updates = [update0065, update0066, update0067, update0068, update0069, update010, update011, update012, update013, update014, update015, update020, update022, update050, update051, update052, update053, update054, update055, update056, update057, update058, update060, update061, update062, update070, update071, update072, update073, update080, update090, update0100, update0110, update0120, update0130, update0131, update0132];
38
+ import { update as update0133 } from './0.13.3/index.js';
39
+ export const updates = [update0065, update0066, update0067, update0068, update0069, update010, update011, update012, update013, update014, update015, update020, update022, update050, update051, update052, update053, update054, update055, update056, update057, update058, update060, update061, update062, update070, update071, update072, update073, update080, update090, update0100, update0110, update0120, update0130, update0131, update0132, update0133];
39
40
  export const getUpdatesFrom = (fromVersion) => {
40
41
  const fromParts = fromVersion.split('.').map(Number);
41
42
  return updates.filter((update) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "includio-cms",
3
- "version": "0.13.2",
3
+ "version": "0.13.3",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && npm run prepack",
@@ -1,17 +0,0 @@
1
- import type { Field } from '../../types/fields.js';
2
- export type TranslationCompleteness = 'complete' | 'partial' | 'empty';
3
- export interface LangStatus {
4
- filled: number;
5
- total: number;
6
- percentage: number;
7
- status: TranslationCompleteness;
8
- missingFields: {
9
- slug: string;
10
- label: string;
11
- }[];
12
- }
13
- /**
14
- * Compute translation status for each language.
15
- * Checks all localized fields (text, richtext, content) + seo/url sub-fields.
16
- */
17
- export declare function computeTranslationStatus(data: Record<string, unknown>, fields: Field[], languages: string[]): Record<string, LangStatus>;
@@ -1,133 +0,0 @@
1
- import { getAtPath } from './objectPath.js';
2
- /** Extract a label string from Localized, preferring pl then en then slug. */
3
- function extractLabel(label, fallback) {
4
- if (!label)
5
- return fallback;
6
- if (typeof label === 'string')
7
- return label;
8
- return label.pl || label.en || fallback;
9
- }
10
- /** Check if a value counts as "filled" for a given field type. */
11
- function isFieldFilled(value, fieldType) {
12
- if (value == null)
13
- return false;
14
- switch (fieldType) {
15
- case 'text':
16
- return typeof value === 'string' && value.length > 0;
17
- case 'content': {
18
- if (typeof value !== 'object')
19
- return false;
20
- const doc = value;
21
- return doc.type === 'doc' && Array.isArray(doc.content) && doc.content.length > 0;
22
- }
23
- case 'seo': {
24
- if (typeof value !== 'object')
25
- return false;
26
- const seo = value;
27
- // At least title or description filled
28
- return ((typeof seo.title === 'string' && seo.title.length > 0) ||
29
- (typeof seo.description === 'string' && seo.description.length > 0));
30
- }
31
- case 'url': {
32
- if (typeof value !== 'object')
33
- return false;
34
- const url = value;
35
- return typeof url.url === 'string' && url.url.length > 0;
36
- }
37
- default:
38
- return false;
39
- }
40
- }
41
- /** Collect all localized fields from a field list, including nested seo/url sub-fields. */
42
- function collectLocalizedFields(fields) {
43
- const result = [];
44
- for (const field of fields) {
45
- if (field.localized === false)
46
- continue;
47
- const label = extractLabel(field.label, field.slug);
48
- if (field.type === 'text' || field.type === 'content') {
49
- result.push({ slug: field.slug, label, type: field.type, required: !!field.required });
50
- }
51
- }
52
- // SEO and URL fields are always localized by nature (sub-fields are lang-keyed)
53
- for (const field of fields) {
54
- if (field.type === 'seo') {
55
- const label = extractLabel(field.label, field.slug);
56
- result.push({ slug: field.slug, label, type: 'seo', required: !!field.required });
57
- }
58
- if (field.type === 'url') {
59
- const label = extractLabel(field.label, field.slug);
60
- result.push({ slug: field.slug, label, type: 'url', required: !!field.required });
61
- }
62
- }
63
- return result;
64
- }
65
- /** Resolve the value of a field for a given language. */
66
- function resolveFieldValue(data, field, lang) {
67
- if (field.type === 'seo') {
68
- const seoData = getAtPath(data, field.slug);
69
- if (seoData && typeof seoData === 'object') {
70
- const seo = seoData;
71
- const title = extractLangValue(seo.title, lang);
72
- const desc = extractLangValue(seo.description, lang);
73
- return { title, description: desc };
74
- }
75
- return undefined;
76
- }
77
- else if (field.type === 'url') {
78
- const urlData = getAtPath(data, field.slug);
79
- if (urlData && typeof urlData === 'object') {
80
- const url = urlData;
81
- const urlVal = url.url && typeof url.url === 'object'
82
- ? url.url[lang]
83
- : undefined;
84
- return { url: urlVal || '' };
85
- }
86
- return undefined;
87
- }
88
- else {
89
- const fieldData = getAtPath(data, field.slug);
90
- if (fieldData && typeof fieldData === 'object' && !Array.isArray(fieldData)) {
91
- return fieldData[lang];
92
- }
93
- return undefined;
94
- }
95
- }
96
- /**
97
- * Compute translation status for each language.
98
- * Checks all localized fields (text, richtext, content) + seo/url sub-fields.
99
- */
100
- export function computeTranslationStatus(data, fields, languages) {
101
- const allLocalizedFields = collectLocalizedFields(fields);
102
- // Pre-filter: keep field if required OR has content in at least one language
103
- const localizedFields = allLocalizedFields.filter((field) => field.required ||
104
- languages.some((lang) => isFieldFilled(resolveFieldValue(data, field, lang), field.type)));
105
- const result = {};
106
- for (const lang of languages) {
107
- let filled = 0;
108
- const total = localizedFields.length;
109
- const missingFields = [];
110
- for (const field of localizedFields) {
111
- const value = resolveFieldValue(data, field, lang);
112
- if (isFieldFilled(value, field.type)) {
113
- filled++;
114
- }
115
- else {
116
- missingFields.push({ slug: field.slug, label: field.label });
117
- }
118
- }
119
- const percentage = total === 0 ? 100 : Math.round((filled / total) * 100);
120
- const status = percentage === 100 ? 'complete' : percentage === 0 ? 'empty' : 'partial';
121
- result[lang] = { filled, total, percentage, status, missingFields };
122
- }
123
- return result;
124
- }
125
- /** Extract lang value from a possibly lang-keyed value. */
126
- function extractLangValue(value, lang) {
127
- if (typeof value === 'string')
128
- return value;
129
- if (value && typeof value === 'object') {
130
- return (value[lang]) || '';
131
- }
132
- return '';
133
- }
@@ -1 +0,0 @@
1
- export declare function resetDemoData(): Promise<void>;