From 597449c3a7dbfca0be085a4f9db173ebba1bedfb Mon Sep 17 00:00:00 2001 From: Sebastian Mertens Make Date: Fri, 29 May 2026 09:46:09 +0200 Subject: [PATCH 1/6] feat: add SDK app icon and visibility tools --- src/endpoints/sdk/apps.tools.ts | 186 +++++++++++++++++++++++++++++ src/endpoints/sdk/apps.ts | 64 ++++++++++ src/endpoints/sdk/modules.tools.ts | 68 +++++++++++ src/endpoints/sdk/modules.ts | 26 ++++ src/index.ts | 2 + src/make.ts | 23 +++- src/types.ts | 6 +- test/sdk/apps.spec.ts | 29 +++++ test/sdk/modules.spec.ts | 22 ++++ 9 files changed, 419 insertions(+), 7 deletions(-) diff --git a/src/endpoints/sdk/apps.tools.ts b/src/endpoints/sdk/apps.tools.ts index 26597ad..3cdc158 100644 --- a/src/endpoints/sdk/apps.tools.ts +++ b/src/endpoints/sdk/apps.tools.ts @@ -1,7 +1,44 @@ +import { readFile, writeFile } from 'node:fs/promises'; +import { basename } from 'node:path'; import type { Make } from '../../make.js'; import type { JSONValue } from '../../types.js'; import type { MakeTool } from '../../tools.js'; +type PngInfo = { + width: number; + height: number; +}; + +const PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); + +export function readPngInfo(buffer: Buffer): PngInfo { + if (buffer.length < 24 || !buffer.subarray(0, 8).equals(PNG_SIGNATURE)) { + throw new Error('Icon file must be a PNG image.'); + } + + const ihdrType = buffer.subarray(12, 16).toString('ascii'); + if (ihdrType !== 'IHDR') { + throw new Error('Icon file is not a valid PNG image: missing IHDR chunk.'); + } + + return { + width: buffer.readUInt32BE(16), + height: buffer.readUInt32BE(20), + }; +} + +function visibilityResult(scope: 'app', name: string, version: number, visibility: 'public' | 'private', response: JSONValue) { + return { + changed: true, + scope, + appName: name, + version, + visibility, + public: visibility === 'public', + response, + }; +} + export const tools: MakeTool[] = [ { name: 'sdk-apps_list', @@ -323,6 +360,155 @@ export const tools: MakeTool[] = [ return await make.sdk.apps.getCommon(args.name, args.version); }, }, + { + name: 'sdk-apps_set-icon', + title: 'Set SDK app icon', + description: 'Upload a PNG icon for a SDK app.', + category: 'sdk-apps', + scope: 'sdk-apps:write', + scopeId: undefined, + identifier: undefined, + annotations: { + idempotentHint: true, + destructiveHint: false, + }, + inputSchema: { + type: 'object', + properties: { + name: { type: 'string', description: 'The name of the app' }, + version: { type: 'number', description: 'The version of the app' }, + file: { type: 'string', description: 'Path to the PNG icon file to upload' }, + allowNon512: { type: 'boolean', description: 'Allow PNG icons that are not 512x512', default: false }, + sdkVersion: { type: 'string', description: 'Apps SDK version header', default: '2.5.0' }, + }, + required: ['name', 'version', 'file'], + }, + examples: [{ name: 'my-app', version: 1, file: './assets/icon.png' }], + execute: async ( + make: Make, + args: { name: string; version: number; file: string; allowNon512?: boolean; sdkVersion?: string }, + ) => { + const icon = await readFile(args.file); + const pngInfo = readPngInfo(icon); + if (!args.allowNon512 && (pngInfo.width !== 512 || pngInfo.height !== 512)) { + throw new Error( + `Icon must be 512x512 PNG. Got ${pngInfo.width}x${pngInfo.height}. ` + + 'Resize it first or pass allowNon512 intentionally.', + ); + } + + const changed = await make.sdk.apps.setIcon(args.name, args.version, icon, { sdkVersion: args.sdkVersion }); + return { + changed, + appName: args.name, + version: args.version, + file: basename(args.file), + width: pngInfo.width, + height: pngInfo.height, + readbackPath: `/sdk/apps/${args.name}/${args.version}/icon/512`, + }; + }, + }, + { + name: 'sdk-apps_get-icon', + title: 'Get SDK app icon', + description: 'Download a SDK app icon. Writes to outputFile when provided, otherwise returns base64 PNG data.', + category: 'sdk-apps', + scope: 'sdk-apps:read', + scopeId: undefined, + identifier: undefined, + annotations: { + readOnlyHint: true, + }, + inputSchema: { + type: 'object', + properties: { + name: { type: 'string', description: 'The name of the app' }, + version: { type: 'number', description: 'The version of the app' }, + outputFile: { type: 'string', description: 'Optional path to write the PNG icon' }, + size: { type: 'number', description: 'Icon size to download', default: 512 }, + sdkVersion: { type: 'string', description: 'Apps SDK version header', default: '2.5.0' }, + }, + required: ['name', 'version'], + }, + examples: [{ name: 'my-app', version: 1, outputFile: '/tmp/my-app-icon.png', size: 512 }], + execute: async ( + make: Make, + args: { name: string; version: number; outputFile?: string; size?: number; sdkVersion?: string }, + ) => { + const icon = Buffer.from( + await make.sdk.apps.getIcon(args.name, args.version, args.size ?? 512, { sdkVersion: args.sdkVersion }), + ); + const pngInfo = readPngInfo(icon); + + if (args.outputFile) { + await writeFile(args.outputFile, icon); + return { + appName: args.name, + version: args.version, + file: args.outputFile, + width: pngInfo.width, + height: pngInfo.height, + }; + } + + return { + appName: args.name, + version: args.version, + size: args.size ?? 512, + width: pngInfo.width, + height: pngInfo.height, + contentType: 'image/png', + dataBase64: icon.toString('base64'), + }; + }, + }, + { + name: 'sdk-apps_set-public', + title: 'Set SDK app public', + description: 'Mark a SDK app version as public.', + category: 'sdk-apps', + scope: 'sdk-apps:write', + scopeId: undefined, + identifier: undefined, + annotations: { idempotentHint: true, destructiveHint: false }, + inputSchema: { + type: 'object', + properties: { + name: { type: 'string', description: 'The name of the app' }, + version: { type: 'number', description: 'The version of the app' }, + }, + required: ['name', 'version'], + }, + examples: [{ name: 'my-app', version: 1 }], + execute: async (make: Make, args: { name: string; version: number }) => { + const response = await make.sdk.apps.makePublic(args.name, args.version); + return visibilityResult('app', args.name, args.version, 'public', response as JSONValue); + }, + }, + { + name: 'sdk-apps_set-private', + title: 'Set SDK app private', + description: 'Mark a SDK app version as private.', + category: 'sdk-apps', + scope: 'sdk-apps:write', + scopeId: undefined, + identifier: undefined, + annotations: { idempotentHint: true, destructiveHint: false }, + inputSchema: { + type: 'object', + properties: { + name: { type: 'string', description: 'The name of the app' }, + version: { type: 'number', description: 'The version of the app' }, + }, + required: ['name', 'version'], + }, + examples: [{ name: 'my-app', version: 1 }], + execute: async (make: Make, args: { name: string; version: number }) => { + const response = await make.sdk.apps.makePrivate(args.name, args.version); + return visibilityResult('app', args.name, args.version, 'private', response as JSONValue); + }, + }, { name: 'sdk-apps_set-common', title: 'Set SDK app common data', diff --git a/src/endpoints/sdk/apps.ts b/src/endpoints/sdk/apps.ts index a115416..59659d7 100644 --- a/src/endpoints/sdk/apps.ts +++ b/src/endpoints/sdk/apps.ts @@ -137,6 +137,16 @@ type UpdateSDKAppResponse = { app: Pick; }; +export type SDKAppVisibilityResponse = { + app?: SDKApp; + changed?: boolean; +}; + +type IconUploadResponse = { + success?: boolean; + changed?: boolean; +}; + /** * Class providing methods for working with Apps */ @@ -265,4 +275,58 @@ export class SDKApps { }); return response.changed; } + + /** + * Upload an app icon. Use PNG icon data for Make app icons. + */ + async setIcon( + name: string, + version: number, + iconData: Uint8Array | ArrayBuffer, + options: { contentType?: string; sdkVersion?: string } = {}, + ): Promise { + const response = await this.#fetch(`/sdk/apps/${name}/${version}/icon`, { + method: 'PUT', + headers: { + 'Content-Type': options.contentType ?? 'image/png', + 'imt-apps-sdk-version': options.sdkVersion ?? '2.5.0', + }, + body: iconData, + }); + + if (response && typeof response === 'object') { + return response.success ?? response.changed ?? true; + } + return true; + } + + /** + * Download an app icon at a given rendered size. + */ + async getIcon(name: string, version: number, size = 512, options: { sdkVersion?: string } = {}): Promise { + return await this.#fetch(`/sdk/apps/${name}/${version}/icon/${size}`, { + headers: { + 'imt-apps-sdk-version': options.sdkVersion ?? '2.5.0', + }, + responseType: 'arrayBuffer', + }); + } + + /** + * Make app private. + */ + async makePrivate(name: string, version: number): Promise { + return await this.#fetch(`/sdk/apps/${name}/${version}/private`, { + method: 'POST', + }); + } + + /** + * Make app public. + */ + async makePublic(name: string, version: number): Promise { + return await this.#fetch(`/sdk/apps/${name}/${version}/public`, { + method: 'POST', + }); + } } diff --git a/src/endpoints/sdk/modules.tools.ts b/src/endpoints/sdk/modules.tools.ts index ce14191..d7be613 100644 --- a/src/endpoints/sdk/modules.tools.ts +++ b/src/endpoints/sdk/modules.tools.ts @@ -1,6 +1,26 @@ import type { Make } from '../../make.js'; +import type { JSONValue } from '../../types.js'; import type { MakeTool } from '../../tools.js'; +function moduleVisibilityResult( + appName: string, + appVersion: number, + moduleName: string, + visibility: 'public' | 'private', + response: JSONValue, +) { + return { + changed: true, + scope: 'module', + appName, + version: appVersion, + moduleName, + visibility, + public: visibility === 'public', + response, + }; +} + export const tools: MakeTool[] = [ { name: 'sdk-modules_list', @@ -210,6 +230,54 @@ export const tools: MakeTool[] = [ return await make.sdk.modules.getSection(args.appName, args.appVersion, args.moduleName, args.section); }, }, + { + name: 'sdk-modules_set-public', + title: 'Set SDK module public', + description: 'Mark a SDK app module as public.', + category: 'sdk-modules', + scope: 'sdk-apps:write', + scopeId: undefined, + identifier: undefined, + annotations: { idempotentHint: true, destructiveHint: false }, + inputSchema: { + type: 'object', + properties: { + appName: { type: 'string', description: 'The name of the app' }, + appVersion: { type: 'number', description: 'The version of the app' }, + moduleName: { type: 'string', description: 'The name of the module' }, + }, + required: ['appName', 'appVersion', 'moduleName'], + }, + examples: [{ appName: 'my-app', appVersion: 1, moduleName: 'listItems' }], + execute: async (make: Make, args: { appName: string; appVersion: number; moduleName: string }) => { + const response = await make.sdk.modules.makePublic(args.appName, args.appVersion, args.moduleName); + return moduleVisibilityResult(args.appName, args.appVersion, args.moduleName, 'public', response as JSONValue); + }, + }, + { + name: 'sdk-modules_set-private', + title: 'Set SDK module private', + description: 'Mark a SDK app module as private.', + category: 'sdk-modules', + scope: 'sdk-apps:write', + scopeId: undefined, + identifier: undefined, + annotations: { idempotentHint: true, destructiveHint: false }, + inputSchema: { + type: 'object', + properties: { + appName: { type: 'string', description: 'The name of the app' }, + appVersion: { type: 'number', description: 'The version of the app' }, + moduleName: { type: 'string', description: 'The name of the module' }, + }, + required: ['appName', 'appVersion', 'moduleName'], + }, + examples: [{ appName: 'my-app', appVersion: 1, moduleName: 'listItems' }], + execute: async (make: Make, args: { appName: string; appVersion: number; moduleName: string }) => { + const response = await make.sdk.modules.makePrivate(args.appName, args.appVersion, args.moduleName); + return moduleVisibilityResult(args.appName, args.appVersion, args.moduleName, 'private', response as JSONValue); + }, + }, { name: 'sdk-modules_set-section', title: 'Set SDK module section', diff --git a/src/endpoints/sdk/modules.ts b/src/endpoints/sdk/modules.ts index c22f04b..0d7bd87 100644 --- a/src/endpoints/sdk/modules.ts +++ b/src/endpoints/sdk/modules.ts @@ -85,6 +85,12 @@ type UpdateSDKModuleResponse = { appModule: SDKModule; }; +export type SDKModuleVisibilityResponse = { + appModule?: SDKModule; + module?: SDKModule; + changed?: boolean; +}; + /** * Class providing methods for working with App Modules */ @@ -187,4 +193,24 @@ export class SDKModules { body: JSONStringifyIfNotString(body), }); } + + /** + * Make a module private. + */ + async makePrivate(appName: string, appVersion: number, moduleName: string): Promise { + return await this.#fetch( + `/sdk/apps/${appName}/${appVersion}/modules/${moduleName}/private`, + { method: 'POST' }, + ); + } + + /** + * Make a module public. + */ + async makePublic(appName: string, appVersion: number, moduleName: string): Promise { + return await this.#fetch( + `/sdk/apps/${appName}/${appVersion}/modules/${moduleName}/public`, + { method: 'POST' }, + ); + } } diff --git a/src/index.ts b/src/index.ts index dff4846..8b0d698 100644 --- a/src/index.ts +++ b/src/index.ts @@ -120,6 +120,7 @@ export type { SDKAppSection, SDKAppSectionType, SDKAppCommon, + SDKAppVisibilityResponse, ListSDKAppsOptions, GetSDKAppsOptions, CreateSDKAppBody, @@ -130,6 +131,7 @@ export type { SDKModules, SDKModuleSection, SDKModuleSectionType, + SDKModuleVisibilityResponse, CreateSDKModuleBody, UpdateSDKModuleBody, } from './endpoints/sdk/modules.js'; diff --git a/src/make.ts b/src/make.ts index 8e8a903..ded3c62 100644 --- a/src/make.ts +++ b/src/make.ts @@ -297,7 +297,7 @@ export class Make { // Success case if (res.status < 400) { - return this.handleResponse(res); + return this.handleResponse(res, options?.responseType); } // Create error for potential retry @@ -395,14 +395,17 @@ export class Make { * @protected */ protected prepareBody( - body: Record | Array | string | undefined, + body: Record | Array | string | Uint8Array | ArrayBuffer | undefined, headers: Record, - ): string { + ): string | Uint8Array | ArrayBuffer | undefined { + if (body instanceof Uint8Array || body instanceof ArrayBuffer) { + return body; + } if (body && typeof body !== 'string') { headers['content-type'] = 'application/json'; return JSON.stringify(body); } - return body as string; + return body as string | undefined; } /** @@ -493,7 +496,17 @@ export class Make { * @returns Promise resolving to the parsed response data * @protected */ - protected async handleResponse(response: Response): Promise { + protected async handleResponse(response: Response, responseType?: FetchOptions['responseType']): Promise { + if (responseType === 'arrayBuffer') { + return (await response.arrayBuffer()) as T; + } + if (responseType === 'text') { + return (await response.text()) as T; + } + if (responseType === 'json') { + return (await response.json()) as T; + } + const contentType = response.headers.get('content-type'); const isJsonType: boolean = Boolean( contentType === 'application/json' || contentType?.startsWith('application/json;'), diff --git a/src/types.ts b/src/types.ts index 5abeb2d..49ef676 100644 --- a/src/types.ts +++ b/src/types.ts @@ -31,10 +31,12 @@ export type FetchOptions = { headers?: Record; /** Query parameters to append to the URL */ query?: Record; - /** Request body as an object or string */ - body?: Record | Array | string; + /** Request body as an object, string, or raw binary payload */ + body?: Record | Array | string | Uint8Array | ArrayBuffer; /** HTTP method (GET, POST, PATCH, etc.) */ method?: string; + /** Override the default content-type based response parser */ + responseType?: 'json' | 'text' | 'arrayBuffer'; }; /** diff --git a/test/sdk/apps.spec.ts b/test/sdk/apps.spec.ts index b0ddaa1..1f1b319 100644 --- a/test/sdk/apps.spec.ts +++ b/test/sdk/apps.spec.ts @@ -144,4 +144,33 @@ describe('Endpoints: SDK > Apps', () => { const result = await make.sdk.apps.setCommon('test-app', 1, common); expect(result).toBe(true); }); + + it('Should upload app icon as raw PNG data', async () => { + const iconData = new Uint8Array([137, 80, 78, 71]); + mockFetch('PUT https://make.local/api/v2/sdk/apps/test-app/1/icon', null, req => { + expect(req.headers.get('content-type')).toBe('image/png'); + expect(req.headers.get('imt-apps-sdk-version')).toBe('2.5.0'); + }); + + const result = await make.sdk.apps.setIcon('test-app', 1, iconData); + expect(result).toBe(true); + }); + + it('Should download app icon as an ArrayBuffer', async () => { + mockFetch('GET https://make.local/api/v2/sdk/apps/test-app/1/icon/512', 'png-bytes'); + + const result = await make.sdk.apps.getIcon('test-app', 1); + expect(result).toBeInstanceOf(ArrayBuffer); + expect(Buffer.from(result).toString()).toBe('png-bytes'); + }); + + it('Should make app public and private', async () => { + mockFetch( + ['POST https://make.local/api/v2/sdk/apps/test-app/1/public', { changed: true }, undefined], + ['POST https://make.local/api/v2/sdk/apps/test-app/1/private', { changed: true }, undefined], + ); + + await expect(make.sdk.apps.makePublic('test-app', 1)).resolves.toStrictEqual({ changed: true }); + await expect(make.sdk.apps.makePrivate('test-app', 1)).resolves.toStrictEqual({ changed: true }); + }); }); diff --git a/test/sdk/modules.spec.ts b/test/sdk/modules.spec.ts index d771bfb..d56f4f4 100644 --- a/test/sdk/modules.spec.ts +++ b/test/sdk/modules.spec.ts @@ -115,4 +115,26 @@ describe('Endpoints: SDK > Modules', () => { await make.sdk.modules.setSection(appName, appVersion, moduleName, section, body); }); + + it('Should make module public and private', async () => { + mockFetch( + [ + `POST https://make.local/api/v2/sdk/apps/${appName}/${appVersion}/modules/${moduleName}/public`, + { changed: true }, + undefined, + ], + [ + `POST https://make.local/api/v2/sdk/apps/${appName}/${appVersion}/modules/${moduleName}/private`, + { changed: true }, + undefined, + ], + ); + + await expect(make.sdk.modules.makePublic(appName, appVersion, moduleName)).resolves.toStrictEqual({ + changed: true, + }); + await expect(make.sdk.modules.makePrivate(appName, appVersion, moduleName)).resolves.toStrictEqual({ + changed: true, + }); + }); }); From 193f1ea4ffb3ef6e3f28ecb68db470d0af415a03 Mon Sep 17 00:00:00 2001 From: Sebastian Mertens Make Date: Fri, 29 May 2026 11:28:38 +0200 Subject: [PATCH 2/6] test: assert SDK app icon binary payload --- test/sdk/apps.spec.ts | 2 ++ test/test.utils.ts | 9 ++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/test/sdk/apps.spec.ts b/test/sdk/apps.spec.ts index 1f1b319..ef8426e 100644 --- a/test/sdk/apps.spec.ts +++ b/test/sdk/apps.spec.ts @@ -148,6 +148,8 @@ describe('Endpoints: SDK > Apps', () => { it('Should upload app icon as raw PNG data', async () => { const iconData = new Uint8Array([137, 80, 78, 71]); mockFetch('PUT https://make.local/api/v2/sdk/apps/test-app/1/icon', null, req => { + expect(req.body).toBeInstanceOf(ArrayBuffer); + expect([...new Uint8Array(req.body as ArrayBuffer)]).toStrictEqual([...iconData]); expect(req.headers.get('content-type')).toBe('image/png'); expect(req.headers.get('imt-apps-sdk-version')).toBe('2.5.0'); }); diff --git a/test/test.utils.ts b/test/test.utils.ts index d2842f2..bf30857 100644 --- a/test/test.utils.ts +++ b/test/test.utils.ts @@ -6,7 +6,7 @@ enableFetchMocks(); type Mock = [string, unknown, number | Asserts | undefined]; type Asserts = (req: { - body: Record | Array | string; + body: Record | Array | string | ArrayBuffer; headers: Headers; method: string; url: string; @@ -55,9 +55,12 @@ export function mockFetch(...args: unknown[]): void { const isJsonType: boolean = Boolean( contentType === 'application/json' || contentType?.startsWith('application/json;'), ); //prevent application/jsonc to be parsed as json - const body = isJsonType ? await req.json() : await req.text(); + const isBinaryType = Boolean( + contentType?.startsWith('image/') || contentType === 'application/octet-stream', + ); + const body = isJsonType ? await req.json() : isBinaryType ? await req.arrayBuffer() : await req.text(); mock.asserts({ - body: isObject(body) ? (body as Record) : Array.isArray(body) ? body : String(body), + body: body instanceof ArrayBuffer ? body : isObject(body) ? (body as Record) : Array.isArray(body) ? body : String(body), headers: req.headers, method: req.method, url: req.url, From 71e588526eae834367a049d34680fd9003bdeed1 Mon Sep 17 00:00:00 2001 From: Patrik Simek Date: Wed, 3 Jun 2026 00:05:25 +0200 Subject: [PATCH 3/6] refactor: harden SDK icon and visibility tools - Accept/return base64 PNG data instead of filesystem paths, removing arbitrary file read/write and node:fs/path coupling from the tool layer - Move PNG + 512x512 validation into core setIcon (platform-neutral, no Node Buffer) so all callers are guarded, not just the tool - Drop allowNon512, sdkVersion, the imt-apps-sdk-version header and the contentType option; hardcode image/png - Return confirmation strings from set-icon and visibility tools, and a bare base64 string from get-icon, matching existing tool conventions - Make app/module visibility methods return void and remove the loose SDKAppVisibilityResponse / SDKModuleVisibilityResponse types --- src/endpoints/sdk/apps.tools.ts | 119 ++++------------------------- src/endpoints/sdk/apps.ts | 76 ++++++++++-------- src/endpoints/sdk/modules.tools.ts | 28 +------ src/endpoints/sdk/modules.ts | 20 +---- src/index.ts | 2 - test/sdk/apps.spec.ts | 31 ++++++-- test/sdk/modules.spec.ts | 8 +- 7 files changed, 98 insertions(+), 186 deletions(-) diff --git a/src/endpoints/sdk/apps.tools.ts b/src/endpoints/sdk/apps.tools.ts index 3cdc158..2c96ab0 100644 --- a/src/endpoints/sdk/apps.tools.ts +++ b/src/endpoints/sdk/apps.tools.ts @@ -1,44 +1,7 @@ -import { readFile, writeFile } from 'node:fs/promises'; -import { basename } from 'node:path'; import type { Make } from '../../make.js'; import type { JSONValue } from '../../types.js'; import type { MakeTool } from '../../tools.js'; -type PngInfo = { - width: number; - height: number; -}; - -const PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); - -export function readPngInfo(buffer: Buffer): PngInfo { - if (buffer.length < 24 || !buffer.subarray(0, 8).equals(PNG_SIGNATURE)) { - throw new Error('Icon file must be a PNG image.'); - } - - const ihdrType = buffer.subarray(12, 16).toString('ascii'); - if (ihdrType !== 'IHDR') { - throw new Error('Icon file is not a valid PNG image: missing IHDR chunk.'); - } - - return { - width: buffer.readUInt32BE(16), - height: buffer.readUInt32BE(20), - }; -} - -function visibilityResult(scope: 'app', name: string, version: number, visibility: 'public' | 'private', response: JSONValue) { - return { - changed: true, - scope, - appName: name, - version, - visibility, - public: visibility === 'public', - response, - }; -} - export const tools: MakeTool[] = [ { name: 'sdk-apps_list', @@ -363,7 +326,7 @@ export const tools: MakeTool[] = [ { name: 'sdk-apps_set-icon', title: 'Set SDK app icon', - description: 'Upload a PNG icon for a SDK app.', + description: 'Upload an icon for a SDK app.', category: 'sdk-apps', scope: 'sdk-apps:write', scopeId: undefined, @@ -377,42 +340,20 @@ export const tools: MakeTool[] = [ properties: { name: { type: 'string', description: 'The name of the app' }, version: { type: 'number', description: 'The version of the app' }, - file: { type: 'string', description: 'Path to the PNG icon file to upload' }, - allowNon512: { type: 'boolean', description: 'Allow PNG icons that are not 512x512', default: false }, - sdkVersion: { type: 'string', description: 'Apps SDK version header', default: '2.5.0' }, + dataBase64: { type: 'string', description: 'Base64-encoded 512x512 PNG icon data to upload' }, }, - required: ['name', 'version', 'file'], + required: ['name', 'version', 'dataBase64'], }, - examples: [{ name: 'my-app', version: 1, file: './assets/icon.png' }], - execute: async ( - make: Make, - args: { name: string; version: number; file: string; allowNon512?: boolean; sdkVersion?: string }, - ) => { - const icon = await readFile(args.file); - const pngInfo = readPngInfo(icon); - if (!args.allowNon512 && (pngInfo.width !== 512 || pngInfo.height !== 512)) { - throw new Error( - `Icon must be 512x512 PNG. Got ${pngInfo.width}x${pngInfo.height}. ` + - 'Resize it first or pass allowNon512 intentionally.', - ); - } - - const changed = await make.sdk.apps.setIcon(args.name, args.version, icon, { sdkVersion: args.sdkVersion }); - return { - changed, - appName: args.name, - version: args.version, - file: basename(args.file), - width: pngInfo.width, - height: pngInfo.height, - readbackPath: `/sdk/apps/${args.name}/${args.version}/icon/512`, - }; + examples: [{ name: 'my-app', version: 1, dataBase64: 'iVBORw0KGgo...' }], + execute: async (make: Make, args: { name: string; version: number; dataBase64: string }) => { + await make.sdk.apps.setIcon(args.name, args.version, Buffer.from(args.dataBase64, 'base64')); + return `Icon has been set.`; }, }, { name: 'sdk-apps_get-icon', title: 'Get SDK app icon', - description: 'Download a SDK app icon. Writes to outputFile when provided, otherwise returns base64 PNG data.', + description: 'Download a SDK app icon and return it as base64-encoded PNG data.', category: 'sdk-apps', scope: 'sdk-apps:read', scopeId: undefined, @@ -425,42 +366,14 @@ export const tools: MakeTool[] = [ properties: { name: { type: 'string', description: 'The name of the app' }, version: { type: 'number', description: 'The version of the app' }, - outputFile: { type: 'string', description: 'Optional path to write the PNG icon' }, size: { type: 'number', description: 'Icon size to download', default: 512 }, - sdkVersion: { type: 'string', description: 'Apps SDK version header', default: '2.5.0' }, }, required: ['name', 'version'], }, - examples: [{ name: 'my-app', version: 1, outputFile: '/tmp/my-app-icon.png', size: 512 }], - execute: async ( - make: Make, - args: { name: string; version: number; outputFile?: string; size?: number; sdkVersion?: string }, - ) => { - const icon = Buffer.from( - await make.sdk.apps.getIcon(args.name, args.version, args.size ?? 512, { sdkVersion: args.sdkVersion }), - ); - const pngInfo = readPngInfo(icon); - - if (args.outputFile) { - await writeFile(args.outputFile, icon); - return { - appName: args.name, - version: args.version, - file: args.outputFile, - width: pngInfo.width, - height: pngInfo.height, - }; - } - - return { - appName: args.name, - version: args.version, - size: args.size ?? 512, - width: pngInfo.width, - height: pngInfo.height, - contentType: 'image/png', - dataBase64: icon.toString('base64'), - }; + examples: [{ name: 'my-app', version: 1, size: 512 }], + execute: async (make: Make, args: { name: string; version: number; size?: number }) => { + const icon = Buffer.from(await make.sdk.apps.getIcon(args.name, args.version, args.size ?? 512)); + return icon.toString('base64'); }, }, { @@ -482,8 +395,8 @@ export const tools: MakeTool[] = [ }, examples: [{ name: 'my-app', version: 1 }], execute: async (make: Make, args: { name: string; version: number }) => { - const response = await make.sdk.apps.makePublic(args.name, args.version); - return visibilityResult('app', args.name, args.version, 'public', response as JSONValue); + await make.sdk.apps.makePublic(args.name, args.version); + return `App has been made public.`; }, }, { @@ -505,8 +418,8 @@ export const tools: MakeTool[] = [ }, examples: [{ name: 'my-app', version: 1 }], execute: async (make: Make, args: { name: string; version: number }) => { - const response = await make.sdk.apps.makePrivate(args.name, args.version); - return visibilityResult('app', args.name, args.version, 'private', response as JSONValue); + await make.sdk.apps.makePrivate(args.name, args.version); + return `App has been made private.`; }, }, { diff --git a/src/endpoints/sdk/apps.ts b/src/endpoints/sdk/apps.ts index 59659d7..460bc74 100644 --- a/src/endpoints/sdk/apps.ts +++ b/src/endpoints/sdk/apps.ts @@ -137,16 +137,44 @@ type UpdateSDKAppResponse = { app: Pick; }; -export type SDKAppVisibilityResponse = { - app?: SDKApp; - changed?: boolean; -}; - type IconUploadResponse = { - success?: boolean; - changed?: boolean; + changed: boolean; }; +/** PNG file signature (first 8 bytes). */ +const PNG_SIGNATURE = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]; + +/** IHDR chunk type marker ("IHDR"), located at bytes 12-15 of a PNG. */ +const PNG_IHDR = [0x49, 0x48, 0x44, 0x52]; + +/** Required app icon dimensions (square, in pixels). */ +const APP_ICON_SIZE = 512; + +/** + * Validate that an icon payload is a 512x512 PNG before upload, mirroring the + * server constraint so callers fail fast with a clear error. Platform-neutral + * (works with both `Uint8Array` and `ArrayBuffer`, no Node `Buffer` required). + */ +function assertAppIcon(iconData: Uint8Array | ArrayBuffer): void { + const bytes = iconData instanceof Uint8Array ? iconData : new Uint8Array(iconData); + if (bytes.length < 24 || PNG_SIGNATURE.some((byte, index) => bytes[index] !== byte)) { + throw new Error('App icon must be a PNG image.'); + } + + if (PNG_IHDR.some((byte, index) => bytes[12 + index] !== byte)) { + throw new Error('App icon is not a valid PNG image: missing IHDR chunk.'); + } + + const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); + const width = view.getUint32(16); + const height = view.getUint32(20); + if (width !== APP_ICON_SIZE || height !== APP_ICON_SIZE) { + throw new Error( + `App icon must be ${APP_ICON_SIZE}x${APP_ICON_SIZE} PNG. Got ${width}x${height}. Resize it first.`, + ); + } +} + /** * Class providing methods for working with Apps */ @@ -277,37 +305,25 @@ export class SDKApps { } /** - * Upload an app icon. Use PNG icon data for Make app icons. + * Upload an app icon. The icon must be a 512x512 PNG; otherwise an error is thrown before upload. */ - async setIcon( - name: string, - version: number, - iconData: Uint8Array | ArrayBuffer, - options: { contentType?: string; sdkVersion?: string } = {}, - ): Promise { - const response = await this.#fetch(`/sdk/apps/${name}/${version}/icon`, { + async setIcon(name: string, version: number, iconData: Uint8Array | ArrayBuffer): Promise { + assertAppIcon(iconData); + const response = await this.#fetch(`/sdk/apps/${name}/${version}/icon`, { method: 'PUT', headers: { - 'Content-Type': options.contentType ?? 'image/png', - 'imt-apps-sdk-version': options.sdkVersion ?? '2.5.0', + 'Content-Type': 'image/png', }, body: iconData, }); - - if (response && typeof response === 'object') { - return response.success ?? response.changed ?? true; - } - return true; + return response.changed; } /** * Download an app icon at a given rendered size. */ - async getIcon(name: string, version: number, size = 512, options: { sdkVersion?: string } = {}): Promise { + async getIcon(name: string, version: number, size = 512): Promise { return await this.#fetch(`/sdk/apps/${name}/${version}/icon/${size}`, { - headers: { - 'imt-apps-sdk-version': options.sdkVersion ?? '2.5.0', - }, responseType: 'arrayBuffer', }); } @@ -315,8 +331,8 @@ export class SDKApps { /** * Make app private. */ - async makePrivate(name: string, version: number): Promise { - return await this.#fetch(`/sdk/apps/${name}/${version}/private`, { + async makePrivate(name: string, version: number): Promise { + await this.#fetch(`/sdk/apps/${name}/${version}/private`, { method: 'POST', }); } @@ -324,8 +340,8 @@ export class SDKApps { /** * Make app public. */ - async makePublic(name: string, version: number): Promise { - return await this.#fetch(`/sdk/apps/${name}/${version}/public`, { + async makePublic(name: string, version: number): Promise { + await this.#fetch(`/sdk/apps/${name}/${version}/public`, { method: 'POST', }); } diff --git a/src/endpoints/sdk/modules.tools.ts b/src/endpoints/sdk/modules.tools.ts index d7be613..3ca57ce 100644 --- a/src/endpoints/sdk/modules.tools.ts +++ b/src/endpoints/sdk/modules.tools.ts @@ -1,26 +1,6 @@ import type { Make } from '../../make.js'; -import type { JSONValue } from '../../types.js'; import type { MakeTool } from '../../tools.js'; -function moduleVisibilityResult( - appName: string, - appVersion: number, - moduleName: string, - visibility: 'public' | 'private', - response: JSONValue, -) { - return { - changed: true, - scope: 'module', - appName, - version: appVersion, - moduleName, - visibility, - public: visibility === 'public', - response, - }; -} - export const tools: MakeTool[] = [ { name: 'sdk-modules_list', @@ -250,8 +230,8 @@ export const tools: MakeTool[] = [ }, examples: [{ appName: 'my-app', appVersion: 1, moduleName: 'listItems' }], execute: async (make: Make, args: { appName: string; appVersion: number; moduleName: string }) => { - const response = await make.sdk.modules.makePublic(args.appName, args.appVersion, args.moduleName); - return moduleVisibilityResult(args.appName, args.appVersion, args.moduleName, 'public', response as JSONValue); + await make.sdk.modules.makePublic(args.appName, args.appVersion, args.moduleName); + return `Module has been made public.`; }, }, { @@ -274,8 +254,8 @@ export const tools: MakeTool[] = [ }, examples: [{ appName: 'my-app', appVersion: 1, moduleName: 'listItems' }], execute: async (make: Make, args: { appName: string; appVersion: number; moduleName: string }) => { - const response = await make.sdk.modules.makePrivate(args.appName, args.appVersion, args.moduleName); - return moduleVisibilityResult(args.appName, args.appVersion, args.moduleName, 'private', response as JSONValue); + await make.sdk.modules.makePrivate(args.appName, args.appVersion, args.moduleName); + return `Module has been made private.`; }, }, { diff --git a/src/endpoints/sdk/modules.ts b/src/endpoints/sdk/modules.ts index 0d7bd87..6f95df6 100644 --- a/src/endpoints/sdk/modules.ts +++ b/src/endpoints/sdk/modules.ts @@ -85,12 +85,6 @@ type UpdateSDKModuleResponse = { appModule: SDKModule; }; -export type SDKModuleVisibilityResponse = { - appModule?: SDKModule; - module?: SDKModule; - changed?: boolean; -}; - /** * Class providing methods for working with App Modules */ @@ -197,20 +191,14 @@ export class SDKModules { /** * Make a module private. */ - async makePrivate(appName: string, appVersion: number, moduleName: string): Promise { - return await this.#fetch( - `/sdk/apps/${appName}/${appVersion}/modules/${moduleName}/private`, - { method: 'POST' }, - ); + async makePrivate(appName: string, appVersion: number, moduleName: string): Promise { + await this.#fetch(`/sdk/apps/${appName}/${appVersion}/modules/${moduleName}/private`, { method: 'POST' }); } /** * Make a module public. */ - async makePublic(appName: string, appVersion: number, moduleName: string): Promise { - return await this.#fetch( - `/sdk/apps/${appName}/${appVersion}/modules/${moduleName}/public`, - { method: 'POST' }, - ); + async makePublic(appName: string, appVersion: number, moduleName: string): Promise { + await this.#fetch(`/sdk/apps/${appName}/${appVersion}/modules/${moduleName}/public`, { method: 'POST' }); } } diff --git a/src/index.ts b/src/index.ts index 8b0d698..dff4846 100644 --- a/src/index.ts +++ b/src/index.ts @@ -120,7 +120,6 @@ export type { SDKAppSection, SDKAppSectionType, SDKAppCommon, - SDKAppVisibilityResponse, ListSDKAppsOptions, GetSDKAppsOptions, CreateSDKAppBody, @@ -131,7 +130,6 @@ export type { SDKModules, SDKModuleSection, SDKModuleSectionType, - SDKModuleVisibilityResponse, CreateSDKModuleBody, UpdateSDKModuleBody, } from './endpoints/sdk/modules.js'; diff --git a/test/sdk/apps.spec.ts b/test/sdk/apps.spec.ts index ef8426e..ed7b887 100644 --- a/test/sdk/apps.spec.ts +++ b/test/sdk/apps.spec.ts @@ -16,6 +16,16 @@ import { join } from 'path'; const MAKE_API_KEY = 'api-key'; const MAKE_ZONE = 'make.local'; +/** Build a minimal valid PNG header (signature + IHDR) with the given dimensions. */ +function pngIcon(width: number, height: number): Uint8Array { + const bytes = new Uint8Array(24); + bytes.set([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); // PNG signature + bytes.set([0x49, 0x48, 0x44, 0x52], 12); // "IHDR" + new DataView(bytes.buffer).setUint32(16, width); + new DataView(bytes.buffer).setUint32(20, height); + return bytes; +} + describe('Endpoints: SDK > Apps', () => { const make = new Make(MAKE_API_KEY, MAKE_ZONE); @@ -146,18 +156,29 @@ describe('Endpoints: SDK > Apps', () => { }); it('Should upload app icon as raw PNG data', async () => { - const iconData = new Uint8Array([137, 80, 78, 71]); - mockFetch('PUT https://make.local/api/v2/sdk/apps/test-app/1/icon', null, req => { + const iconData = pngIcon(512, 512); + mockFetch('PUT https://make.local/api/v2/sdk/apps/test-app/1/icon', { changed: true }, req => { expect(req.body).toBeInstanceOf(ArrayBuffer); expect([...new Uint8Array(req.body as ArrayBuffer)]).toStrictEqual([...iconData]); expect(req.headers.get('content-type')).toBe('image/png'); - expect(req.headers.get('imt-apps-sdk-version')).toBe('2.5.0'); }); const result = await make.sdk.apps.setIcon('test-app', 1, iconData); expect(result).toBe(true); }); + it('Should reject a non-PNG app icon before upload', async () => { + await expect(make.sdk.apps.setIcon('test-app', 1, new Uint8Array([1, 2, 3, 4]))).rejects.toThrow( + 'App icon must be a PNG image.', + ); + }); + + it('Should reject an app icon that is not 512x512', async () => { + await expect(make.sdk.apps.setIcon('test-app', 1, pngIcon(256, 256))).rejects.toThrow( + 'App icon must be 512x512 PNG. Got 256x256.', + ); + }); + it('Should download app icon as an ArrayBuffer', async () => { mockFetch('GET https://make.local/api/v2/sdk/apps/test-app/1/icon/512', 'png-bytes'); @@ -172,7 +193,7 @@ describe('Endpoints: SDK > Apps', () => { ['POST https://make.local/api/v2/sdk/apps/test-app/1/private', { changed: true }, undefined], ); - await expect(make.sdk.apps.makePublic('test-app', 1)).resolves.toStrictEqual({ changed: true }); - await expect(make.sdk.apps.makePrivate('test-app', 1)).resolves.toStrictEqual({ changed: true }); + await expect(make.sdk.apps.makePublic('test-app', 1)).resolves.toBeUndefined(); + await expect(make.sdk.apps.makePrivate('test-app', 1)).resolves.toBeUndefined(); }); }); diff --git a/test/sdk/modules.spec.ts b/test/sdk/modules.spec.ts index d56f4f4..19e71ab 100644 --- a/test/sdk/modules.spec.ts +++ b/test/sdk/modules.spec.ts @@ -130,11 +130,7 @@ describe('Endpoints: SDK > Modules', () => { ], ); - await expect(make.sdk.modules.makePublic(appName, appVersion, moduleName)).resolves.toStrictEqual({ - changed: true, - }); - await expect(make.sdk.modules.makePrivate(appName, appVersion, moduleName)).resolves.toStrictEqual({ - changed: true, - }); + await expect(make.sdk.modules.makePublic(appName, appVersion, moduleName)).resolves.toBeUndefined(); + await expect(make.sdk.modules.makePrivate(appName, appVersion, moduleName)).resolves.toBeUndefined(); }); }); From cfba59b6ca063d53bfcaad397e78ba047d15d39b Mon Sep 17 00:00:00 2001 From: Patrik Simek Date: Wed, 3 Jun 2026 00:30:38 +0200 Subject: [PATCH 4/6] refactor: parse binary responses by content-type Replace the per-call responseType override with content-type detection in handleResponse (image/* and application/octet-stream -> ArrayBuffer), mirroring the binary classification already used for request bodies. getIcon no longer needs an explicit response type. Also deliver raw bytes from the mock harness so binary responses round-trip losslessly in tests, and assert the real PNG payload in the get-icon test. --- src/endpoints/sdk/apps.ts | 4 +--- src/make.ts | 31 +++++++++++++++---------------- src/types.ts | 2 -- test/sdk/apps.spec.ts | 5 +++-- test/test.utils.ts | 27 ++++++++++++++++++++++++--- 5 files changed, 43 insertions(+), 26 deletions(-) diff --git a/src/endpoints/sdk/apps.ts b/src/endpoints/sdk/apps.ts index 460bc74..722ddc0 100644 --- a/src/endpoints/sdk/apps.ts +++ b/src/endpoints/sdk/apps.ts @@ -323,9 +323,7 @@ export class SDKApps { * Download an app icon at a given rendered size. */ async getIcon(name: string, version: number, size = 512): Promise { - return await this.#fetch(`/sdk/apps/${name}/${version}/icon/${size}`, { - responseType: 'arrayBuffer', - }); + return await this.#fetch(`/sdk/apps/${name}/${version}/icon/${size}`); } /** diff --git a/src/make.ts b/src/make.ts index ded3c62..6538acf 100644 --- a/src/make.ts +++ b/src/make.ts @@ -297,7 +297,7 @@ export class Make { // Success case if (res.status < 400) { - return this.handleResponse(res, options?.responseType); + return this.handleResponse(res); } // Create error for potential retry @@ -488,31 +488,30 @@ export class Make { /** * Handle successful API responses * - * Parses the response based on content-type header. - * JSON responses are parsed as objects, other responses as text. + * Parses the response based on the content-type header: JSON responses are + * parsed as objects, binary responses (e.g. `image/*` or + * `application/octet-stream`) as an ArrayBuffer, and everything else as text. * * @template T The expected response type * @param response The successful response from the API * @returns Promise resolving to the parsed response data * @protected */ - protected async handleResponse(response: Response, responseType?: FetchOptions['responseType']): Promise { - if (responseType === 'arrayBuffer') { - return (await response.arrayBuffer()) as T; - } - if (responseType === 'text') { - return (await response.text()) as T; - } - if (responseType === 'json') { - return (await response.json()) as T; - } - + protected async handleResponse(response: Response): Promise { const contentType = response.headers.get('content-type'); const isJsonType: boolean = Boolean( contentType === 'application/json' || contentType?.startsWith('application/json;'), ); //prevent application/jsonc to be parsed as json + const isBinaryType: boolean = Boolean( + contentType?.startsWith('image/') || contentType === 'application/octet-stream', + ); - const result = isJsonType ? await response.json() : await response.text(); - return result as T; + if (isJsonType) { + return (await response.json()) as T; + } + if (isBinaryType) { + return (await response.arrayBuffer()) as T; + } + return (await response.text()) as T; } } diff --git a/src/types.ts b/src/types.ts index 49ef676..fa8ea51 100644 --- a/src/types.ts +++ b/src/types.ts @@ -35,8 +35,6 @@ export type FetchOptions = { body?: Record | Array | string | Uint8Array | ArrayBuffer; /** HTTP method (GET, POST, PATCH, etc.) */ method?: string; - /** Override the default content-type based response parser */ - responseType?: 'json' | 'text' | 'arrayBuffer'; }; /** diff --git a/test/sdk/apps.spec.ts b/test/sdk/apps.spec.ts index ed7b887..d80c5a7 100644 --- a/test/sdk/apps.spec.ts +++ b/test/sdk/apps.spec.ts @@ -180,11 +180,12 @@ describe('Endpoints: SDK > Apps', () => { }); it('Should download app icon as an ArrayBuffer', async () => { - mockFetch('GET https://make.local/api/v2/sdk/apps/test-app/1/icon/512', 'png-bytes'); + const iconData = pngIcon(512, 512); + mockFetch('GET https://make.local/api/v2/sdk/apps/test-app/1/icon/512', iconData); const result = await make.sdk.apps.getIcon('test-app', 1); expect(result).toBeInstanceOf(ArrayBuffer); - expect(Buffer.from(result).toString()).toBe('png-bytes'); + expect([...new Uint8Array(result)]).toStrictEqual([...iconData]); }); it('Should make app public and private', async () => { diff --git a/test/test.utils.ts b/test/test.utils.ts index bf30857..6040358 100644 --- a/test/test.utils.ts +++ b/test/test.utils.ts @@ -60,18 +60,39 @@ export function mockFetch(...args: unknown[]): void { ); const body = isJsonType ? await req.json() : isBinaryType ? await req.arrayBuffer() : await req.text(); mock.asserts({ - body: body instanceof ArrayBuffer ? body : isObject(body) ? (body as Record) : Array.isArray(body) ? body : String(body), + body: + body instanceof ArrayBuffer + ? body + : isObject(body) + ? (body as Record) + : Array.isArray(body) + ? body + : String(body), headers: req.headers, method: req.method, url: req.url, }); } + const binaryBody = + mock.body instanceof Uint8Array + ? mock.body + : mock.body instanceof ArrayBuffer + ? new Uint8Array(mock.body) + : null; return Promise.resolve({ - body: typeof mock.body === 'string' ? mock.body : JSON.stringify(mock.body), + // Hand the raw bytes to Response so they survive intact; a string body + // would be re-encoded as UTF-8 and corrupt any byte > 0x7f. Cast because + // jest-fetch-mock types `body` as string, but undici accepts a Uint8Array. + body: (binaryBody ?? + (typeof mock.body === 'string' ? mock.body : JSON.stringify(mock.body))) as unknown as string, status: mock.status, headers: { - 'content-type': typeof mock.body === 'string' ? 'text/plain' : 'application/json', + 'content-type': binaryBody + ? 'image/png' + : typeof mock.body === 'string' + ? 'text/plain' + : 'application/json', }, }); }); From 9fc7b300a3480cc59cf4683375d588a1f309d541 Mon Sep 17 00:00:00 2001 From: Patrik Simek Date: Wed, 3 Jun 2026 00:38:39 +0200 Subject: [PATCH 5/6] docs: clarify prepareBody binary passthrough in JSDoc --- src/make.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/make.ts b/src/make.ts index 6538acf..992c330 100644 --- a/src/make.ts +++ b/src/make.ts @@ -389,9 +389,14 @@ export class Make { /** * Prepare the request body for API calls * - * @param body The request body - can be an object, string, or undefined + * Objects and arrays are JSON-serialized (setting the JSON content-type), + * strings are passed through unchanged, and raw binary payloads + * (`Uint8Array`/`ArrayBuffer`) are returned as-is so the caller controls the + * content-type. + * + * @param body The request body - an object/array, string, raw binary payload, or undefined * @param headers The headers object to potentially modify the content-type - * @returns The body serialized as a string + * @returns The JSON string for objects/arrays, or the string/binary/undefined body unchanged * @protected */ protected prepareBody( From 5b1df1dc7cfb6de5a5026ad8f6374a474bb9880e Mon Sep 17 00:00:00 2001 From: Patrik Simek Date: Wed, 3 Jun 2026 00:48:49 +0200 Subject: [PATCH 6/6] fix: broaden octet-stream content-type match and tidy tool description grammar --- src/endpoints/sdk/apps.tools.ts | 16 ++++++++-------- src/endpoints/sdk/modules.tools.ts | 4 ++-- src/make.ts | 2 +- test/test.utils.ts | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/endpoints/sdk/apps.tools.ts b/src/endpoints/sdk/apps.tools.ts index 2c96ab0..f8d0932 100644 --- a/src/endpoints/sdk/apps.tools.ts +++ b/src/endpoints/sdk/apps.tools.ts @@ -26,7 +26,7 @@ export const tools: MakeTool[] = [ { name: 'sdk-apps_get', title: 'Get SDK app', - description: 'Get a SDK app by name and version.', + description: 'Get an SDK app by name and version.', category: 'sdk-apps', scope: 'sdk-apps:read', scopeId: undefined, @@ -154,7 +154,7 @@ export const tools: MakeTool[] = [ { name: 'sdk-apps_delete', title: 'Delete SDK app', - description: 'Delete a SDK app by name and version.', + description: 'Delete an SDK app by name and version.', category: 'sdk-apps', scope: 'sdk-apps:write', scopeId: undefined, @@ -179,7 +179,7 @@ export const tools: MakeTool[] = [ { name: 'sdk-apps_get-section', title: 'Get SDK app section', - description: 'Get a specific section of a SDK app.', + description: 'Get a specific section of an SDK app.', category: 'sdk-apps', scope: 'sdk-apps:read', scopeId: undefined, @@ -211,7 +211,7 @@ export const tools: MakeTool[] = [ { name: 'sdk-apps_set-section', title: 'Set SDK app section', - description: 'Set/update a specific section of a SDK app.', + description: 'Set/update a specific section of an SDK app.', category: 'sdk-apps', scope: 'sdk-apps:write', scopeId: undefined, @@ -326,7 +326,7 @@ export const tools: MakeTool[] = [ { name: 'sdk-apps_set-icon', title: 'Set SDK app icon', - description: 'Upload an icon for a SDK app.', + description: 'Upload an icon for an SDK app.', category: 'sdk-apps', scope: 'sdk-apps:write', scopeId: undefined, @@ -353,7 +353,7 @@ export const tools: MakeTool[] = [ { name: 'sdk-apps_get-icon', title: 'Get SDK app icon', - description: 'Download a SDK app icon and return it as base64-encoded PNG data.', + description: 'Download an SDK app icon and return it as base64-encoded PNG data.', category: 'sdk-apps', scope: 'sdk-apps:read', scopeId: undefined, @@ -379,7 +379,7 @@ export const tools: MakeTool[] = [ { name: 'sdk-apps_set-public', title: 'Set SDK app public', - description: 'Mark a SDK app version as public.', + description: 'Mark an SDK app version as public.', category: 'sdk-apps', scope: 'sdk-apps:write', scopeId: undefined, @@ -402,7 +402,7 @@ export const tools: MakeTool[] = [ { name: 'sdk-apps_set-private', title: 'Set SDK app private', - description: 'Mark a SDK app version as private.', + description: 'Mark an SDK app version as private.', category: 'sdk-apps', scope: 'sdk-apps:write', scopeId: undefined, diff --git a/src/endpoints/sdk/modules.tools.ts b/src/endpoints/sdk/modules.tools.ts index 3ca57ce..983cd56 100644 --- a/src/endpoints/sdk/modules.tools.ts +++ b/src/endpoints/sdk/modules.tools.ts @@ -213,7 +213,7 @@ export const tools: MakeTool[] = [ { name: 'sdk-modules_set-public', title: 'Set SDK module public', - description: 'Mark a SDK app module as public.', + description: 'Mark an SDK app module as public.', category: 'sdk-modules', scope: 'sdk-apps:write', scopeId: undefined, @@ -237,7 +237,7 @@ export const tools: MakeTool[] = [ { name: 'sdk-modules_set-private', title: 'Set SDK module private', - description: 'Mark a SDK app module as private.', + description: 'Mark an SDK app module as private.', category: 'sdk-modules', scope: 'sdk-apps:write', scopeId: undefined, diff --git a/src/make.ts b/src/make.ts index 992c330..e1209a7 100644 --- a/src/make.ts +++ b/src/make.ts @@ -508,7 +508,7 @@ export class Make { contentType === 'application/json' || contentType?.startsWith('application/json;'), ); //prevent application/jsonc to be parsed as json const isBinaryType: boolean = Boolean( - contentType?.startsWith('image/') || contentType === 'application/octet-stream', + contentType?.startsWith('image/') || contentType?.startsWith('application/octet-stream'), ); if (isJsonType) { diff --git a/test/test.utils.ts b/test/test.utils.ts index 6040358..37385e6 100644 --- a/test/test.utils.ts +++ b/test/test.utils.ts @@ -56,7 +56,7 @@ export function mockFetch(...args: unknown[]): void { contentType === 'application/json' || contentType?.startsWith('application/json;'), ); //prevent application/jsonc to be parsed as json const isBinaryType = Boolean( - contentType?.startsWith('image/') || contentType === 'application/octet-stream', + contentType?.startsWith('image/') || contentType?.startsWith('application/octet-stream'), ); const body = isJsonType ? await req.json() : isBinaryType ? await req.arrayBuffer() : await req.text(); mock.asserts({