includio-cms 0.13.0 → 0.13.2
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 +36 -0
- package/ROADMAP.md +24 -0
- package/dist/admin/api/handler.js +2 -0
- package/dist/admin/api/replace.js +2 -1
- package/dist/admin/api/rest/routes/upload.js +2 -1
- package/dist/admin/api/upload-limit.d.ts +2 -0
- package/dist/admin/api/upload-limit.js +7 -0
- package/dist/admin/api/upload.js +2 -1
- package/dist/admin/client/collection/collection-entries.svelte +58 -11
- package/dist/admin/client/users/users-page.svelte +5 -6
- package/dist/admin/client/users/users-page.svelte.d.ts +1 -4
- package/dist/admin/components/fields/block-picker-modal.svelte +13 -4
- package/dist/admin/components/fields/blocks-field.svelte +31 -9
- package/dist/admin/components/fields/simple-array-field.svelte +22 -11
- package/dist/admin/components/layout/layout-renderer.svelte +10 -4
- package/dist/admin/components/media/file-preview.svelte +10 -1
- package/dist/admin/components/media/file-upload.svelte +66 -9
- package/dist/admin/components/media/files-list.svelte +12 -3
- package/dist/admin/components/media/media-selector.svelte +11 -5
- package/dist/admin/components/tiptap/FigureNodeView.svelte +15 -10
- package/dist/admin/components/tiptap/InlineBlockNodeView.svelte +32 -1
- package/dist/admin/components/tiptap/SlashCommandPopup.svelte +8 -3
- package/dist/admin/components/tiptap/editor-toolbar.svelte +28 -23
- package/dist/admin/components/tiptap/image-dialog.svelte +12 -7
- package/dist/admin/components/tiptap/lang.d.ts +77 -0
- package/dist/admin/components/tiptap/lang.js +170 -0
- package/dist/admin/components/tiptap/link-dialog.svelte +22 -18
- package/dist/admin/components/tiptap/slash-command.js +26 -22
- package/dist/admin/components/tiptap/table-dialog.svelte +9 -4
- package/dist/admin/components/tiptap/video-dialog.svelte +6 -1
- package/dist/admin/remote/email.remote.d.ts +1 -0
- package/dist/admin/remote/email.remote.js +5 -0
- package/dist/admin/remote/entry.remote.d.ts +1 -0
- package/dist/admin/remote/entry.remote.js +6 -4
- package/dist/admin/remote/index.d.ts +1 -0
- package/dist/admin/remote/index.js +1 -0
- package/dist/admin/remote/reorder.d.ts +1 -0
- package/dist/admin/remote/reorder.js +33 -0
- package/dist/core/server/entries/operations/get.js +15 -3
- package/dist/core/server/fields/utils/imageStyles.js +7 -3
- package/dist/core/server/generator/fields.js +2 -2
- package/dist/core/server/generator/generator.js +4 -2
- package/dist/core/server/media/styles/operations/createMediaStyle.d.ts +2 -2
- package/dist/core/server/media/styles/operations/createMediaStyle.js +5 -3
- package/dist/core/server/media/styles/operations/generateDefaultStyles.js +33 -6
- package/dist/core/server/media/styles/operations/getImageStyle.d.ts +1 -0
- package/dist/core/server/media/styles/operations/getImageStyle.js +3 -0
- package/dist/core/server/media/styles/sharp/generateImageStyle.d.ts +2 -1
- package/dist/core/server/media/styles/sharp/generateImageStyle.js +5 -3
- package/dist/core/server/media/uploadLimit.d.ts +2 -0
- package/dist/core/server/media/uploadLimit.js +26 -0
- package/dist/types/entries.d.ts +1 -0
- package/dist/types/layout.d.ts +0 -1
- package/dist/updates/0.13.1/index.d.ts +2 -0
- package/dist/updates/0.13.1/index.js +20 -0
- package/dist/updates/0.13.2/index.d.ts +2 -0
- package/dist/updates/0.13.2/index.js +20 -0
- package/dist/updates/index.js +3 -1
- package/package.json +1 -1
|
@@ -119,7 +119,7 @@ export const getEntries = async (options = {}) => {
|
|
|
119
119
|
const slugPath = getEntrySlugPath(entry.slug);
|
|
120
120
|
const slug = getSlugFromEntryData(version.data, slugPath, language);
|
|
121
121
|
const _url = slug ? getEntryPath(entry.slug, slug) : undefined;
|
|
122
|
-
|
|
122
|
+
const result = {
|
|
123
123
|
_id: entry.id,
|
|
124
124
|
_slug: entry.slug,
|
|
125
125
|
_type: entry.type,
|
|
@@ -127,6 +127,10 @@ export const getEntries = async (options = {}) => {
|
|
|
127
127
|
_url,
|
|
128
128
|
...populatedData
|
|
129
129
|
};
|
|
130
|
+
if (config.type === 'collection' && config.orderable) {
|
|
131
|
+
result._sortOrder = entry.sortOrder;
|
|
132
|
+
}
|
|
133
|
+
return result;
|
|
130
134
|
}
|
|
131
135
|
catch (error) {
|
|
132
136
|
console.error(`[CMS] Failed to populate entry ${entry.id} (${entry.slug}):`, error);
|
|
@@ -206,7 +210,7 @@ export const getEntries = async (options = {}) => {
|
|
|
206
210
|
const slugPath = getEntrySlugPath(dbEntry.slug);
|
|
207
211
|
const slug = getSlugFromEntryData(version.data, slugPath, language);
|
|
208
212
|
const _url = slug ? getEntryPath(dbEntry.slug, slug) : undefined;
|
|
209
|
-
|
|
213
|
+
const result = {
|
|
210
214
|
_id: dbEntry.id,
|
|
211
215
|
_slug: dbEntry.slug,
|
|
212
216
|
_type: dbEntry.type,
|
|
@@ -214,6 +218,10 @@ export const getEntries = async (options = {}) => {
|
|
|
214
218
|
_url,
|
|
215
219
|
...populatedData
|
|
216
220
|
};
|
|
221
|
+
if (config.type === 'collection' && config.orderable) {
|
|
222
|
+
result._sortOrder = dbEntry.sortOrder;
|
|
223
|
+
}
|
|
224
|
+
return result;
|
|
217
225
|
}
|
|
218
226
|
catch (error) {
|
|
219
227
|
console.error(`[CMS] Failed to populate entry ${dbEntry.id} (${dbEntry.slug}):`, error);
|
|
@@ -349,7 +357,7 @@ export const getEntryVersion = async (options) => {
|
|
|
349
357
|
const slugPath = getEntrySlugPath(dbEntry.slug);
|
|
350
358
|
const entrySlug = getSlugFromEntryData(dbEntryVersion.data, slugPath, language);
|
|
351
359
|
const _url = entrySlug ? getEntryPath(dbEntry.slug, entrySlug) : undefined;
|
|
352
|
-
|
|
360
|
+
const result = {
|
|
353
361
|
_id: dbEntry.id,
|
|
354
362
|
_slug: dbEntry.slug,
|
|
355
363
|
_type: dbEntry.type,
|
|
@@ -357,6 +365,10 @@ export const getEntryVersion = async (options) => {
|
|
|
357
365
|
_url,
|
|
358
366
|
...populatedData
|
|
359
367
|
};
|
|
368
|
+
if (config.type === 'collection' && config.orderable) {
|
|
369
|
+
result._sortOrder = dbEntry.sortOrder;
|
|
370
|
+
}
|
|
371
|
+
return result;
|
|
360
372
|
}
|
|
361
373
|
catch (error) {
|
|
362
374
|
console.error(`[CMS] Failed to populate entry ${dbEntry.id} (${dbEntry.slug}):`, error);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getImageStyleIfExists } from '../../media/styles/operations/getImageStyle.js';
|
|
2
2
|
import { getCMS } from '../../../cms.js';
|
|
3
3
|
import { generateBlurDataUrl } from '../../media/utils/generateBlurDataUrl.js';
|
|
4
4
|
export const defaultStyles = [
|
|
@@ -67,7 +67,9 @@ export async function getImageStyles(field, val) {
|
|
|
67
67
|
const [styles, blurDataUrl] = await Promise.all([
|
|
68
68
|
Promise.all(stylesArr.map(async (style) => {
|
|
69
69
|
try {
|
|
70
|
-
const styleDbData = await
|
|
70
|
+
const styleDbData = await getImageStyleIfExists(val.id, style);
|
|
71
|
+
if (!styleDbData)
|
|
72
|
+
return null;
|
|
71
73
|
const result = {
|
|
72
74
|
url: styleDbData.url,
|
|
73
75
|
media: styleDbData.media,
|
|
@@ -86,7 +88,9 @@ export async function getImageStyles(field, val) {
|
|
|
86
88
|
srcset: undefined,
|
|
87
89
|
sizes: undefined
|
|
88
90
|
};
|
|
89
|
-
const variantData = await
|
|
91
|
+
const variantData = await getImageStyleIfExists(val.id, variantStyle);
|
|
92
|
+
if (!variantData)
|
|
93
|
+
return null;
|
|
90
94
|
return `${variantData.url} ${w}w`;
|
|
91
95
|
}
|
|
92
96
|
catch (e) {
|
|
@@ -103,8 +103,8 @@ function getFlatFieldTypeAsString(field) {
|
|
|
103
103
|
switch (field.type) {
|
|
104
104
|
case 'media': {
|
|
105
105
|
const base = field.multiple
|
|
106
|
-
? '(
|
|
107
|
-
: '
|
|
106
|
+
? '(ImageFieldData | VideoFieldData)[]'
|
|
107
|
+
: 'ImageFieldData | VideoFieldData';
|
|
108
108
|
return base + (field.required ? '' : ' | null');
|
|
109
109
|
}
|
|
110
110
|
case 'relation': {
|
|
@@ -19,6 +19,8 @@ function generateTypesStringForRecords(type, records) {
|
|
|
19
19
|
const fieldsType = generateFlatTsTypeFromFields(getFieldsFromConfig(single));
|
|
20
20
|
// Strip outer braces to inline fields into the interface
|
|
21
21
|
const innerFields = fieldsType.replace(/^\s*\{/, '').replace(/\}\s*$/, '');
|
|
22
|
+
const sortOrderField = type === 'collection' && 'orderable' in single && single.orderable
|
|
23
|
+
? '_sortOrder: number;\n\t\t\t\t' : '';
|
|
22
24
|
return `
|
|
23
25
|
export interface ${toPascalCase(single.slug)} {
|
|
24
26
|
_id: string;
|
|
@@ -26,7 +28,7 @@ function generateTypesStringForRecords(type, records) {
|
|
|
26
28
|
_type: string;
|
|
27
29
|
_publishedAt: Date | null;
|
|
28
30
|
_url?: string;
|
|
29
|
-
${innerFields}
|
|
31
|
+
${sortOrderField}${innerFields}
|
|
30
32
|
}
|
|
31
33
|
`;
|
|
32
34
|
})
|
|
@@ -108,7 +110,7 @@ function generateTypes(config) {
|
|
|
108
110
|
const cmsDir = join(process.cwd(), 'src/lib/cms/runtime');
|
|
109
111
|
const filePath = join(cmsDir, 'types.ts');
|
|
110
112
|
let code = `// This file is auto-generated. Do not edit directly.\n\n`;
|
|
111
|
-
code += `import type { MediaFile, ImageFieldData, VideoFieldData,
|
|
113
|
+
code += `import type { MediaFile, ImageFieldData, VideoFieldData, StructuredContentDoc, SCTypedInlineBlock } from 'includio-cms/types';\n\n`;
|
|
112
114
|
if (config.singles && config.singles.length > 0) {
|
|
113
115
|
code += generateTypesStringForRecords('single', Object.values(config.singles));
|
|
114
116
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { ImageFieldStyle } from '../../../../../types/fields.js';
|
|
2
|
-
import type { ImageStyle } from '../../../../../types/media.js';
|
|
3
|
-
export declare function createImageStyle(mediaFileId: string, style: ImageFieldStyle): Promise<ImageStyle>;
|
|
2
|
+
import type { ImageStyle, MediaFile } from '../../../../../types/media.js';
|
|
3
|
+
export declare function createImageStyle(mediaFileId: string, style: ImageFieldStyle, buffer?: Buffer, mediaFile?: MediaFile): Promise<ImageStyle>;
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { getCMS } from '../../../../cms.js';
|
|
2
|
-
import { generateImageStyle } from '../sharp/generateImageStyle.js';
|
|
3
|
-
export async function createImageStyle(mediaFileId, style) {
|
|
2
|
+
import { generateImageStyle, generateImageStyleFromBuffer } from '../sharp/generateImageStyle.js';
|
|
3
|
+
export async function createImageStyle(mediaFileId, style, buffer, mediaFile) {
|
|
4
4
|
const cms = getCMS();
|
|
5
5
|
// Check for existing style to clean up old file after upsert
|
|
6
6
|
const existing = await cms.databaseAdapter.getImageStyle(mediaFileId, style);
|
|
7
7
|
const oldUrl = existing?.url;
|
|
8
|
-
const imageStyleFile =
|
|
8
|
+
const imageStyleFile = buffer && mediaFile
|
|
9
|
+
? await generateImageStyleFromBuffer(buffer, mediaFile, style)
|
|
10
|
+
: await generateImageStyle(mediaFileId, style);
|
|
9
11
|
const imageStyle = await cms.databaseAdapter.createImageStyle(mediaFileId, imageStyleFile, style);
|
|
10
12
|
// Delete old file from disk if URL changed
|
|
11
13
|
if (oldUrl && oldUrl !== imageStyle.url) {
|
|
@@ -1,16 +1,35 @@
|
|
|
1
|
+
import { getCMS } from '../../../../cms.js';
|
|
1
2
|
import { defaultStyles, isProcessableImage, expandStyleFormats, getOriginalFormat } from '../../../fields/utils/imageStyles.js';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
import { createImageStyle } from './createMediaStyle.js';
|
|
4
|
+
const CONCURRENCY = 3;
|
|
5
|
+
async function runWithConcurrency(tasks, limit) {
|
|
6
|
+
const results = new Array(tasks.length);
|
|
7
|
+
let i = 0;
|
|
8
|
+
async function next() {
|
|
9
|
+
while (i < tasks.length) {
|
|
10
|
+
const idx = i++;
|
|
11
|
+
results[idx] = await tasks[idx]();
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
await Promise.all(Array.from({ length: Math.min(limit, tasks.length) }, () => next()));
|
|
15
|
+
return results;
|
|
16
|
+
}
|
|
17
|
+
async function downloadOriginalBuffer(mediaFile) {
|
|
18
|
+
const file = await getCMS().filesAdapter.downloadFile(mediaFile.url.split('/').pop() || '');
|
|
19
|
+
if (!file)
|
|
20
|
+
throw new Error('Media file not found');
|
|
21
|
+
return Buffer.from(await file.arrayBuffer());
|
|
22
|
+
}
|
|
23
|
+
function collectStyleTasks(mediaFile) {
|
|
6
24
|
const origFormat = getOriginalFormat(mediaFile);
|
|
7
25
|
const expanded = expandStyleFormats(defaultStyles, origFormat);
|
|
26
|
+
const tasks = [];
|
|
8
27
|
for (const style of expanded) {
|
|
9
|
-
|
|
28
|
+
tasks.push(style);
|
|
10
29
|
if (style.srcset && mediaFile.width) {
|
|
11
30
|
const widths = style.srcset.filter((w) => w <= mediaFile.width);
|
|
12
31
|
for (const w of widths) {
|
|
13
|
-
|
|
32
|
+
tasks.push({
|
|
14
33
|
...style,
|
|
15
34
|
name: `${style.name}_${w}w`,
|
|
16
35
|
width: w,
|
|
@@ -20,6 +39,14 @@ export async function generateDefaultStyles(mediaFile) {
|
|
|
20
39
|
}
|
|
21
40
|
}
|
|
22
41
|
}
|
|
42
|
+
return tasks;
|
|
43
|
+
}
|
|
44
|
+
export async function generateDefaultStyles(mediaFile) {
|
|
45
|
+
if (!isProcessableImage(mediaFile))
|
|
46
|
+
return;
|
|
47
|
+
const buffer = await downloadOriginalBuffer(mediaFile);
|
|
48
|
+
const styles = collectStyleTasks(mediaFile);
|
|
49
|
+
await runWithConcurrency(styles.map((style) => () => createImageStyle(mediaFile.id, style, buffer, mediaFile)), CONCURRENCY);
|
|
23
50
|
}
|
|
24
51
|
export function generateDefaultStylesInBackground(mediaFile) {
|
|
25
52
|
generateDefaultStyles(mediaFile).catch((e) => console.warn('Background style generation failed:', e));
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { ImageFieldStyle } from '../../../../../types/fields.js';
|
|
2
2
|
import type { ImageStyle } from '../../../../../types/media.js';
|
|
3
3
|
export declare function getImageStyle(mediaFileId: string, style: ImageFieldStyle): Promise<ImageStyle>;
|
|
4
|
+
export declare function getImageStyleIfExists(mediaFileId: string, style: ImageFieldStyle): Promise<ImageStyle | null>;
|
|
@@ -6,3 +6,6 @@ export async function getImageStyle(mediaFileId, style) {
|
|
|
6
6
|
return imageStyle;
|
|
7
7
|
return createImageStyle(mediaFileId, style);
|
|
8
8
|
}
|
|
9
|
+
export async function getImageStyleIfExists(mediaFileId, style) {
|
|
10
|
+
return getCMS().databaseAdapter.getImageStyle(mediaFileId, style);
|
|
11
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { ImageFieldStyle } from '../../../../../types/fields.js';
|
|
2
|
-
import type { UploadedMediaFile } from '../../../../../types/media.js';
|
|
2
|
+
import type { MediaFile, UploadedMediaFile } from '../../../../../types/media.js';
|
|
3
3
|
export declare function generateImageStyle(mediaFileId: string, style: ImageFieldStyle): Promise<UploadedMediaFile>;
|
|
4
|
+
export declare function generateImageStyleFromBuffer(buf: Buffer, mediaFile: MediaFile, style: ImageFieldStyle): Promise<UploadedMediaFile>;
|
|
@@ -14,8 +14,10 @@ export async function generateImageStyle(mediaFileId, style) {
|
|
|
14
14
|
if (!file) {
|
|
15
15
|
throw new Error('Media file not found');
|
|
16
16
|
}
|
|
17
|
-
const
|
|
18
|
-
|
|
17
|
+
const buf = Buffer.from(await file.arrayBuffer());
|
|
18
|
+
return generateImageStyleFromBuffer(buf, mediaFile, style);
|
|
19
|
+
}
|
|
20
|
+
export async function generateImageStyleFromBuffer(buf, mediaFile, style) {
|
|
19
21
|
// Read EXIF orientation before processing
|
|
20
22
|
const metadata = await sharp(buf).metadata();
|
|
21
23
|
// .rotate() applies EXIF orientation to pixels AND strips the tag from output.
|
|
@@ -51,7 +53,7 @@ export async function generateImageStyle(mediaFileId, style) {
|
|
|
51
53
|
const format = style.format ?? originalExt ?? 'jpeg';
|
|
52
54
|
sharpInstance = sharpInstance.toFormat(format, style.quality != null ? { quality: Math.max(1, Math.min(100, style.quality)) } : undefined);
|
|
53
55
|
const outputBuffer = await sharpInstance.toBuffer();
|
|
54
|
-
return getCMS().filesAdapter.uploadFile(new File([new Uint8Array(outputBuffer)], `${
|
|
56
|
+
return getCMS().filesAdapter.uploadFile(new File([new Uint8Array(outputBuffer)], `${mediaFile.id}_${style.name}_${Date.now().toString(36)}.${format}`, {
|
|
55
57
|
type: `image/${format}`
|
|
56
58
|
}));
|
|
57
59
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const SUFFIX_MULTIPLIERS = {
|
|
2
|
+
K: 1024,
|
|
3
|
+
M: 1024 * 1024,
|
|
4
|
+
G: 1024 * 1024 * 1024
|
|
5
|
+
};
|
|
6
|
+
const DEFAULT_LIMIT = '50M';
|
|
7
|
+
export function parseAsBytes(value) {
|
|
8
|
+
const trimmed = value.trim();
|
|
9
|
+
if (!trimmed)
|
|
10
|
+
return 0;
|
|
11
|
+
const suffix = trimmed.at(-1).toUpperCase();
|
|
12
|
+
const multiplier = SUFFIX_MULTIPLIERS[suffix];
|
|
13
|
+
if (multiplier) {
|
|
14
|
+
const num = Number(trimmed.slice(0, -1));
|
|
15
|
+
if (isNaN(num) || num < 0)
|
|
16
|
+
return 0;
|
|
17
|
+
return Math.floor(num * multiplier);
|
|
18
|
+
}
|
|
19
|
+
const num = Number(trimmed);
|
|
20
|
+
if (isNaN(num) || num < 0)
|
|
21
|
+
return 0;
|
|
22
|
+
return Math.floor(num);
|
|
23
|
+
}
|
|
24
|
+
export function getMaxUploadSize() {
|
|
25
|
+
return parseAsBytes(process.env.BODY_SIZE_LIMIT || DEFAULT_LIMIT);
|
|
26
|
+
}
|
package/dist/types/entries.d.ts
CHANGED
package/dist/types/layout.d.ts
CHANGED
|
@@ -31,7 +31,6 @@ export interface ColumnsNode extends LayoutNodeBase {
|
|
|
31
31
|
}
|
|
32
32
|
export interface CardNode extends LayoutNodeBase {
|
|
33
33
|
type: 'card';
|
|
34
|
-
label: Localized;
|
|
35
34
|
/**
|
|
36
35
|
* Field references — supports top-level slugs and dot-notation for object fields.
|
|
37
36
|
* @example ['companyInfo.name', 'companyInfo.motto'] — fields from different objects in one card
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const update = {
|
|
2
|
+
version: '0.13.1',
|
|
3
|
+
date: '2026-03-20',
|
|
4
|
+
description: 'Admin UI i18n, codegen cleanup, docs rewrite',
|
|
5
|
+
features: [
|
|
6
|
+
'Admin UI i18n — TipTap editor, blocks field, array field, media components support pl/en interface language',
|
|
7
|
+
'Inline block accordion labels — show field value in collapsed block header',
|
|
8
|
+
'Blocks field UrlFieldData label support',
|
|
9
|
+
'Email configuration remote endpoint'
|
|
10
|
+
],
|
|
11
|
+
fixes: [
|
|
12
|
+
'Codegen: remove unused FlatImageFieldData/FlatVideoFieldData, use ImageFieldData/VideoFieldData',
|
|
13
|
+
'Layout renderer: hide card header when label is empty',
|
|
14
|
+
'Collection entries: improved relation label fetching with UUID validation and multi-version support',
|
|
15
|
+
'Users page: client-side email config check, remove server load dependency',
|
|
16
|
+
'Entry remote: stricter UUID validation for ids parameter',
|
|
17
|
+
'Layout type: remove unused label property from layout node'
|
|
18
|
+
],
|
|
19
|
+
breakingChanges: []
|
|
20
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const update = {
|
|
2
|
+
version: '0.13.2',
|
|
3
|
+
date: '2026-03-20',
|
|
4
|
+
description: 'Upload limits, stable reordering, relation filters, image style optimization',
|
|
5
|
+
features: [
|
|
6
|
+
'Configurable upload size limit via BODY_SIZE_LIMIT env var (supports K/M/G suffixes, default 50M)',
|
|
7
|
+
'File upload pre-validation — rejects oversized files before upload with localized error messages',
|
|
8
|
+
'Stable slot reordering — preserves positions of filtered-out entries during DnD reorder',
|
|
9
|
+
'Collection relation field filtering — filter entries by relation field values',
|
|
10
|
+
'Expose _sortOrder on entries for orderable collections',
|
|
11
|
+
'Codegen: _sortOrder field in generated TypeScript interfaces for orderable collections',
|
|
12
|
+
'getImageStyleIfExists — non-blocking image style lookup (returns null if missing)'
|
|
13
|
+
],
|
|
14
|
+
fixes: [
|
|
15
|
+
'Concurrent image style generation with buffer reuse — download original once, generate styles in parallel (limit 3)',
|
|
16
|
+
'Image style resolution gracefully skips missing styles instead of blocking',
|
|
17
|
+
'Upload endpoints use configurable size limit instead of hardcoded 50MB'
|
|
18
|
+
],
|
|
19
|
+
breakingChanges: []
|
|
20
|
+
};
|
package/dist/updates/index.js
CHANGED
|
@@ -33,7 +33,9 @@ import { update as update0100 } from './0.10.0/index.js';
|
|
|
33
33
|
import { update as update0110 } from './0.11.0/index.js';
|
|
34
34
|
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
|
+
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];
|
|
37
39
|
export const getUpdatesFrom = (fromVersion) => {
|
|
38
40
|
const fromParts = fromVersion.split('.').map(Number);
|
|
39
41
|
return updates.filter((update) => {
|