Skip to content

Commit bd9efc7

Browse files
committed
Add support for custom social message
1 parent 47cfdaa commit bd9efc7

File tree

3 files changed

+185
-17
lines changed

3 files changed

+185
-17
lines changed

README.md

Lines changed: 120 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@ AutoPub plugins maintained by Strawberry GraphQL.
55
## Included plugins
66

77
- `InviteContributorsPlugin` (`strawberry_autopub_plugins.invite_contributors:InviteContributorsPlugin`)
8-
- Invites PR contributors to a GitHub organization after `autopub publish`.
9-
- Optionally adds them to a team.
8+
- Invites pull request contributors to a GitHub organization after `autopub publish`.
9+
- Can also add invited users to a GitHub team.
10+
- `TypefullyPlugin` (`strawberry_autopub_plugins.typefully:TypefullyPlugin`)
11+
- Creates Typefully drafts or scheduled posts for release announcements.
12+
- Supports per-platform templates for `x`, `linkedin`, `threads`, `bluesky`, and `mastodon`.
1013

1114
## Installation
1215

@@ -16,34 +19,141 @@ pip install strawberry-autopub-plugins
1619

1720
## Usage
1821

19-
Add the plugin path to your AutoPub config:
22+
Add one or more plugin paths to your AutoPub config:
2023

2124
```toml
2225
[tool.autopub]
2326
plugins = [
2427
"poetry",
2528
"github",
2629
"strawberry_autopub_plugins.invite_contributors:InviteContributorsPlugin",
30+
"strawberry_autopub_plugins.typefully:TypefullyPlugin",
2731
]
32+
```
33+
34+
Plugin config is keyed by each plugin's `id`:
35+
36+
- `invite_contributors`
37+
- `typefully`
38+
39+
## InviteContributorsPlugin
40+
41+
Plugin path:
2842

43+
```text
44+
strawberry_autopub_plugins.invite_contributors:InviteContributorsPlugin
45+
```
46+
47+
Required environment variables:
48+
49+
- `GITHUB_TOKEN`
50+
- `GITHUB_REPOSITORY`
51+
52+
Optional environment variables:
53+
54+
- `GITHUB_EVENT_PATH`
55+
56+
`GITHUB_TOKEN` must be able to invite users to the target organization.
57+
58+
Example config:
59+
60+
```toml
2961
[tool.autopub.plugin_config.invite_contributors]
3062
organization = "strawberry-graphql"
3163
team-slug = "strawberry-contributors"
3264
role = "direct_member"
65+
skip-bots = true
3366
include-co-authors = true
34-
# Optional: extend defaults.
35-
# By default, skip-bots is true and these users are excluded:
36-
# dependabot-preview[bot], dependabot-preview, dependabot, dependabot[bot]
3767
exclude-users = ["renovate[bot]"]
3868
dry-run = false
3969
```
4070

41-
## Required environment variables
71+
Options:
4272

43-
- `GITHUB_TOKEN`
44-
- `GITHUB_REPOSITORY`
73+
- `organization`: Target GitHub organization. If omitted, the plugin falls back to the repository organization.
74+
- `team-slug`: Optional team slug to add invited contributors to.
75+
- `role`: One of `direct_member`, `admin`, or `billing_manager`. Default: `direct_member`.
76+
- `skip-bots`: Skip logins ending in `[bot]`. Default: `true`.
77+
- `include-co-authors`: Include `Co-authored-by:` trailers found in commit messages. Default: `true`.
78+
- `exclude-users`: Additional usernames to skip. Defaults to `dependabot-preview[bot]`, `dependabot-preview`, `dependabot`, and `dependabot[bot]`.
79+
- `dry-run`: Print which users would be invited without sending invitations. Default: `false`.
80+
81+
## TypefullyPlugin
4582

46-
The token must be able to invite users to the target organization.
83+
Plugin path:
84+
85+
```text
86+
strawberry_autopub_plugins.typefully:TypefullyPlugin
87+
```
88+
89+
Required environment variables:
90+
91+
- `TYPEFULLY_API_KEY`
92+
93+
Optional environment variables:
94+
95+
- `TYPEFULLY_SOCIAL_SET_ID`
96+
97+
You can provide the social set ID either through `social-set-id` in config or `TYPEFULLY_SOCIAL_SET_ID`.
98+
99+
Example config:
100+
101+
```toml
102+
[tool.autopub.plugin_config.typefully]
103+
social-set-id = "abc-123"
104+
platforms = ["x", "linkedin", "bluesky"]
105+
project-name = "Strawberry"
106+
message-template = "{project_name} {version} has been released!\n\n{release_notes}"
107+
publish-mode = "draft"
108+
tags = ["release", "python"]
109+
max-length = 280
110+
truncation-suffix = "..."
111+
dry-run = false
112+
113+
[tool.autopub.plugin_config.typefully.platform-templates]
114+
x = "{project_name} {version} is out now.\n\n{release_notes}"
115+
linkedin = "{project_name} {version} has been released.\n\n{release_notes}"
116+
```
117+
118+
Options:
119+
120+
- `social-set-id`: Typefully social set to post into. Required unless `TYPEFULLY_SOCIAL_SET_ID` is set.
121+
- `platforms`: Platforms to enable. Supported values: `x`, `linkedin`, `threads`, `bluesky`, `mastodon`. Default: `["x"]`.
122+
- `message-template`: Default template for all platforms. Default: `{project_name} {version} has been released!\n\n{release_notes}`.
123+
- `platform-templates`: Per-platform template overrides.
124+
- `project-name`: Value exposed to templates as `{project_name}`.
125+
- `publish-mode`: One of `draft`, `now`, `next-free-slot`, or `scheduled`. Default: `draft`.
126+
- `publish-at`: Required when `publish-mode = "scheduled"`.
127+
- `tags`: Optional Typefully tags to attach to the draft.
128+
- `max-length`: Maximum post length before truncation. Default: `280`.
129+
- `truncation-suffix`: Suffix appended after truncation. Default: `...`.
130+
- `dry-run`: Print the request body without calling the Typefully API. Default: `false`.
131+
132+
Template variables:
133+
134+
- `{project_name}`
135+
- `{version}`
136+
- `{release_type}`
137+
- `{release_notes}`
138+
- `{previous_version}`
139+
140+
Release-specific override from `RELEASE.md` frontmatter:
141+
142+
```md
143+
---
144+
release type: patch
145+
social_message: |
146+
Strawberry {version} is out now.
147+
148+
Highlights:
149+
{release_notes}
150+
---
151+
152+
- Fixed X
153+
- Added Y
154+
```
155+
156+
When `social_message` is present in AutoPub frontmatter, the plugin uses it as the message template for all configured platforms and still expands the same template variables listed above.
47157

