diff --git a/README.md b/README.md index 5b618d5..aebe0e3 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ const make = new Make('your-api-key', 'eu2.make.com', { - **Organizations** - Top-level account and billing management - **Scenarios** - Scenario management - **Teams** - Team management and collaboration +- **Public Templates** - Public template discovery and blueprint export (read-only) - **Users** - Current user information and authentication ## Custom Apps Development Endpoints @@ -205,6 +206,7 @@ All tools are organized into the following categories: - `organizations` - `scenarios` - `teams` +- `public-templates` - `users` - `sdk.apps` - `sdk.connections` diff --git a/src/endpoints/public-templates.tools.ts b/src/endpoints/public-templates.tools.ts new file mode 100644 index 0000000..4e06fc2 --- /dev/null +++ b/src/endpoints/public-templates.tools.ts @@ -0,0 +1,92 @@ +import type { Make } from '../make.js'; + +export const tools = [ + { + name: 'public-templates_list', + title: 'List public templates', + description: + 'Search and list public (approved) templates available for anyone. Supports name-based search for template discovery. Results are sorted by usage by default.', + category: 'public-templates', + scope: 'templates:read', + annotations: { + readOnlyHint: true, + }, + inputSchema: { + type: 'object', + properties: { + name: { type: 'string', description: 'Search public templates by name' }, + usedApps: { + type: 'array', + items: { type: 'string' }, + description: 'Filter public templates by apps used', + }, + includeEn: { + type: 'boolean', + description: 'Whether to include English-language public templates in results', + }, + }, + }, + examples: [{ name: 'webhook' }], + execute: async (make: Make, args?: { name?: string; usedApps?: string[]; includeEn?: boolean }) => { + return await make.publicTemplates.list({ ...(args ?? {}), cols: ['*'] }); + }, + }, + { + name: 'public-templates_get', + title: 'Get public template', + description: + 'Get details of a public template by its URL slug (e.g. "12289-add-webhook-data-to-a-google-sheet"). Use this for templates discovered via public-templates_list.', + category: 'public-templates', + scope: 'templates:read', + scopeId: 'templateUrl', + identifier: 'templateUrl', + resourceId: 'templateUrl', + annotations: { + readOnlyHint: true, + }, + inputSchema: { + type: 'object', + properties: { + templateUrl: { + type: 'string', + description: + 'The URL slug of the public template (e.g. "12289-add-webhook-data-to-a-google-sheet")', + }, + }, + required: ['templateUrl'], + }, + examples: [{ templateUrl: '12289-add-webhook-data-to-a-google-sheet' }], + execute: async (make: Make, args: { templateUrl: string }) => { + return await make.publicTemplates.get(args.templateUrl, { cols: ['*'] }); + }, + }, + { + name: 'public-templates_get-blueprint', + title: 'Get public template blueprint', + description: + 'Get the full blueprint of a public template including scenario flow, controller configuration, scheduling, and metadata. Use this for templates discovered via public-templates_list.', + category: 'public-templates', + scope: 'templates:read', + scopeId: 'templateUrl', + identifier: 'templateUrl', + resourceId: 'templateUrl', + annotations: { + readOnlyHint: true, + }, + inputSchema: { + type: 'object', + properties: { + templateUrl: { + type: 'string', + description: + 'The URL slug of the public template (e.g. "12289-add-webhook-data-to-a-google-sheet")', + }, + }, + required: ['templateUrl'], + }, + examples: [{ templateUrl: '12289-add-webhook-data-to-a-google-sheet' }], + execute: async (make: Make, args: { templateUrl: string }) => { + return await make.publicTemplates.getBlueprint(args.templateUrl); + }, + }, +]; diff --git a/src/endpoints/public-templates.ts b/src/endpoints/public-templates.ts new file mode 100644 index 0000000..53d142e --- /dev/null +++ b/src/endpoints/public-templates.ts @@ -0,0 +1,180 @@ +import type { FetchFunction, Pagination, PickColumns } from '../types.js'; +import type { Blueprint } from './blueprints.js'; +import type { Scheduling } from './scenarios.js'; + +/** + * Represents a publicly available approved template in Make. + * Public templates can be discovered and used by all Make users. + */ +export type PublicTemplate = { + /** Unique identifier of the public template */ + id: number; + /** Name of the public template */ + name: string; + /** Human-readable description of what the public template does, or null if not set */ + description: string | null; + /** URL slug identifying this public template */ + url: string; + /** List of app identifiers used in the public template */ + usedApps: string[]; + /** Number of times this public template has been used */ + usage: number; +}; + +/** + * Blueprint payload returned by the public-template blueprint endpoint. + * Wraps the scenario blueprint together with its scheduling and controller configuration. + */ +export type PublicTemplateBlueprint = { + /** The scenario blueprint definition (modules, flow, metadata). Scheduling is exposed at the top level of this payload instead. */ + blueprint: Omit; + /** Controller configuration for the scenario */ + controller: { + /** Controller name */ + name: string; + /** Controller-tracked module state, keyed by module ID */ + modules: Record; + /** Next ID to assign when adding a module */ + idSequence: number; + }; + /** Scheduling configuration for the scenario */ + scheduling: Scheduling; + /** Language code for the public template (e.g. "en") */ + language: string; + /** Additional metadata for the public template, or null if not set */ + metadata: Record | null; +}; + +/** + * Options for listing public (approved) templates. + * @template C Keys of the PublicTemplate type to include in the response + */ +export type ListPublicTemplatesOptions = { + /** Specific columns/fields to include in the response */ + cols?: C[] | ['*']; + /** Pagination options */ + pg?: Partial>; + /** Search public templates by name */ + name?: string; + /** Filter public templates by apps used */ + usedApps?: string[]; + /** Whether to include English-language public templates in results */ + includeEn?: boolean; +}; + +/** + * Options for getting a single public template. + * @template C Keys of the PublicTemplate type to include in the response + */ +export type GetPublicTemplateOptions = { + /** Specific columns/fields to include in the response */ + cols?: C[] | ['*']; +}; + +/** + * Response format for listing public templates. + */ +type ListPublicTemplatesResponse = { + /** List of public templates matching the query */ + templatesPublic: PickColumns[]; + /** Pagination information */ + pg: Pagination; +}; + +/** + * Response format for getting a single public template. + */ +type GetPublicTemplateResponse = { + /** The requested public template */ + templatePublic: PickColumns; +}; + +/** + * Class providing methods for working with public Make templates. + * Public templates are approved scenario configurations that can be + * discovered and used by any Make user. + */ +export class PublicTemplates { + readonly #fetch: FetchFunction; + + /** + * Create a new PublicTemplates instance. + * @param fetch Function for making API requests + */ + constructor(fetch: FetchFunction) { + this.#fetch = fetch; + } + + /** + * List public (approved) templates available for anyone. + * Supports name-based search for template discovery. + * Results are sorted by usage in descending order by default. + * @param options Optional parameters for searching, filtering, and pagination + * @returns Promise with the list of public templates + * + * @example + * ```typescript + * // Search public templates by name + * const templates = await make.publicTemplates.list({ name: 'webhook' }); + * + * // Filter by apps used + * const gmailTemplates = await make.publicTemplates.list({ usedApps: ['gmail'] }); + * ``` + */ + async list( + options: ListPublicTemplatesOptions = {}, + ): Promise[]> { + return ( + await this.#fetch>('/templates/public', { + query: { + name: options.name, + usedApps: options.usedApps, + includeEn: options.includeEn, + cols: options.cols, + pg: options.pg, + }, + }) + ).templatesPublic; + } + + /** + * Get a single public template by its URL slug. + * Use this for templates discovered via {@link list}. + * @param templateUrl The URL slug of the template (e.g. "12289-add-webhook-data-to-a-google-sheet") + * @param options Optional parameters for field selection + * @returns Promise with the public template details + * + * @example + * ```typescript + * const template = await make.publicTemplates.get('12289-add-webhook-data-to-a-google-sheet'); + * ``` + */ + async get( + templateUrl: string, + options: GetPublicTemplateOptions = {}, + ): Promise> { + return ( + await this.#fetch>(`/templates/public/${templateUrl}`, { + query: { + cols: options.cols, + }, + }) + ).templatePublic; + } + + /** + * Get the blueprint (scenario definition) for a public template by its URL slug. + * The full response object is returned directly since the API returns a flat + * structure rather than wrapping the blueprint in a named property. + * @param templateUrl The URL slug of the template (e.g. "12289-add-webhook-data-to-a-google-sheet") + * @returns Promise with the full blueprint response + * + * @example + * ```typescript + * const blueprint = await make.publicTemplates.getBlueprint('12289-add-webhook-data-to-a-google-sheet'); + * ``` + */ + async getBlueprint(templateUrl: string): Promise { + return await this.#fetch(`/templates/public/${templateUrl}/blueprint`); + } +} diff --git a/src/index.ts b/src/index.ts index adad5d0..31b88cd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -166,4 +166,11 @@ export type { UpdateSDKWebhookBody, } from './endpoints/sdk/webhooks.js'; export type { Team, Teams, CreateTeamBody, ListTeamsOptions, GetTeamOptions } from './endpoints/teams.js'; +export type { + PublicTemplate, + PublicTemplateBlueprint, + PublicTemplates, + ListPublicTemplatesOptions, + GetPublicTemplateOptions, +} from './endpoints/public-templates.js'; export type { User, Users } from './endpoints/users.js'; diff --git a/src/make.ts b/src/make.ts index e9e81ea..8e8a903 100644 --- a/src/make.ts +++ b/src/make.ts @@ -15,6 +15,7 @@ import { Devices } from './endpoints/devices.js'; import { Functions } from './endpoints/functions.js'; import { Organizations } from './endpoints/organizations.js'; import { Enums } from './endpoints/enums.js'; +import { PublicTemplates } from './endpoints/public-templates.js'; import { SDKApps } from './endpoints/sdk/apps.js'; import { SDKModules } from './endpoints/sdk/modules.js'; import { SDKConnections } from './endpoints/sdk/connections.js'; @@ -163,6 +164,12 @@ export class Make { */ public readonly enums: Enums; + /** + * Access to public template endpoints. + * Public templates are approved, read-only scenario configurations discoverable and usable by any Make user. + */ + public readonly publicTemplates: PublicTemplates; + /** * Access to SDK-related endpoints */ @@ -248,6 +255,7 @@ export class Make { this.organizations = new Organizations(this.fetch.bind(this)); this.enums = new Enums(this.fetch.bind(this)); this.credentialRequests = new CredentialRequests(this.fetch.bind(this)); + this.publicTemplates = new PublicTemplates(this.fetch.bind(this)); this.sdk = { apps: new SDKApps(this.fetch.bind(this)), modules: new SDKModules(this.fetch.bind(this)), diff --git a/src/tools.ts b/src/tools.ts index bac7075..e916673 100644 --- a/src/tools.ts +++ b/src/tools.ts @@ -25,6 +25,7 @@ import { tools as FoldersTools } from './endpoints/folders.tools.js'; import { tools as IncompleteExecutionsTools } from './endpoints/incomplete-executions.tools.js'; import { tools as DataStructuresTools } from './endpoints/data-structures.tools.js'; import { tools as EnumsTools } from './endpoints/enums.tools.js'; +import { tools as PublicTemplatesTools } from './endpoints/public-templates.tools.js'; /** * JSON Schema definition for input parameters. @@ -196,4 +197,5 @@ export const MakeTools = [ ...OrganizationsTools, ...UsersTools, ...EnumsTools, + ...PublicTemplatesTools, ] as MakeTool[]; diff --git a/test/mocks/public-templates/blueprint.json b/test/mocks/public-templates/blueprint.json new file mode 100644 index 0000000..ba92222 --- /dev/null +++ b/test/mocks/public-templates/blueprint.json @@ -0,0 +1,34 @@ +{ + "blueprint": { + "name": "Http template example", + "flow": [ + { + "id": 1, + "module": "http:ActionSendData", + "version": 3, + "parameters": {}, + "mapper": { + "url": "https://example.com", + "method": "get" + }, + "metadata": { + "expect": [] + } + } + ], + "metadata": { + "version": 1 + } + }, + "controller": { + "name": "Http template example", + "modules": {}, + "idSequence": 2 + }, + "scheduling": { + "type": "indefinitely", + "interval": 900 + }, + "language": "en", + "metadata": null +} diff --git a/test/mocks/public-templates/get.json b/test/mocks/public-templates/get.json new file mode 100644 index 0000000..70d3daf --- /dev/null +++ b/test/mocks/public-templates/get.json @@ -0,0 +1,10 @@ +{ + "templatePublic": { + "id": 13, + "name": "Http template example", + "description": null, + "url": "13-http-template-example", + "usedApps": ["http"], + "usage": 321 + } +} diff --git a/test/mocks/public-templates/list.json b/test/mocks/public-templates/list.json new file mode 100644 index 0000000..bef7656 --- /dev/null +++ b/test/mocks/public-templates/list.json @@ -0,0 +1,26 @@ +{ + "templatesPublic": [ + { + "id": 13, + "name": "Http template example", + "description": null, + "url": "13-http-template-example", + "usedApps": ["http"], + "usage": 321 + }, + { + "id": 17, + "name": "Webhook to Email", + "description": "Send email when webhook is triggered", + "url": "17-webhook-to-email", + "usedApps": ["gateway", "gmail"], + "usage": 52 + } + ], + "pg": { + "sortBy": "usage", + "limit": 100, + "sortDir": "desc", + "offset": 0 + } +} diff --git a/test/public-templates.integration.test.ts b/test/public-templates.integration.test.ts new file mode 100644 index 0000000..6716ece --- /dev/null +++ b/test/public-templates.integration.test.ts @@ -0,0 +1,61 @@ +import 'dotenv/config'; +import { describe, expect, it } from '@jest/globals'; +import { Make } from '../src/make.js'; + +const MAKE_API_KEY = String(process.env.MAKE_API_KEY || ''); +const MAKE_ZONE = String(process.env.MAKE_ZONE || ''); + +describe('Integration: PublicTemplates', () => { + const make = new Make(MAKE_API_KEY, MAKE_ZONE); + + it('Should list public templates', async () => { + const templates = await make.publicTemplates.list(); + + expect(Array.isArray(templates)).toBe(true); + expect(templates.length).toBeGreaterThan(0); + expect(templates[0]).toHaveProperty('id'); + expect(templates[0]).toHaveProperty('name'); + expect(templates[0]).toHaveProperty('url'); + expect(templates[0]).toHaveProperty('usage'); + }); + + it('Should search public templates by name', async () => { + const templates = await make.publicTemplates.list({ name: 'http' }); + + expect(Array.isArray(templates)).toBe(true); + expect(templates.length).toBeGreaterThan(0); + expect(templates[0]).toHaveProperty('id'); + expect(templates[0]).toHaveProperty('name'); + }); + + it('Should search public templates with usedApps filter', async () => { + const templates = await make.publicTemplates.list({ usedApps: ['http'] }); + + expect(Array.isArray(templates)).toBe(true); + }); + + it('Should get a public template by URL slug', async () => { + const [first] = await make.publicTemplates.list({ name: 'http' }); + expect(first).toBeDefined(); + if (!first) { + throw new Error('Expected at least one public template matching "http"'); + } + const template = await make.publicTemplates.get(first.url); + + expect(template.id).toBe(first.id); + expect(template.url).toBe(first.url); + }); + + it('Should get a public template blueprint by URL slug', async () => { + const [first] = await make.publicTemplates.list({ name: 'http' }); + expect(first).toBeDefined(); + if (!first) { + throw new Error('Expected at least one public template matching "http"'); + } + const blueprint = await make.publicTemplates.getBlueprint(first.url); + + expect(blueprint).toHaveProperty('blueprint'); + expect(blueprint).toHaveProperty('scheduling'); + expect(blueprint).toHaveProperty('language'); + }); +}); diff --git a/test/public-templates.spec.ts b/test/public-templates.spec.ts new file mode 100644 index 0000000..1bb0c9b --- /dev/null +++ b/test/public-templates.spec.ts @@ -0,0 +1,41 @@ +import { describe, expect, it } from '@jest/globals'; +import { Make } from '../src/make.js'; +import { mockFetch } from './test.utils.js'; + +import * as listMock from './mocks/public-templates/list.json'; +import * as getMock from './mocks/public-templates/get.json'; +import * as blueprintMock from './mocks/public-templates/blueprint.json'; + +const MAKE_API_KEY = 'api-key'; +const MAKE_ZONE = 'make.local'; + +describe('Endpoints: PublicTemplates', () => { + const make = new Make(MAKE_API_KEY, MAKE_ZONE); + + it('Should list public templates with name search', async () => { + mockFetch('GET https://make.local/api/v2/templates/public?name=Http', listMock); + const result = await make.publicTemplates.list({ name: 'Http' }); + expect(result).toStrictEqual(listMock.templatesPublic); + }); + + it('Should serialize usedApps array and includeEn boolean query params', async () => { + mockFetch( + 'GET https://make.local/api/v2/templates/public?usedApps%5B%5D=gmail&usedApps%5B%5D=http&includeEn=true', + listMock, + ); + const result = await make.publicTemplates.list({ usedApps: ['gmail', 'http'], includeEn: true }); + expect(result).toStrictEqual(listMock.templatesPublic); + }); + + it('Should get public template by URL slug', async () => { + mockFetch('GET https://make.local/api/v2/templates/public/13-http-template-example', getMock); + const result = await make.publicTemplates.get('13-http-template-example'); + expect(result).toStrictEqual(getMock.templatePublic); + }); + + it('Should get public template blueprint by URL slug', async () => { + mockFetch('GET https://make.local/api/v2/templates/public/13-http-template-example/blueprint', blueprintMock); + const result = await make.publicTemplates.getBlueprint('13-http-template-example'); + expect(result).toStrictEqual(blueprintMock); + }); +});