From 6b268d7ad25cd7992a00a4d709f05eb6537fd4c0 Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Mon, 13 Jul 2026 17:00:31 -0700 Subject: [PATCH 01/18] feat(methods): Scaffold methods example set tooling Add shared package.json (ESM, @slack/web-api v8 RC, lint/check/test scripts), biome.json, .gitignore, and an index README for the new methods/ example set. Co-Authored-By: Claude --- methods/.gitignore | 1 + methods/README.md | 16 ++ methods/biome.json | 34 ++++ methods/package-lock.json | 341 ++++++++++++++++++++++++++++++++++++++ methods/package.json | 28 ++++ 5 files changed, 420 insertions(+) create mode 100644 methods/.gitignore create mode 100644 methods/README.md create mode 100644 methods/biome.json create mode 100644 methods/package-lock.json create mode 100644 methods/package.json diff --git a/methods/.gitignore b/methods/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/methods/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/methods/README.md b/methods/README.md new file mode 100644 index 0000000..8a324c2 --- /dev/null +++ b/methods/README.md @@ -0,0 +1,16 @@ +# Methods + +Examples of calling individual [Slack Web API methods](https://docs.slack.dev/reference/methods) with [`@slack/web-api`](https://www.npmjs.com/package/@slack/web-api). + +Each example is a complete, runnable Node script. Set the appropriate token in your environment and run it directly: + + export SLACK_TOKEN="xoxb-your-token" + node chat/src/chat-post-message.js + +Examples are grouped by method family. Each family documents the OAuth scopes its methods require, so you only grant what you need. + +## What's on display + +### chat + +- **[chat.postMessage](https://docs.slack.dev/reference/methods/chat.postmessage)**: Sends a message to a channel. [Implementation](./chat/src/chat-post-message.js). Scopes: `chat:write`. diff --git a/methods/biome.json b/methods/biome.json new file mode 100644 index 0000000..01ff577 --- /dev/null +++ b/methods/biome.json @@ -0,0 +1,34 @@ +{ + "$schema": "./node_modules/@biomejs/biome/configuration_schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + "files": { + "ignoreUnknown": false + }, + "formatter": { + "enabled": true, + "indentStyle": "space" + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "javascript": { + "formatter": { + "quoteStyle": "double" + } + }, + "assist": { + "enabled": true, + "actions": { + "source": { + "organizeImports": "on" + } + } + } +} diff --git a/methods/package-lock.json b/methods/package-lock.json new file mode 100644 index 0000000..98c38ec --- /dev/null +++ b/methods/package-lock.json @@ -0,0 +1,341 @@ +{ + "name": "methods", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "@slack/web-api": "8.0.0-rc.2" + }, + "devDependencies": { + "@biomejs/biome": "^2.5.3", + "@types/node": "^24.13.3", + "typescript": "^6.0.3" + } + }, + "node_modules/@biomejs/biome": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.5.3.tgz", + "integrity": "sha512-MrJswFdei9EfDwwUy2tQrPDpK0AO+RmMFvBoaaJ6ayBc3sUbHdCE+XG5N8vp+5So41ZupZJQm0roHFFhMGVD7A==", + "dev": true, + "license": "MIT OR Apache-2.0", + "bin": { + "biome": "bin/biome" + }, + "engines": { + "node": ">=14.21.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/biome" + }, + "optionalDependencies": { + "@biomejs/cli-darwin-arm64": "2.5.3", + "@biomejs/cli-darwin-x64": "2.5.3", + "@biomejs/cli-linux-arm64": "2.5.3", + "@biomejs/cli-linux-arm64-musl": "2.5.3", + "@biomejs/cli-linux-x64": "2.5.3", + "@biomejs/cli-linux-x64-musl": "2.5.3", + "@biomejs/cli-win32-arm64": "2.5.3", + "@biomejs/cli-win32-x64": "2.5.3" + } + }, + "node_modules/@biomejs/cli-darwin-arm64": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.5.3.tgz", + "integrity": "sha512-QhYP9muVQ0nUO5zztFuPbEwi4+94sJWVjaZds9aMi1l/KNZBiUjdiSUrGHsTaMGDXrYl+r4AS2sUKfgH3w+V3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-darwin-x64": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.5.3.tgz", + "integrity": "sha512-NC1Ss13UaW7QZX+y8j44bF7AP0jSJdBl6iRhe0MAkvaSqZy+mWg3GaXsrb+eSoHoGDBtaXWEbMVV0iVN2cZ7cQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.5.3.tgz", + "integrity": "sha512-ksx1KWeyYW18ILL04msF/J4ZBtBDN33znYK8Z/aNv/vlBVxL9/g3mGP+omgHJKy4+KWbK87vcmmpmurfNjSgiA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64-musl": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.5.3.tgz", + "integrity": "sha512-fccix0w6xp6csCXgxeC0dU/3ecgRQal0y+cv2SP9ajNlhe7Yrk2Ug7UDe2j9AT9ZDYitkXpvUKgZjjuoYeP4Vg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.5.3.tgz", + "integrity": "sha512-yMkJtilsgvILDcVkh187aVLTb64xYsrxYajx5kym+r1ULkO5HUOfu9AYKLGQbOVLwJtT2utNw7hhFNg+17mUYA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64-musl": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.5.3.tgz", + "integrity": "sha512-O/yU9YKRUiHhmcjF2f38PSjseVk3G4VLWYc0G2HWpzdBVREV6G8IGWIVEFf7MFPfWIzNUIvPsEjeAZQIOgnLcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-arm64": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.5.3.tgz", + "integrity": "sha512-cX5z+GYwRcqEok0AH3KSfQGgqYd0Nomfp6Fbe1uiTtELE38hdH2k842wQ9wLNaF/JJ7r4rjJQ4VR+ce+fRmQbw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-x64": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.5.3.tgz", + "integrity": "sha512-ExSaJWi4/u6+GXCszlSKpWSjKNbDseAYqqkCznsCsZ/4uidZ/BEqsCc5/3ctlq6dfIubdIIRSVLC/PG9xPl70Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@slack/logger": { + "version": "5.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@slack/logger/-/logger-5.0.0-rc.1.tgz", + "integrity": "sha512-3vO8zNGvk8n8tXpAzhIz1u/fHjhsLxGMhlZqzJEa3FxlXAe2lsY3qn8XBgKYEG2LmGP6ZWWmDq7vAPr2gZe2CQ==", + "license": "MIT", + "dependencies": { + "@types/node": ">=20" + }, + "engines": { + "node": ">= 20", + "npm": ">=9.6.4" + } + }, + "node_modules/@slack/types": { + "version": "3.0.0-rc.2", + "resolved": "https://registry.npmjs.org/@slack/types/-/types-3.0.0-rc.2.tgz", + "integrity": "sha512-2SF8sLu29VVKhavnA57exg2+fqxY6KrfQYVHzGPSW/M+b2sykzEa34ZALdhHrtEBzM26nB6i+DEcbKKQs8La9g==", + "license": "MIT", + "engines": { + "node": ">= 20", + "npm": ">=9.6.4" + } + }, + "node_modules/@slack/web-api": { + "version": "8.0.0-rc.2", + "resolved": "https://registry.npmjs.org/@slack/web-api/-/web-api-8.0.0-rc.2.tgz", + "integrity": "sha512-WL+FDNMpprPPabr9EOw/xv65mlFwSvQOUjbLcD6NJ2U5Vkzb1aKjnpVvJyPIQy6+vlWlXXFZZadOTlZViMWKDg==", + "license": "MIT", + "dependencies": { + "@slack/logger": "^5.0.0-rc.1", + "@slack/types": "^3.0.0-rc.1", + "@types/node": ">=20", + "@types/retry": "0.12.0", + "eventemitter3": "^5.0.1", + "p-queue": "^6", + "p-retry": "^4", + "retry": "^0.13.1" + }, + "engines": { + "node": ">= 20", + "npm": ">=9.6.4" + } + }, + "node_modules/@types/node": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", + "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "license": "MIT" + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue/node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "license": "MIT", + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "license": "MIT" + } + } +} diff --git a/methods/package.json b/methods/package.json new file mode 100644 index 0000000..f096471 --- /dev/null +++ b/methods/package.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://www.schemastore.org/package.json", + "private": true, + "type": "module", + "scripts": { + "check": "npx tsc --checkJs --noEmit --types node **/src/**/*.js **/tests/**/*.js", + "lint": "npx @biomejs/biome check .", + "lint:fix": "npx @biomejs/biome check --write .", + "test": "node --test" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/slack-samples/bolt-js-examples.git" + }, + "author": "Slack Technologies, LLC", + "bugs": { + "url": "https://github.com/slack-samples/bolt-js-examples/issues" + }, + "homepage": "https://github.com/slack-samples/bolt-js-examples/blob/main/methods/README.md", + "dependencies": { + "@slack/web-api": "8.0.0-rc.2" + }, + "devDependencies": { + "@biomejs/biome": "^2.5.3", + "@types/node": "^24.13.3", + "typescript": "^6.0.3" + } +} From 93125a7e32cb99b22cc2e31f7cedd4a54f95b84c Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Mon, 13 Jul 2026 17:04:37 -0700 Subject: [PATCH 02/18] feat(methods): Add chat.postMessage example Add a runnable chat.postMessage example, a fetch-stub test asserting the exact request URL and body, and a chat family README documenting the required chat:write scope. Also add methods/tsconfig.json (enables checkJs with include globs) and fix the check script to run `tsc -p tsconfig.json`, since npm does not expand the previous inline `**` globs and left type-checking inert. Co-Authored-By: Claude --- methods/chat/README.md | 18 +++++++++ methods/chat/src/chat-post-message.js | 13 ++++++ methods/chat/tests/chat-post-message.test.js | 42 ++++++++++++++++++++ methods/package.json | 2 +- methods/tsconfig.json | 13 ++++++ 5 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 methods/chat/README.md create mode 100644 methods/chat/src/chat-post-message.js create mode 100644 methods/chat/tests/chat-post-message.test.js create mode 100644 methods/tsconfig.json diff --git a/methods/chat/README.md b/methods/chat/README.md new file mode 100644 index 0000000..5e2d844 --- /dev/null +++ b/methods/chat/README.md @@ -0,0 +1,18 @@ +# chat + +Methods for sending and managing messages. + +## Required scopes + +| Method | Token type | Scopes | +| --- | --- | --- | +| [`chat.postMessage`](https://docs.slack.dev/reference/methods/chat.postmessage) | Bot | `chat:write` | + +## Examples + +- **[chat-post-message.js](./src/chat-post-message.js)** — posts a message to a channel with `chat.postMessage`. + +Set a bot token and run: + + export SLACK_TOKEN="xoxb-your-token" + node chat/src/chat-post-message.js diff --git a/methods/chat/src/chat-post-message.js b/methods/chat/src/chat-post-message.js new file mode 100644 index 0000000..0453a54 --- /dev/null +++ b/methods/chat/src/chat-post-message.js @@ -0,0 +1,13 @@ +import { WebClient } from "@slack/web-api"; + +// Read a token from the environment variables +const token = process.env.SLACK_TOKEN; + +// Initialize +const client = new WebClient(token); + +// Call the chat.postMessage method +await client.chat.postMessage({ + channel: "C123ABC456", + text: "Here's a message for you", +}); diff --git a/methods/chat/tests/chat-post-message.test.js b/methods/chat/tests/chat-post-message.test.js new file mode 100644 index 0000000..28d19e4 --- /dev/null +++ b/methods/chat/tests/chat-post-message.test.js @@ -0,0 +1,42 @@ +import * as assert from "node:assert"; +import { after, describe, it, mock } from "node:test"; + +describe("chat.postMessage", () => { + // Restore the patched global fetch once the suite finishes. + after(() => { + mock.restoreAll(); + }); + + it("sends the expected request", async () => { + process.env.SLACK_TOKEN = "xoxb-test"; + + // @slack/web-api v8 calls the native global fetch. Stub it and return a + // minimal successful Slack response so the SDK resolves cleanly. + const fetchMock = mock.method( + globalThis, + "fetch", + async () => + new Response(JSON.stringify({ ok: true }), { + status: 200, + headers: { "content-type": "application/json" }, + }), + ); + + // The example fires its API call as a side effect on import. + await import("../src/chat-post-message.js"); + + assert.strictEqual(fetchMock.mock.callCount(), 1); + const [url, init] = fetchMock.mock.calls[0].arguments; + assert.ok(init, "fetch was called with a request init"); + + // Exact URL — pins the method endpoint. + assert.strictEqual(String(url), "https://slack.com/api/chat.postMessage"); + + // Exact body — decode the url-encoded form and deep-equal the payload. + const params = Object.fromEntries(new URLSearchParams(String(init.body))); + assert.deepStrictEqual(params, { + channel: "C123ABC456", + text: "Here's a message for you", + }); + }); +}); diff --git a/methods/package.json b/methods/package.json index f096471..e8c8c4d 100644 --- a/methods/package.json +++ b/methods/package.json @@ -3,7 +3,7 @@ "private": true, "type": "module", "scripts": { - "check": "npx tsc --checkJs --noEmit --types node **/src/**/*.js **/tests/**/*.js", + "check": "npx tsc -p tsconfig.json", "lint": "npx @biomejs/biome check .", "lint:fix": "npx @biomejs/biome check --write .", "test": "node --test" diff --git a/methods/tsconfig.json b/methods/tsconfig.json new file mode 100644 index 0000000..7fe6acf --- /dev/null +++ b/methods/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "checkJs": true, + "noEmit": true, + "allowJs": true, + "module": "nodenext", + "moduleResolution": "nodenext", + "target": "es2022", + "types": ["node"], + "skipLibCheck": true + }, + "include": ["**/src/**/*.js", "**/tests/**/*.js"] +} From 2ccd69e15e2b937b65dedeba2bbf299c6c04f46e Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Mon, 13 Jul 2026 17:13:21 -0700 Subject: [PATCH 03/18] chore(methods): list methods example set in README and CI Add the methods/ example set to the repo README's demonstrations list and to the JS examples CI matrix so it is linted, type-checked, and tested on every push. Co-Authored-By: Claude --- .github/workflows/test.yml | 1 + README.md | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ac9f46b..ccf6ee6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,6 +16,7 @@ jobs: - "ai/slackbot-mcp-client/rich-responses/mcp-apps" - "ai/slackbot-mcp-client/slack-identity" - "block-kit" + - "methods" steps: - name: Checkout code uses: actions/checkout@v7 diff --git a/README.md b/README.md index 997d88e..0ec7209 100644 --- a/README.md +++ b/README.md @@ -6,3 +6,4 @@ This collections of examples highlights features of a Slack app in the language - **[AI in Slack](./ai)**: Agent experiences and MCP features in an interactive conversation interface. - **[Block Kit](./block-kit)**: The framework of visual components arranged to create app layouts. +- **[Methods](./methods)**: Individual Slack Web API method calls with the `@slack/web-api` client. From 91424a7b66a16170c93794909f2683788b1ae137 Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Mon, 13 Jul 2026 22:41:23 -0700 Subject: [PATCH 04/18] feat(methods): add blocks.validate example Adds a new blocks/ family with a blocks.validate example using client.apiCall since blocks.validate is not yet a typed method in @slack/web-api@8.0.0-rc.2. Includes the implementation, a fetch-stub test, and a README. Co-Authored-By: Claude --- methods/blocks/README.md | 22 ++++++++++ methods/blocks/src/blocks-validate.js | 16 +++++++ methods/blocks/tests/blocks-validate.test.js | 44 ++++++++++++++++++++ 3 files changed, 82 insertions(+) create mode 100644 methods/blocks/README.md create mode 100644 methods/blocks/src/blocks-validate.js create mode 100644 methods/blocks/tests/blocks-validate.test.js diff --git a/methods/blocks/README.md b/methods/blocks/README.md new file mode 100644 index 0000000..33ba43e --- /dev/null +++ b/methods/blocks/README.md @@ -0,0 +1,22 @@ +# blocks + +Methods for validating Block Kit payloads. + +## Required scopes + +| Method | Token type | Scopes | +| --- | --- | --- | +| [`blocks.validate`](https://docs.slack.dev/reference/methods/blocks.validate) | Any | None | + +> **Note:** `blocks.validate` is not yet a typed method in `@slack/web-api`, so the +> example uses the generic `client.apiCall(...)` escape hatch instead of a typed +> `client.blocks.validate(...)` call. + +## Examples + +- **[blocks-validate.js](./src/blocks-validate.js)** — validates a Block Kit payload with `blocks.validate`. + +Set a token and run: + + export SLACK_TOKEN="xoxb-your-token" + node blocks/src/blocks-validate.js diff --git a/methods/blocks/src/blocks-validate.js b/methods/blocks/src/blocks-validate.js new file mode 100644 index 0000000..004fb4d --- /dev/null +++ b/methods/blocks/src/blocks-validate.js @@ -0,0 +1,16 @@ +import { WebClient } from "@slack/web-api"; + +// Read a token from the environment variables +const token = process.env.SLACK_TOKEN; + +// Initialize +const client = new WebClient(token); + +// Validate a Block Kit payload with the blocks.validate method +const blocks = JSON.stringify([ + { type: "section", text: { type: "plain_text", text: "Hello world" } }, +]); + +// blocks.validate is not yet a typed method in @slack/web-api, so use the +// generic apiCall escape hatch instead of client.blocks.validate. +await client.apiCall("blocks.validate", { blocks }); diff --git a/methods/blocks/tests/blocks-validate.test.js b/methods/blocks/tests/blocks-validate.test.js new file mode 100644 index 0000000..045c979 --- /dev/null +++ b/methods/blocks/tests/blocks-validate.test.js @@ -0,0 +1,44 @@ +import * as assert from "node:assert"; +import { after, describe, it, mock } from "node:test"; + +describe("blocks.validate", () => { + // Restore the patched global fetch once the suite finishes. + after(() => { + mock.restoreAll(); + }); + + it("sends the expected request", async () => { + process.env.SLACK_TOKEN = "xoxb-test"; + + // @slack/web-api v8 calls the native global fetch. Stub it and return a + // minimal successful Slack response so the SDK resolves cleanly. + const fetchMock = mock.method( + globalThis, + "fetch", + async () => + new Response(JSON.stringify({ ok: true }), { + status: 200, + headers: { "content-type": "application/json" }, + }), + ); + + // The example fires its API call as a side effect on import. + await import("../src/blocks-validate.js"); + + assert.strictEqual(fetchMock.mock.callCount(), 1); + const [url, init] = fetchMock.mock.calls[0].arguments; + assert.ok(init, "fetch was called with a request init"); + + // Exact URL — pins the method endpoint. + assert.strictEqual(String(url), "https://slack.com/api/blocks.validate"); + + // Exact body — decode the url-encoded form and deep-equal the payload. + const params = Object.fromEntries(new URLSearchParams(String(init.body))); + const expectedBlocks = JSON.stringify([ + { type: "section", text: { type: "plain_text", text: "Hello world" } }, + ]); + assert.deepStrictEqual(params, { + blocks: expectedBlocks, + }); + }); +}); From e4c59796cb668fa9c5a33fd34c1eede22c199528 Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Mon, 13 Jul 2026 22:44:01 -0700 Subject: [PATCH 05/18] docs(methods): list blocks family in methods index Add the blocks.validate example to the methods/ index README so the new blocks family is discoverable alongside chat. Co-Authored-By: Claude --- methods/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/methods/README.md b/methods/README.md index 8a324c2..460ce1a 100644 --- a/methods/README.md +++ b/methods/README.md @@ -11,6 +11,10 @@ Examples are grouped by method family. Each family documents the OAuth scopes it ## What's on display +### blocks + +- **[blocks.validate](https://docs.slack.dev/reference/methods/blocks.validate)**: Validates Block Kit JSON payloads. [Implementation](./blocks/src/blocks-validate.js). Scopes: none. + ### chat - **[chat.postMessage](https://docs.slack.dev/reference/methods/chat.postmessage)**: Sends a message to a channel. [Implementation](./chat/src/chat-post-message.js). Scopes: `chat:write`. From c56d6188a80a1c62d8667caa641c364dd9204952 Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Mon, 13 Jul 2026 22:52:13 -0700 Subject: [PATCH 06/18] refactor(methods): remove comments from method tests Strip the explanatory comments from the chat.postMessage and blocks.validate fetch-stub tests; the assertions are self-explanatory. Co-Authored-By: Claude --- methods/blocks/tests/blocks-validate.test.js | 6 ------ methods/chat/tests/chat-post-message.test.js | 6 ------ 2 files changed, 12 deletions(-) diff --git a/methods/blocks/tests/blocks-validate.test.js b/methods/blocks/tests/blocks-validate.test.js index 045c979..c1448c4 100644 --- a/methods/blocks/tests/blocks-validate.test.js +++ b/methods/blocks/tests/blocks-validate.test.js @@ -2,7 +2,6 @@ import * as assert from "node:assert"; import { after, describe, it, mock } from "node:test"; describe("blocks.validate", () => { - // Restore the patched global fetch once the suite finishes. after(() => { mock.restoreAll(); }); @@ -10,8 +9,6 @@ describe("blocks.validate", () => { it("sends the expected request", async () => { process.env.SLACK_TOKEN = "xoxb-test"; - // @slack/web-api v8 calls the native global fetch. Stub it and return a - // minimal successful Slack response so the SDK resolves cleanly. const fetchMock = mock.method( globalThis, "fetch", @@ -22,17 +19,14 @@ describe("blocks.validate", () => { }), ); - // The example fires its API call as a side effect on import. await import("../src/blocks-validate.js"); assert.strictEqual(fetchMock.mock.callCount(), 1); const [url, init] = fetchMock.mock.calls[0].arguments; assert.ok(init, "fetch was called with a request init"); - // Exact URL — pins the method endpoint. assert.strictEqual(String(url), "https://slack.com/api/blocks.validate"); - // Exact body — decode the url-encoded form and deep-equal the payload. const params = Object.fromEntries(new URLSearchParams(String(init.body))); const expectedBlocks = JSON.stringify([ { type: "section", text: { type: "plain_text", text: "Hello world" } }, diff --git a/methods/chat/tests/chat-post-message.test.js b/methods/chat/tests/chat-post-message.test.js index 28d19e4..44a11f1 100644 --- a/methods/chat/tests/chat-post-message.test.js +++ b/methods/chat/tests/chat-post-message.test.js @@ -2,7 +2,6 @@ import * as assert from "node:assert"; import { after, describe, it, mock } from "node:test"; describe("chat.postMessage", () => { - // Restore the patched global fetch once the suite finishes. after(() => { mock.restoreAll(); }); @@ -10,8 +9,6 @@ describe("chat.postMessage", () => { it("sends the expected request", async () => { process.env.SLACK_TOKEN = "xoxb-test"; - // @slack/web-api v8 calls the native global fetch. Stub it and return a - // minimal successful Slack response so the SDK resolves cleanly. const fetchMock = mock.method( globalThis, "fetch", @@ -22,17 +19,14 @@ describe("chat.postMessage", () => { }), ); - // The example fires its API call as a side effect on import. await import("../src/chat-post-message.js"); assert.strictEqual(fetchMock.mock.callCount(), 1); const [url, init] = fetchMock.mock.calls[0].arguments; assert.ok(init, "fetch was called with a request init"); - // Exact URL — pins the method endpoint. assert.strictEqual(String(url), "https://slack.com/api/chat.postMessage"); - // Exact body — decode the url-encoded form and deep-equal the payload. const params = Object.fromEntries(new URLSearchParams(String(init.body))); assert.deepStrictEqual(params, { channel: "C123ABC456", From 6ef31bd7516dc36958db57d9941d5f2258b01203 Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Mon, 13 Jul 2026 23:20:20 -0700 Subject: [PATCH 07/18] refactor(methods): shorten test name and expand blocks JSON Rename the it() case to "sends" (the describe already names the method), and format the blocks.validate payload multi-line for readability. Co-Authored-By: Claude --- methods/blocks/src/blocks-validate.js | 8 +++++++- methods/blocks/tests/blocks-validate.test.js | 10 ++++++++-- methods/chat/tests/chat-post-message.test.js | 2 +- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/methods/blocks/src/blocks-validate.js b/methods/blocks/src/blocks-validate.js index 004fb4d..05e793b 100644 --- a/methods/blocks/src/blocks-validate.js +++ b/methods/blocks/src/blocks-validate.js @@ -8,7 +8,13 @@ const client = new WebClient(token); // Validate a Block Kit payload with the blocks.validate method const blocks = JSON.stringify([ - { type: "section", text: { type: "plain_text", text: "Hello world" } }, + { + type: "section", + text: { + type: "plain_text", + text: "Hello world", + }, + }, ]); // blocks.validate is not yet a typed method in @slack/web-api, so use the diff --git a/methods/blocks/tests/blocks-validate.test.js b/methods/blocks/tests/blocks-validate.test.js index c1448c4..330fc10 100644 --- a/methods/blocks/tests/blocks-validate.test.js +++ b/methods/blocks/tests/blocks-validate.test.js @@ -6,7 +6,7 @@ describe("blocks.validate", () => { mock.restoreAll(); }); - it("sends the expected request", async () => { + it("sends", async () => { process.env.SLACK_TOKEN = "xoxb-test"; const fetchMock = mock.method( @@ -29,7 +29,13 @@ describe("blocks.validate", () => { const params = Object.fromEntries(new URLSearchParams(String(init.body))); const expectedBlocks = JSON.stringify([ - { type: "section", text: { type: "plain_text", text: "Hello world" } }, + { + type: "section", + text: { + type: "plain_text", + text: "Hello world", + }, + }, ]); assert.deepStrictEqual(params, { blocks: expectedBlocks, diff --git a/methods/chat/tests/chat-post-message.test.js b/methods/chat/tests/chat-post-message.test.js index 44a11f1..0167099 100644 --- a/methods/chat/tests/chat-post-message.test.js +++ b/methods/chat/tests/chat-post-message.test.js @@ -6,7 +6,7 @@ describe("chat.postMessage", () => { mock.restoreAll(); }); - it("sends the expected request", async () => { + it("sends", async () => { process.env.SLACK_TOKEN = "xoxb-test"; const fetchMock = mock.method( From f2da9c418dd83d0a23b2659308c3fa932fa44bdb Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Mon, 13 Jul 2026 23:29:42 -0700 Subject: [PATCH 08/18] refactor(methods): inline expected params and drop assert messages Inline the expected blocks payload directly into the deepStrictEqual, and remove the assert.ok description string in both method tests. Co-Authored-By: Claude --- methods/blocks/tests/blocks-validate.test.js | 21 ++++++++++---------- methods/chat/tests/chat-post-message.test.js | 2 +- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/methods/blocks/tests/blocks-validate.test.js b/methods/blocks/tests/blocks-validate.test.js index 330fc10..4839235 100644 --- a/methods/blocks/tests/blocks-validate.test.js +++ b/methods/blocks/tests/blocks-validate.test.js @@ -23,22 +23,21 @@ describe("blocks.validate", () => { assert.strictEqual(fetchMock.mock.callCount(), 1); const [url, init] = fetchMock.mock.calls[0].arguments; - assert.ok(init, "fetch was called with a request init"); + assert.ok(init); assert.strictEqual(String(url), "https://slack.com/api/blocks.validate"); const params = Object.fromEntries(new URLSearchParams(String(init.body))); - const expectedBlocks = JSON.stringify([ - { - type: "section", - text: { - type: "plain_text", - text: "Hello world", - }, - }, - ]); assert.deepStrictEqual(params, { - blocks: expectedBlocks, + blocks: JSON.stringify([ + { + type: "section", + text: { + type: "plain_text", + text: "Hello world", + }, + }, + ]), }); }); }); diff --git a/methods/chat/tests/chat-post-message.test.js b/methods/chat/tests/chat-post-message.test.js index 0167099..b2fb2ac 100644 --- a/methods/chat/tests/chat-post-message.test.js +++ b/methods/chat/tests/chat-post-message.test.js @@ -23,7 +23,7 @@ describe("chat.postMessage", () => { assert.strictEqual(fetchMock.mock.callCount(), 1); const [url, init] = fetchMock.mock.calls[0].arguments; - assert.ok(init, "fetch was called with a request init"); + assert.ok(init); assert.strictEqual(String(url), "https://slack.com/api/chat.postMessage"); From 76f91d8924391c612635a41cd816698281e87d0f Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Mon, 13 Jul 2026 23:30:42 -0700 Subject: [PATCH 09/18] refactor(methods): inline params assert and move init guard Move assert.ok(init) to just before the body assertion and inline the URLSearchParams decode directly into deepStrictEqual in both method tests. Co-Authored-By: Claude --- methods/blocks/tests/blocks-validate.test.js | 26 +++++++++++--------- methods/chat/tests/chat-post-message.test.js | 14 ++++++----- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/methods/blocks/tests/blocks-validate.test.js b/methods/blocks/tests/blocks-validate.test.js index 4839235..82f7865 100644 --- a/methods/blocks/tests/blocks-validate.test.js +++ b/methods/blocks/tests/blocks-validate.test.js @@ -23,21 +23,23 @@ describe("blocks.validate", () => { assert.strictEqual(fetchMock.mock.callCount(), 1); const [url, init] = fetchMock.mock.calls[0].arguments; - assert.ok(init); assert.strictEqual(String(url), "https://slack.com/api/blocks.validate"); - const params = Object.fromEntries(new URLSearchParams(String(init.body))); - assert.deepStrictEqual(params, { - blocks: JSON.stringify([ - { - type: "section", - text: { - type: "plain_text", - text: "Hello world", + assert.ok(init); + assert.deepStrictEqual( + Object.fromEntries(new URLSearchParams(String(init.body))), + { + blocks: JSON.stringify([ + { + type: "section", + text: { + type: "plain_text", + text: "Hello world", + }, }, - }, - ]), - }); + ]), + }, + ); }); }); diff --git a/methods/chat/tests/chat-post-message.test.js b/methods/chat/tests/chat-post-message.test.js index b2fb2ac..79e3a60 100644 --- a/methods/chat/tests/chat-post-message.test.js +++ b/methods/chat/tests/chat-post-message.test.js @@ -23,14 +23,16 @@ describe("chat.postMessage", () => { assert.strictEqual(fetchMock.mock.callCount(), 1); const [url, init] = fetchMock.mock.calls[0].arguments; - assert.ok(init); assert.strictEqual(String(url), "https://slack.com/api/chat.postMessage"); - const params = Object.fromEntries(new URLSearchParams(String(init.body))); - assert.deepStrictEqual(params, { - channel: "C123ABC456", - text: "Here's a message for you", - }); + assert.ok(init); + assert.deepStrictEqual( + Object.fromEntries(new URLSearchParams(String(init.body))), + { + channel: "C123ABC456", + text: "Here's a message for you", + }, + ); }); }); From b12e26f1a7abd453b9601c6b8b1d0e005a6be1c9 Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Mon, 13 Jul 2026 23:33:39 -0700 Subject: [PATCH 10/18] style(methods): remove blank lines between test assertions Tighten the assertion block in both method tests. Co-Authored-By: Claude --- methods/blocks/tests/blocks-validate.test.js | 2 -- methods/chat/tests/chat-post-message.test.js | 2 -- 2 files changed, 4 deletions(-) diff --git a/methods/blocks/tests/blocks-validate.test.js b/methods/blocks/tests/blocks-validate.test.js index 82f7865..37021e1 100644 --- a/methods/blocks/tests/blocks-validate.test.js +++ b/methods/blocks/tests/blocks-validate.test.js @@ -23,9 +23,7 @@ describe("blocks.validate", () => { assert.strictEqual(fetchMock.mock.callCount(), 1); const [url, init] = fetchMock.mock.calls[0].arguments; - assert.strictEqual(String(url), "https://slack.com/api/blocks.validate"); - assert.ok(init); assert.deepStrictEqual( Object.fromEntries(new URLSearchParams(String(init.body))), diff --git a/methods/chat/tests/chat-post-message.test.js b/methods/chat/tests/chat-post-message.test.js index 79e3a60..eec87d1 100644 --- a/methods/chat/tests/chat-post-message.test.js +++ b/methods/chat/tests/chat-post-message.test.js @@ -23,9 +23,7 @@ describe("chat.postMessage", () => { assert.strictEqual(fetchMock.mock.callCount(), 1); const [url, init] = fetchMock.mock.calls[0].arguments; - assert.strictEqual(String(url), "https://slack.com/api/chat.postMessage"); - assert.ok(init); assert.deepStrictEqual( Object.fromEntries(new URLSearchParams(String(init.body))), From 3e7a6c384783dc2e4f70a7e6cc1b6ff17d47f5da Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Mon, 13 Jul 2026 23:40:34 -0700 Subject: [PATCH 11/18] docs(methods): align READMEs with block-kit and ai format Rework the methods index and family READMEs to match the house style used by block-kit and ai: matching one-line description, a "Read the docs" link, a "What's on display" bullet list with [Implementation] links, and a "Running an example" section instead of the ad hoc scopes tables. Co-Authored-By: Claude --- methods/README.md | 18 ++++-------------- methods/blocks/README.md | 24 +++++++++++------------- methods/chat/README.md | 20 ++++++++++---------- 3 files changed, 25 insertions(+), 37 deletions(-) diff --git a/methods/README.md b/methods/README.md index 460ce1a..88eccd1 100644 --- a/methods/README.md +++ b/methods/README.md @@ -1,20 +1,10 @@ # Methods -Examples of calling individual [Slack Web API methods](https://docs.slack.dev/reference/methods) with [`@slack/web-api`](https://www.npmjs.com/package/@slack/web-api). +Individual Slack Web API method calls with the `@slack/web-api` client. -Each example is a complete, runnable Node script. Set the appropriate token in your environment and run it directly: - - export SLACK_TOKEN="xoxb-your-token" - node chat/src/chat-post-message.js - -Examples are grouped by method family. Each family documents the OAuth scopes its methods require, so you only grant what you need. +Read the [docs](https://docs.slack.dev/reference/methods) to explore every method, or explore implementations of specific families. ## What's on display -### blocks - -- **[blocks.validate](https://docs.slack.dev/reference/methods/blocks.validate)**: Validates Block Kit JSON payloads. [Implementation](./blocks/src/blocks-validate.js). Scopes: none. - -### chat - -- **[chat.postMessage](https://docs.slack.dev/reference/methods/chat.postmessage)**: Sends a message to a channel. [Implementation](./chat/src/chat-post-message.js). Scopes: `chat:write`. +- **[blocks](blocks/)**: Validate Block Kit payloads. [Implementation](./blocks/). +- **[chat](chat/)**: Send and manage messages. [Implementation](./chat/). diff --git a/methods/blocks/README.md b/methods/blocks/README.md index 33ba43e..2ec6c60 100644 --- a/methods/blocks/README.md +++ b/methods/blocks/README.md @@ -1,22 +1,20 @@ # blocks -Methods for validating Block Kit payloads. +Validate Block Kit payloads. -## Required scopes +Read the [docs](https://docs.slack.dev/reference/methods#blocks) to explore more methods in the `blocks` family. -| Method | Token type | Scopes | -| --- | --- | --- | -| [`blocks.validate`](https://docs.slack.dev/reference/methods/blocks.validate) | Any | None | +## What's on display -> **Note:** `blocks.validate` is not yet a typed method in `@slack/web-api`, so the -> example uses the generic `client.apiCall(...)` escape hatch instead of a typed -> `client.blocks.validate(...)` call. +- **[blocks.validate](https://docs.slack.dev/reference/methods/blocks.validate)**: Validates Block Kit JSON payloads. [Implementation](./src/blocks-validate.js). Scopes: none. -## Examples +`blocks.validate` is not yet a typed method in `@slack/web-api`, so the example uses the generic `client.apiCall(...)` escape hatch instead of a typed `client.blocks.validate(...)` call. -- **[blocks-validate.js](./src/blocks-validate.js)** — validates a Block Kit payload with `blocks.validate`. +## Running an example -Set a token and run: +Set a token and run the script directly: - export SLACK_TOKEN="xoxb-your-token" - node blocks/src/blocks-validate.js +```sh +export SLACK_TOKEN="xoxb-your-token" +node blocks/src/blocks-validate.js +``` diff --git a/methods/chat/README.md b/methods/chat/README.md index 5e2d844..0ede6ff 100644 --- a/methods/chat/README.md +++ b/methods/chat/README.md @@ -1,18 +1,18 @@ # chat -Methods for sending and managing messages. +Send and manage messages. -## Required scopes +Read the [docs](https://docs.slack.dev/reference/methods#chat) to explore more methods in the `chat` family. -| Method | Token type | Scopes | -| --- | --- | --- | -| [`chat.postMessage`](https://docs.slack.dev/reference/methods/chat.postmessage) | Bot | `chat:write` | +## What's on display -## Examples +- **[chat.postMessage](https://docs.slack.dev/reference/methods/chat.postmessage)**: Sends a message to a channel. [Implementation](./src/chat-post-message.js). Scopes: `chat:write`. -- **[chat-post-message.js](./src/chat-post-message.js)** — posts a message to a channel with `chat.postMessage`. +## Running an example -Set a bot token and run: +Set a bot token and run the script directly: - export SLACK_TOKEN="xoxb-your-token" - node chat/src/chat-post-message.js +```sh +export SLACK_TOKEN="xoxb-your-token" +node chat/src/chat-post-message.js +``` From 7f42349b712a95b13bece17d262efcdeed0c300a Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Mon, 13 Jul 2026 23:45:09 -0700 Subject: [PATCH 12/18] refactor(methods): inline blocks payload in blocks.validate example Pass the blocks payload directly into apiCall, matching the inline style of the chat.postMessage example, instead of a separate const. Co-Authored-By: Claude --- methods/blocks/src/blocks-validate.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/methods/blocks/src/blocks-validate.js b/methods/blocks/src/blocks-validate.js index 05e793b..8a0956b 100644 --- a/methods/blocks/src/blocks-validate.js +++ b/methods/blocks/src/blocks-validate.js @@ -6,17 +6,17 @@ const token = process.env.SLACK_TOKEN; // Initialize const client = new WebClient(token); -// Validate a Block Kit payload with the blocks.validate method -const blocks = JSON.stringify([ - { - type: "section", - text: { - type: "plain_text", - text: "Hello world", - }, - }, -]); - +// Call the blocks.validate method // blocks.validate is not yet a typed method in @slack/web-api, so use the // generic apiCall escape hatch instead of client.blocks.validate. -await client.apiCall("blocks.validate", { blocks }); +await client.apiCall("blocks.validate", { + blocks: JSON.stringify([ + { + type: "section", + text: { + type: "plain_text", + text: "Hello world", + }, + }, + ]), +}); From 1817f5e56f435971f9c239076e8eeaff2457328c Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Mon, 13 Jul 2026 23:49:10 -0700 Subject: [PATCH 13/18] refactor(methods): drop apiCall note comment from blocks example Remove the inline comment explaining the apiCall escape hatch; the blocks README already covers why blocks.validate uses client.apiCall. Co-Authored-By: Claude --- methods/blocks/src/blocks-validate.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/methods/blocks/src/blocks-validate.js b/methods/blocks/src/blocks-validate.js index 8a0956b..2ed91d3 100644 --- a/methods/blocks/src/blocks-validate.js +++ b/methods/blocks/src/blocks-validate.js @@ -7,8 +7,6 @@ const token = process.env.SLACK_TOKEN; const client = new WebClient(token); // Call the blocks.validate method -// blocks.validate is not yet a typed method in @slack/web-api, so use the -// generic apiCall escape hatch instead of client.blocks.validate. await client.apiCall("blocks.validate", { blocks: JSON.stringify([ { From 40ec2022f9195fa81c0274f65aecdafd5c5b31b5 Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Tue, 14 Jul 2026 22:26:12 -0700 Subject: [PATCH 14/18] chore(methods): pin @slack/web-api to stable ^8.0.0 @slack/web-api v8 is now GA; move off the 8.0.0-rc.2 release candidate to the stable ^8.0.0 range, matching the caret style used by block-kit. Co-Authored-By: Claude --- methods/package-lock.json | 24 ++++++++++++------------ methods/package.json | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/methods/package-lock.json b/methods/package-lock.json index 98c38ec..4bcf71b 100644 --- a/methods/package-lock.json +++ b/methods/package-lock.json @@ -5,7 +5,7 @@ "packages": { "": { "dependencies": { - "@slack/web-api": "8.0.0-rc.2" + "@slack/web-api": "^8.0.0" }, "devDependencies": { "@biomejs/biome": "^2.5.3", @@ -189,9 +189,9 @@ } }, "node_modules/@slack/logger": { - "version": "5.0.0-rc.1", - "resolved": "https://registry.npmjs.org/@slack/logger/-/logger-5.0.0-rc.1.tgz", - "integrity": "sha512-3vO8zNGvk8n8tXpAzhIz1u/fHjhsLxGMhlZqzJEa3FxlXAe2lsY3qn8XBgKYEG2LmGP6ZWWmDq7vAPr2gZe2CQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@slack/logger/-/logger-5.0.0.tgz", + "integrity": "sha512-VGXhmmgsAo9shdQYh4tFDndd+7nsgp0Y5h0UPDaUp8K359pBasI6YdkMqFW3mCOxLQkq09qj7o7cq6f3DuXcJQ==", "license": "MIT", "dependencies": { "@types/node": ">=20" @@ -202,9 +202,9 @@ } }, "node_modules/@slack/types": { - "version": "3.0.0-rc.2", - "resolved": "https://registry.npmjs.org/@slack/types/-/types-3.0.0-rc.2.tgz", - "integrity": "sha512-2SF8sLu29VVKhavnA57exg2+fqxY6KrfQYVHzGPSW/M+b2sykzEa34ZALdhHrtEBzM26nB6i+DEcbKKQs8La9g==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@slack/types/-/types-3.0.0.tgz", + "integrity": "sha512-KNOqpnNAlsFt5Jk9XBclslQ0lobRIg/0tnhpmvZJAglHJx9E8oceN8hC3gaBzkR6UzQ9Wzq4rLsJ98wUcxWPfw==", "license": "MIT", "engines": { "node": ">= 20", @@ -212,13 +212,13 @@ } }, "node_modules/@slack/web-api": { - "version": "8.0.0-rc.2", - "resolved": "https://registry.npmjs.org/@slack/web-api/-/web-api-8.0.0-rc.2.tgz", - "integrity": "sha512-WL+FDNMpprPPabr9EOw/xv65mlFwSvQOUjbLcD6NJ2U5Vkzb1aKjnpVvJyPIQy6+vlWlXXFZZadOTlZViMWKDg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@slack/web-api/-/web-api-8.0.0.tgz", + "integrity": "sha512-ORx3XQryQPq2Jnxv5giSKXVoQRUeylrrymIR2S9fPzLjPcCts8RayMeBSZMcpfpAqp6fnBRuPW2UB6dUPUTEZA==", "license": "MIT", "dependencies": { - "@slack/logger": "^5.0.0-rc.1", - "@slack/types": "^3.0.0-rc.1", + "@slack/logger": "^5.0.0", + "@slack/types": "^3.0.0", "@types/node": ">=20", "@types/retry": "0.12.0", "eventemitter3": "^5.0.1", diff --git a/methods/package.json b/methods/package.json index e8c8c4d..3d5f900 100644 --- a/methods/package.json +++ b/methods/package.json @@ -18,7 +18,7 @@ }, "homepage": "https://github.com/slack-samples/bolt-js-examples/blob/main/methods/README.md", "dependencies": { - "@slack/web-api": "8.0.0-rc.2" + "@slack/web-api": "^8.0.0" }, "devDependencies": { "@biomejs/biome": "^2.5.3", From 441f58af59b6d622e15c318f0f4602dcc4c295bf Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Tue, 14 Jul 2026 23:09:01 -0700 Subject: [PATCH 15/18] feat(methods): add per-family manifests; align README copy Add a per-family manifest.json to each method family requesting only that family's scopes (chat -> chat:write; blocks -> none), delivering the "no god apps" goal as runnable app config. Move scope documentation out of the README bullets into the manifest, and use the methods' exact docs descriptions. Co-Authored-By: Claude --- methods/blocks/README.md | 4 ++-- methods/blocks/manifest.json | 22 ++++++++++++++++++++++ methods/chat/README.md | 4 ++-- methods/chat/manifest.json | 22 ++++++++++++++++++++++ 4 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 methods/blocks/manifest.json create mode 100644 methods/chat/manifest.json diff --git a/methods/blocks/README.md b/methods/blocks/README.md index 2ec6c60..62e4466 100644 --- a/methods/blocks/README.md +++ b/methods/blocks/README.md @@ -6,13 +6,13 @@ Read the [docs](https://docs.slack.dev/reference/methods#blocks) to explore more ## What's on display -- **[blocks.validate](https://docs.slack.dev/reference/methods/blocks.validate)**: Validates Block Kit JSON payloads. [Implementation](./src/blocks-validate.js). Scopes: none. +- **[blocks.validate](https://docs.slack.dev/reference/methods/blocks.validate)**: Validates blocks, messages, and views Block Kit JSON payloads. [Implementation](./src/blocks-validate.js). `blocks.validate` is not yet a typed method in `@slack/web-api`, so the example uses the generic `client.apiCall(...)` escape hatch instead of a typed `client.blocks.validate(...)` call. ## Running an example -Set a token and run the script directly: +Create an app from [`manifest.json`](./manifest.json) — `blocks.validate` requires no scopes — then set a token and run the script directly: ```sh export SLACK_TOKEN="xoxb-your-token" diff --git a/methods/blocks/manifest.json b/methods/blocks/manifest.json new file mode 100644 index 0000000..f137db7 --- /dev/null +++ b/methods/blocks/manifest.json @@ -0,0 +1,22 @@ +{ + "display_information": { + "name": "Methods - blocks", + "description": "Examples of calling blocks Web API methods" + }, + "features": { + "bot_user": { + "display_name": "Methods - blocks", + "always_online": true + } + }, + "oauth_config": { + "scopes": { + "bot": [] + } + }, + "settings": { + "org_deploy_enabled": true, + "socket_mode_enabled": false, + "token_rotation_enabled": false + } +} diff --git a/methods/chat/README.md b/methods/chat/README.md index 0ede6ff..243fb04 100644 --- a/methods/chat/README.md +++ b/methods/chat/README.md @@ -6,11 +6,11 @@ Read the [docs](https://docs.slack.dev/reference/methods#chat) to explore more m ## What's on display -- **[chat.postMessage](https://docs.slack.dev/reference/methods/chat.postmessage)**: Sends a message to a channel. [Implementation](./src/chat-post-message.js). Scopes: `chat:write`. +- **[chat.postMessage](https://docs.slack.dev/reference/methods/chat.postmessage)**: Sends a message to a channel. [Implementation](./src/chat-post-message.js). ## Running an example -Set a bot token and run the script directly: +Create an app from [`manifest.json`](./manifest.json) — which requests only the scopes this family needs — then set a bot token and run the script directly: ```sh export SLACK_TOKEN="xoxb-your-token" diff --git a/methods/chat/manifest.json b/methods/chat/manifest.json new file mode 100644 index 0000000..5dea6d2 --- /dev/null +++ b/methods/chat/manifest.json @@ -0,0 +1,22 @@ +{ + "display_information": { + "name": "Methods - chat", + "description": "Examples of calling chat Web API methods" + }, + "features": { + "bot_user": { + "display_name": "Methods - chat", + "always_online": true + } + }, + "oauth_config": { + "scopes": { + "bot": ["chat:write"] + } + }, + "settings": { + "org_deploy_enabled": true, + "socket_mode_enabled": false, + "token_rotation_enabled": false + } +} From 79a7f02cef7f8d5891afe4ce78f4cdb73008c864 Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Tue, 14 Jul 2026 23:43:47 -0700 Subject: [PATCH 16/18] feat(methods): make each family a scoped Slack CLI app Turn each method family into a self-contained Slack CLI app: add @slack/cli-hooks, a per-family .slack/ workspace (config + hooks, with local state gitignored), and a manifest.json requesting only that family's scopes (chat -> chat:write; blocks -> none). This delivers the "no god apps" goal as runnable, minimally-scoped app config. READMEs use the methods' exact docs descriptions and document the `slack install` flow. Co-Authored-By: Claude --- methods/blocks/.slack/.gitignore | 2 ++ methods/blocks/.slack/config.json | 6 ++++ methods/blocks/.slack/hooks.json | 5 ++++ methods/blocks/README.md | 10 +++++-- methods/blocks/manifest.json | 6 ++-- methods/chat/.slack/.gitignore | 2 ++ methods/chat/.slack/config.json | 6 ++++ methods/chat/.slack/hooks.json | 5 ++++ methods/chat/README.md | 10 +++++-- methods/chat/manifest.json | 6 ++-- methods/package-lock.json | 46 +++++++++++++++++++++++++++++++ methods/package.json | 1 + 12 files changed, 95 insertions(+), 10 deletions(-) create mode 100644 methods/blocks/.slack/.gitignore create mode 100644 methods/blocks/.slack/config.json create mode 100644 methods/blocks/.slack/hooks.json create mode 100644 methods/chat/.slack/.gitignore create mode 100644 methods/chat/.slack/config.json create mode 100644 methods/chat/.slack/hooks.json diff --git a/methods/blocks/.slack/.gitignore b/methods/blocks/.slack/.gitignore new file mode 100644 index 0000000..973ba60 --- /dev/null +++ b/methods/blocks/.slack/.gitignore @@ -0,0 +1,2 @@ +apps.dev.json +cache/ diff --git a/methods/blocks/.slack/config.json b/methods/blocks/.slack/config.json new file mode 100644 index 0000000..909afbe --- /dev/null +++ b/methods/blocks/.slack/config.json @@ -0,0 +1,6 @@ +{ + "manifest": { + "source": "local" + }, + "project_id": "00000000-0000-0000-0000-000000000000" +} diff --git a/methods/blocks/.slack/hooks.json b/methods/blocks/.slack/hooks.json new file mode 100644 index 0000000..063cb30 --- /dev/null +++ b/methods/blocks/.slack/hooks.json @@ -0,0 +1,5 @@ +{ + "hooks": { + "get-hooks": "npx -q --no-install -p @slack/cli-hooks slack-cli-get-hooks" + } +} diff --git a/methods/blocks/README.md b/methods/blocks/README.md index 62e4466..58d8281 100644 --- a/methods/blocks/README.md +++ b/methods/blocks/README.md @@ -12,9 +12,15 @@ Read the [docs](https://docs.slack.dev/reference/methods#blocks) to explore more ## Running an example -Create an app from [`manifest.json`](./manifest.json) — `blocks.validate` requires no scopes — then set a token and run the script directly: +This family is a self-contained Slack CLI app whose [`manifest.json`](./manifest.json) requests no scopes (`blocks.validate` requires none). From this directory, install the app and grab a token: + +```sh +slack install +``` + +Then set the token and run the example directly: ```sh export SLACK_TOKEN="xoxb-your-token" -node blocks/src/blocks-validate.js +node src/blocks-validate.js ``` diff --git a/methods/blocks/manifest.json b/methods/blocks/manifest.json index f137db7..0c294dd 100644 --- a/methods/blocks/manifest.json +++ b/methods/blocks/manifest.json @@ -1,11 +1,11 @@ { "display_information": { - "name": "Methods - blocks", - "description": "Examples of calling blocks Web API methods" + "name": "Slack API Methods", + "description": "Example implementations to call \"blocks\" methods" }, "features": { "bot_user": { - "display_name": "Methods - blocks", + "display_name": "Slack API Methods", "always_online": true } }, diff --git a/methods/chat/.slack/.gitignore b/methods/chat/.slack/.gitignore new file mode 100644 index 0000000..973ba60 --- /dev/null +++ b/methods/chat/.slack/.gitignore @@ -0,0 +1,2 @@ +apps.dev.json +cache/ diff --git a/methods/chat/.slack/config.json b/methods/chat/.slack/config.json new file mode 100644 index 0000000..909afbe --- /dev/null +++ b/methods/chat/.slack/config.json @@ -0,0 +1,6 @@ +{ + "manifest": { + "source": "local" + }, + "project_id": "00000000-0000-0000-0000-000000000000" +} diff --git a/methods/chat/.slack/hooks.json b/methods/chat/.slack/hooks.json new file mode 100644 index 0000000..063cb30 --- /dev/null +++ b/methods/chat/.slack/hooks.json @@ -0,0 +1,5 @@ +{ + "hooks": { + "get-hooks": "npx -q --no-install -p @slack/cli-hooks slack-cli-get-hooks" + } +} diff --git a/methods/chat/README.md b/methods/chat/README.md index 243fb04..47f84f8 100644 --- a/methods/chat/README.md +++ b/methods/chat/README.md @@ -10,9 +10,15 @@ Read the [docs](https://docs.slack.dev/reference/methods#chat) to explore more m ## Running an example -Create an app from [`manifest.json`](./manifest.json) — which requests only the scopes this family needs — then set a bot token and run the script directly: +This family is a self-contained Slack CLI app whose [`manifest.json`](./manifest.json) requests only the scopes it needs (`chat:write`). From this directory, install the app and grab a bot token: + +```sh +slack install +``` + +Then set the token and run the example directly: ```sh export SLACK_TOKEN="xoxb-your-token" -node chat/src/chat-post-message.js +node src/chat-post-message.js ``` diff --git a/methods/chat/manifest.json b/methods/chat/manifest.json index 5dea6d2..ad13ee2 100644 --- a/methods/chat/manifest.json +++ b/methods/chat/manifest.json @@ -1,11 +1,11 @@ { "display_information": { - "name": "Methods - chat", - "description": "Examples of calling chat Web API methods" + "name": "Slack API Methods", + "description": "Example implementations to call \"chat\" methods" }, "features": { "bot_user": { - "display_name": "Methods - chat", + "display_name": "Slack API Methods", "always_online": true } }, diff --git a/methods/package-lock.json b/methods/package-lock.json index 4bcf71b..838e535 100644 --- a/methods/package-lock.json +++ b/methods/package-lock.json @@ -9,6 +9,7 @@ }, "devDependencies": { "@biomejs/biome": "^2.5.3", + "@slack/cli-hooks": "^1.3.3", "@types/node": "^24.13.3", "typescript": "^6.0.3" } @@ -188,6 +189,28 @@ "node": ">=14.21.3" } }, + "node_modules/@slack/cli-hooks": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@slack/cli-hooks/-/cli-hooks-1.3.3.tgz", + "integrity": "sha512-mdhYkDQcAxfXUJs6s25bjkM5mm7nqR5sbaS9X7cmmAgDW8FiRRib6pfr3ihzA91IubRMT1728yM2fHZYfc80ZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.8", + "semver": "^7.5.4" + }, + "bin": { + "slack-cli-check-update": "src/check-update.js", + "slack-cli-doctor": "src/doctor.js", + "slack-cli-get-hooks": "src/get-hooks.js", + "slack-cli-get-manifest": "src/get-manifest.js", + "slack-cli-start": "src/start.js" + }, + "engines": { + "node": ">= 18", + "npm": ">= 8.6.0" + } + }, "node_modules/@slack/logger": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@slack/logger/-/logger-5.0.0.tgz", @@ -252,6 +275,16 @@ "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", "license": "MIT" }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", @@ -317,6 +350,19 @@ "node": ">= 4" } }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/typescript": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", diff --git a/methods/package.json b/methods/package.json index 3d5f900..c23145c 100644 --- a/methods/package.json +++ b/methods/package.json @@ -22,6 +22,7 @@ }, "devDependencies": { "@biomejs/biome": "^2.5.3", + "@slack/cli-hooks": "^1.3.3", "@types/node": "^24.13.3", "typescript": "^6.0.3" } From 69801cb33ec2f9634ee34b5aee2b1a14eb4c8e0a Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Wed, 15 Jul 2026 00:25:27 -0700 Subject: [PATCH 17/18] chore(methods): bump cli-hooks to ^2.0.0; enable skipLibCheck MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move @slack/cli-hooks to the stable ^2.0.0 (the get-hooks bin is unchanged, so .slack/hooks.json still works). Set skipLibCheck: false so tsc fully type-checks the SDK's declarations too — verified the @slack/web-api v8 and @types/node types pass cleanly. Co-Authored-By: Claude --- methods/package-lock.json | 12 ++++++------ methods/package.json | 2 +- methods/tsconfig.json | 11 ++++++++--- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/methods/package-lock.json b/methods/package-lock.json index 838e535..8cad385 100644 --- a/methods/package-lock.json +++ b/methods/package-lock.json @@ -9,7 +9,7 @@ }, "devDependencies": { "@biomejs/biome": "^2.5.3", - "@slack/cli-hooks": "^1.3.3", + "@slack/cli-hooks": "^2.0.0", "@types/node": "^24.13.3", "typescript": "^6.0.3" } @@ -190,9 +190,9 @@ } }, "node_modules/@slack/cli-hooks": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@slack/cli-hooks/-/cli-hooks-1.3.3.tgz", - "integrity": "sha512-mdhYkDQcAxfXUJs6s25bjkM5mm7nqR5sbaS9X7cmmAgDW8FiRRib6pfr3ihzA91IubRMT1728yM2fHZYfc80ZA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@slack/cli-hooks/-/cli-hooks-2.0.0.tgz", + "integrity": "sha512-VLxGqJZwbrH3S+ovRhqlrcrKWHRDJtn3toraZKAcLaPqca5CgqTa/PmiCvCq3uUowiFw9B7FOB0y3ikQoDppTw==", "dev": true, "license": "MIT", "dependencies": { @@ -207,8 +207,8 @@ "slack-cli-start": "src/start.js" }, "engines": { - "node": ">= 18", - "npm": ">= 8.6.0" + "node": ">= 20", + "npm": ">=9.6.4" } }, "node_modules/@slack/logger": { diff --git a/methods/package.json b/methods/package.json index c23145c..dc7555c 100644 --- a/methods/package.json +++ b/methods/package.json @@ -22,7 +22,7 @@ }, "devDependencies": { "@biomejs/biome": "^2.5.3", - "@slack/cli-hooks": "^1.3.3", + "@slack/cli-hooks": "^2.0.0", "@types/node": "^24.13.3", "typescript": "^6.0.3" } diff --git a/methods/tsconfig.json b/methods/tsconfig.json index 7fe6acf..70971bc 100644 --- a/methods/tsconfig.json +++ b/methods/tsconfig.json @@ -6,8 +6,13 @@ "module": "nodenext", "moduleResolution": "nodenext", "target": "es2022", - "types": ["node"], - "skipLibCheck": true + "types": [ + "node" + ], + "skipLibCheck": false }, - "include": ["**/src/**/*.js", "**/tests/**/*.js"] + "include": [ + "**/src/**/*.js", + "**/tests/**/*.js" + ] } From b7615ffe7db4ae908ef0c6ede0b140e203f94b50 Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Wed, 15 Jul 2026 00:26:20 -0700 Subject: [PATCH 18/18] chore(methods): drop explicit skipLibCheck (use default) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the skipLibCheck option entirely; it defaults to false, so tsc fully type-checks library declarations — matching block-kit (which sets no tsconfig and relies on the same default). The example type-checks against the real SDK declarations, which matters for trustworthy documentation. Co-Authored-By: Claude --- methods/tsconfig.json | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/methods/tsconfig.json b/methods/tsconfig.json index 70971bc..0b4db47 100644 --- a/methods/tsconfig.json +++ b/methods/tsconfig.json @@ -6,13 +6,7 @@ "module": "nodenext", "moduleResolution": "nodenext", "target": "es2022", - "types": [ - "node" - ], - "skipLibCheck": false + "types": ["node"] }, - "include": [ - "**/src/**/*.js", - "**/tests/**/*.js" - ] + "include": ["**/src/**/*.js", "**/tests/**/*.js"] }