48158
## Development
49159

@@ -57,7 +167,3 @@ When changing dependencies, update the lockfile:
57167
```bash
58168
uv lock
59169
```
60-
61-
## Next plugin
62-
63-
This repository is structured to host multiple plugins under `src/strawberry_autopub_plugins/`.

src/strawberry_autopub_plugins/typefully.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,16 +83,34 @@ def _format_message(
8383

8484
return message
8585

86+
def _frontmatter_message_template(self, release_info: ReleaseInfo) -> str | None:
87+
additional_info = release_info.additional_info
88+
template = additional_info.get("social_message")
89+
90+
if template is None:
91+
template = additional_info.get("social-message")
92+
93+
if template is None:
94+
return None
95+
96+
if not isinstance(template, str):
97+
raise _autopub_error("social_message frontmatter value must be a string")
98+
99+
if not template.strip():
100+
raise _autopub_error("social_message frontmatter value cannot be empty")
101+
102+
return template
103+
86104
def _build_platforms_payload(
87105
self,
88106
release_info: ReleaseInfo,
89107
) -> dict[str, object]:
90108
platforms: dict[str, object] = {}
109+
frontmatter_template = self._frontmatter_message_template(release_info)
91110

92111
for platform in self.config.platforms:
93-
template = self.config.platform_templates.get(
94-
platform,
95-
self.config.message_template,
112+
template = frontmatter_template or self.config.platform_templates.get(
113+
platform, self.config.message_template
96114
)
97115
message = self._format_message(template, release_info)
98116
platforms[platform] = {

tests/test_typefully.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@ def _release_info(
1515
*,
1616
version: str | None = "1.0.0",
1717
previous_version: str | None = "0.9.0",
18+
additional_info: dict | None = None,
1819
) -> ReleaseInfo:
1920
return ReleaseInfo(
2021
release_type="patch",
2122
release_notes="Bug fixes and improvements",
23+
additional_info=additional_info or {},
2224
version=version,
2325
previous_version=previous_version,
2426
)
@@ -168,6 +170,48 @@ def test_custom_message_template(mock_urlopen, monkeypatch) -> None:
168170
assert body["platforms"]["x"]["posts"][0]["text"] == "v1.0.0 (patch) is out!"
169171

170172

173+
@patch("strawberry_autopub_plugins.typefully.urlopen")
174+
def test_frontmatter_social_message_overrides_templates(mock_urlopen, monkeypatch) -> None:
175+
plugin = _plugin_with_config(
176+
monkeypatch,
177+
config={
178+
"platforms": ["x", "linkedin"],
179+
"message-template": "Default {version}",
180+
"platform-templates": {"linkedin": "LinkedIn {version}"},
181+
"project-name": "Strawberry",
182+
},
183+
)
184+
185+
plugin.post_publish(
186+
_release_info(
187+
additional_info={
188+
"social_message": "{project_name} {version} shipped\n\n{release_notes}"
189+
}
190+
)
191+
)
192+
193+
body = _get_request_body(mock_urlopen)
194+
assert (
195+
body["platforms"]["x"]["posts"][0]["text"]
196+
== "Strawberry 1.0.0 shipped\n\nBug fixes and improvements"
197+
)
198+
assert (
199+
body["platforms"]["linkedin"]["posts"][0]["text"]
200+
== "Strawberry 1.0.0 shipped\n\nBug fixes and improvements"
201+
)
202+
203+
204+
def test_frontmatter_social_message_must_be_a_string(monkeypatch) -> None:
205+
plugin = _plugin_with_config(monkeypatch)
206+
207+
with pytest.raises(AutopubException, match="social_message frontmatter value must be a string"):
208+
plugin.post_publish(
209+
_release_info(
210+
additional_info={"social_message": ["not", "a", "string"]},
211+
)
212+
)
213+
214+
171215
@patch("strawberry_autopub_plugins.typefully.urlopen")
172216
def test_message_truncation(mock_urlopen, monkeypatch) -> None:
173217
plugin = _plugin_with_config(

0 commit comments

Comments
 (0)