astro-buffer 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +143 -2
- package/dist/index.d.ts +8 -0
- package/dist/index.js +237 -0
- package/package.json +43 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Agustín Díaz
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,144 @@
|
|
|
1
|
-
#
|
|
1
|
+
# astro-buffer
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A small Astro integration that shares newly published articles to your connected Buffer channels.
|
|
4
|
+
It uses Buffer's experimental `createContentItem` API: one request per article, with a variant for
|
|
5
|
+
each channel. No SDK, external database, or browser JavaScript.
|
|
6
|
+
|
|
7
|
+
## Setup
|
|
8
|
+
|
|
9
|
+
Requires Astro 7.x and Node 24 or newer. Node 24 LTS is the tested baseline.
|
|
10
|
+
|
|
11
|
+
Install it in your Astro project:
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
npm install astro-buffer
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Configure Astro with an endpoint that lists your articles, and the directory containing their
|
|
18
|
+
source files:
|
|
19
|
+
|
|
20
|
+
```js
|
|
21
|
+
import { defineConfig } from "astro/config";
|
|
22
|
+
import buffer from "astro-buffer";
|
|
23
|
+
|
|
24
|
+
export default defineConfig({
|
|
25
|
+
site: "https://example.com",
|
|
26
|
+
integrations: [
|
|
27
|
+
buffer({
|
|
28
|
+
posts: new URL("./src/buffer-posts.ts", import.meta.url),
|
|
29
|
+
contentDirectory: "src/content/blog",
|
|
30
|
+
// Optional: omit to use every connected, unlocked channel.
|
|
31
|
+
channelIds: ["your_linkedin_channel_id", "your_bluesky_channel_id"],
|
|
32
|
+
// Optional: use Buffer's posting schedule instead of publishing immediately.
|
|
33
|
+
mode: "addToQueue",
|
|
34
|
+
}),
|
|
35
|
+
],
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
In `src/buffer-posts.ts`, adapt your content collection and public routes:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import type { APIRoute } from "astro";
|
|
43
|
+
import { getCollection } from "astro:content";
|
|
44
|
+
|
|
45
|
+
export const GET: APIRoute = async ({ site }) => {
|
|
46
|
+
if (!site) throw new Error("Configure site before publishing.");
|
|
47
|
+
const posts = await getCollection("blog");
|
|
48
|
+
return Response.json(
|
|
49
|
+
posts.map((post) => ({
|
|
50
|
+
filePath: post.filePath,
|
|
51
|
+
title: post.data.title,
|
|
52
|
+
url: new URL(`/${post.id}/`, site).href,
|
|
53
|
+
})),
|
|
54
|
+
);
|
|
55
|
+
};
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
This endpoint is injected only for publishing builds and removed from the output before any API
|
|
59
|
+
requests. It is not injected during normal builds, development, preview, or sync, even if the
|
|
60
|
+
publishing flag is present for those non-build commands.
|
|
61
|
+
|
|
62
|
+
Each article may also include an optional `text` string in the endpoint response. It replaces
|
|
63
|
+
the title in the announcement, but not the content item's title. For example:
|
|
64
|
+
|
|
65
|
+
```js
|
|
66
|
+
{
|
|
67
|
+
filePath: post.filePath,
|
|
68
|
+
title: post.data.title,
|
|
69
|
+
url: new URL(`/${post.id}/`, site).href,
|
|
70
|
+
text: post.data.description,
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The integration always appends a blank line and the canonical article URL, preserving duplicate
|
|
75
|
+
detection. Omit `text` to keep the title + URL default. Provided text must be non-empty; surrounding
|
|
76
|
+
whitespace is trimmed. Dry runs show the same announcement text as publishing.
|
|
77
|
+
|
|
78
|
+
## Publishing
|
|
79
|
+
|
|
80
|
+
Normal builds and development never publish. Set these environment variables for a publishing build:
|
|
81
|
+
|
|
82
|
+
| Variable | Purpose |
|
|
83
|
+
| --- | --- |
|
|
84
|
+
| `BUFFER_PUBLISH=true` | Explicitly enable publishing. |
|
|
85
|
+
| `BUFFER_DRY_RUN=true` | Preview announcements without creating posts (also requires `BUFFER_PUBLISH=true`). |
|
|
86
|
+
| `BUFFER_API_KEY` | Buffer personal API key; keep this secret. |
|
|
87
|
+
| `BUFFER_ORGANIZATION_ID` | Organization containing the channels to share to. |
|
|
88
|
+
| `BUFFER_BASE_REF` | Full commit SHA before the newly added articles. |
|
|
89
|
+
|
|
90
|
+
Create a personal API key in Buffer's **Settings → API**, selecting only `account:read`,
|
|
91
|
+
`posts:read`, and `posts:write`. The integration reads channels, checks post history for duplicates,
|
|
92
|
+
and creates announcements; it does not manage account or channel settings.
|
|
93
|
+
|
|
94
|
+
Store the key in your CI secret store or an ignored local `.env` file. Load local environment
|
|
95
|
+
variables before running Astro; the integration reads `process.env` and does not load `.env`
|
|
96
|
+
itself. Find your organization ID with Buffer's `account { organizations { id name } }` query
|
|
97
|
+
and use the ID of the organization containing your channels.
|
|
98
|
+
|
|
99
|
+
Run `astro build` from the exact publishing commit in a Git checkout with the base revision
|
|
100
|
+
available. Only added files between that revision and `HEAD` are shared; edits, detected renames,
|
|
101
|
+
and archive entries are ignored. Directory paths are relative to the Astro project root.
|
|
102
|
+
|
|
103
|
+
Each article's production URL must return successfully without a redirect. The integration waits
|
|
104
|
+
about five minutes for deployment before failing. Posts use `title + URL` unless custom text is
|
|
105
|
+
provided. The default `mode: "shareNow"` publishes immediately; `mode: "addToQueue"` uses the next
|
|
106
|
+
available slot in each channel's Buffer posting schedule. Both use automatic delivery.
|
|
107
|
+
Disconnected and locked channels are reported and excluded.
|
|
108
|
+
|
|
109
|
+
The announcement contains no media. Networks requiring media or extra metadata may reject it.
|
|
110
|
+
Batch input validation is all-or-nothing. Subsequent processing errors can occur after Buffer
|
|
111
|
+
creates records; inspect the Buffer dashboard for delivery errors.
|
|
112
|
+
|
|
113
|
+
On failure, rerun with the same base revision. All history pages are checked for matching article
|
|
114
|
+
URLs per channel before creating anything. Existing failed posts, drafts, or approval requests
|
|
115
|
+
require attention in Buffer, not replacement posts. Keep their URL text and records intact.
|
|
116
|
+
Creation requests are never automatically retried; this is not an exactly-once delivery guarantee.
|
|
117
|
+
Run publishing builds serially to avoid races.
|
|
118
|
+
|
|
119
|
+
Dry runs still require credentials and a base revision. They read channels and post history,
|
|
120
|
+
report only missing announcements, and skip deployment checks and all Buffer mutations. Existing
|
|
121
|
+
failed posts still require attention and cause the run to fail.
|
|
122
|
+
|
|
123
|
+
Keep publishing separate from preview/PR builds. A production-only GitHub Actions workflow can
|
|
124
|
+
invoke `BUFFER_PUBLISH=true astro build` after checking out the publishing commit with
|
|
125
|
+
`fetch-depth: 0`. Pass `${{ github.event.before }}` as `BUFFER_BASE_REF` for push events. Serialize
|
|
126
|
+
runs with a concurrency group using `queue: max` so newer pushes do not replace pending runs.
|
|
127
|
+
|
|
128
|
+
## Development
|
|
129
|
+
|
|
130
|
+
Use Node 24 or newer. Tests exercise the compiled JavaScript that
|
|
131
|
+
ships to users, not TypeScript source:
|
|
132
|
+
|
|
133
|
+
```sh
|
|
134
|
+
npm ci
|
|
135
|
+
npm test
|
|
136
|
+
npm run build
|
|
137
|
+
npm run format:check
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
CI runs the build, tests, and formatting check on Node 24 LTS with Astro 7.
|
|
141
|
+
Buffer requests are mocked; no credentials or real social posts are involved.
|
|
142
|
+
|
|
143
|
+
References: [Astro Integration API](https://docs.astro.build/en/reference/integrations-reference/)
|
|
144
|
+
and [Buffer content items](https://developers.buffer.com/guides/content-items).
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AstroIntegration } from "astro";
|
|
2
|
+
export interface BufferOptions {
|
|
3
|
+
posts: URL;
|
|
4
|
+
contentDirectory: string;
|
|
5
|
+
channelIds?: string[];
|
|
6
|
+
mode?: "shareNow" | "addToQueue";
|
|
7
|
+
}
|
|
8
|
+
export default function buffer({ posts: entrypoint, contentDirectory, channelIds, mode, }: BufferOptions): AstroIntegration;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { readFile, unlink } from "node:fs/promises";
|
|
3
|
+
import timers from "node:timers/promises";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
async function request(token, query, variables) {
|
|
6
|
+
const response = await fetch("https://api.buffer.com", {
|
|
7
|
+
method: "POST",
|
|
8
|
+
headers: {
|
|
9
|
+
Authorization: `Bearer ${token}`,
|
|
10
|
+
"Content-Type": "application/json",
|
|
11
|
+
},
|
|
12
|
+
body: JSON.stringify({ query, variables }),
|
|
13
|
+
signal: AbortSignal.timeout(30_000),
|
|
14
|
+
});
|
|
15
|
+
if (!response.ok) {
|
|
16
|
+
const retryAfter = response.headers.get("Retry-After");
|
|
17
|
+
throw new Error(`Buffer request failed: HTTP ${response.status}.${retryAfter ? ` Retry after ${retryAfter} seconds.` : ""}`);
|
|
18
|
+
}
|
|
19
|
+
const result = await response.json();
|
|
20
|
+
if (result.errors?.length) {
|
|
21
|
+
throw new Error(`Buffer: ${result.errors.map((error) => error.message).join("; ")}`);
|
|
22
|
+
}
|
|
23
|
+
if (!result.data) {
|
|
24
|
+
throw new Error("Buffer returned no data.");
|
|
25
|
+
}
|
|
26
|
+
return result.data;
|
|
27
|
+
}
|
|
28
|
+
export default function buffer({ posts: entrypoint, contentDirectory, channelIds, mode = "shareNow", }) {
|
|
29
|
+
if (mode !== "shareNow" && mode !== "addToQueue") {
|
|
30
|
+
throw new Error('mode must be "shareNow" or "addToQueue".');
|
|
31
|
+
}
|
|
32
|
+
if (channelIds && (!channelIds.length || channelIds.some((id) => !id.trim()))) {
|
|
33
|
+
throw new Error("channelIds must contain at least one non-empty Buffer channel ID.");
|
|
34
|
+
}
|
|
35
|
+
let root;
|
|
36
|
+
let enabled = false;
|
|
37
|
+
const dryRun = process.env.BUFFER_DRY_RUN === "true";
|
|
38
|
+
return {
|
|
39
|
+
name: "astro-buffer",
|
|
40
|
+
hooks: {
|
|
41
|
+
"astro:config:setup": ({ injectRoute, command }) => {
|
|
42
|
+
enabled = command === "build" && process.env.BUFFER_PUBLISH === "true";
|
|
43
|
+
if (enabled) {
|
|
44
|
+
injectRoute({
|
|
45
|
+
pattern: "/buffer-posts.json",
|
|
46
|
+
entrypoint: fileURLToPath(entrypoint),
|
|
47
|
+
prerender: true,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"astro:config:done": ({ config }) => {
|
|
52
|
+
root = config.root;
|
|
53
|
+
},
|
|
54
|
+
"astro:build:done": async ({ dir, logger }) => {
|
|
55
|
+
if (!enabled)
|
|
56
|
+
return;
|
|
57
|
+
const manifest = new URL("buffer-posts.json", dir);
|
|
58
|
+
const articles = JSON.parse(await readFile(manifest, "utf8"));
|
|
59
|
+
await unlink(manifest);
|
|
60
|
+
const base = process.env.BUFFER_BASE_REF;
|
|
61
|
+
if (!base || !/^[a-f0-9]{40}$/i.test(base) || /^0+$/.test(base)) {
|
|
62
|
+
throw new Error("Set BUFFER_BASE_REF to the full commit SHA before the new posts.");
|
|
63
|
+
}
|
|
64
|
+
const added = new Set(execFileSync("git", [
|
|
65
|
+
"diff",
|
|
66
|
+
"--name-only",
|
|
67
|
+
"--relative",
|
|
68
|
+
"-z",
|
|
69
|
+
"--diff-filter=A",
|
|
70
|
+
"--find-renames",
|
|
71
|
+
base,
|
|
72
|
+
"HEAD",
|
|
73
|
+
"--",
|
|
74
|
+
contentDirectory,
|
|
75
|
+
], { cwd: fileURLToPath(root), encoding: "utf8" })
|
|
76
|
+
.split("\0")
|
|
77
|
+
.filter(Boolean));
|
|
78
|
+
const posts = articles
|
|
79
|
+
.filter((article) => added.has(article.filePath))
|
|
80
|
+
.map((article) => {
|
|
81
|
+
if (article.text !== undefined &&
|
|
82
|
+
(typeof article.text !== "string" || !article.text.trim())) {
|
|
83
|
+
throw new Error(`"${article.title}": announcement text must be a non-empty string.`);
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
...article,
|
|
87
|
+
text: `${article.text?.trim() ?? article.title}\n\n${article.url}`,
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
if (!posts.length) {
|
|
91
|
+
logger.info("No new blog posts to share.");
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const token = process.env.BUFFER_API_KEY;
|
|
95
|
+
const organizationId = process.env.BUFFER_ORGANIZATION_ID;
|
|
96
|
+
if (!token || !organizationId) {
|
|
97
|
+
throw new Error("Set BUFFER_API_KEY and BUFFER_ORGANIZATION_ID before publishing.");
|
|
98
|
+
}
|
|
99
|
+
const { channels } = await request(token, `query Channels($input: ChannelsInput!) {
|
|
100
|
+
channels(input: $input) { id name isDisconnected isLocked }
|
|
101
|
+
}`, { input: { organizationId } });
|
|
102
|
+
if (channelIds) {
|
|
103
|
+
const unknown = channelIds.filter((id) => !channels.some((channel) => channel.id === id));
|
|
104
|
+
if (unknown.length) {
|
|
105
|
+
throw new Error(`Unknown Buffer channel IDs: ${unknown.join(", ")}.`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const connected = channels.filter((channel) => {
|
|
109
|
+
if (channelIds && !channelIds.includes(channel.id))
|
|
110
|
+
return false;
|
|
111
|
+
if (channel.isDisconnected || channel.isLocked) {
|
|
112
|
+
logger.warn(`Cannot publish to "${channel.name}": disconnected or locked in Buffer.`);
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
return true;
|
|
116
|
+
});
|
|
117
|
+
if (!connected.length) {
|
|
118
|
+
throw new Error("No connected, unlocked Buffer channels found.");
|
|
119
|
+
}
|
|
120
|
+
const existing = [];
|
|
121
|
+
let after = null;
|
|
122
|
+
do {
|
|
123
|
+
const { posts: page, } = await request(token, `query Posts($input: PostsInput!, $after: String) {
|
|
124
|
+
posts(input: $input, first: 100, after: $after) {
|
|
125
|
+
edges { node { id text channelId status } }
|
|
126
|
+
pageInfo { hasNextPage endCursor }
|
|
127
|
+
}
|
|
128
|
+
}`, {
|
|
129
|
+
input: { organizationId, filter: { channelIds: connected.map(({ id }) => id) } },
|
|
130
|
+
after,
|
|
131
|
+
});
|
|
132
|
+
if (!page.edges) {
|
|
133
|
+
throw new Error("Buffer returned no post history.");
|
|
134
|
+
}
|
|
135
|
+
existing.push(...page.edges.map(({ node }) => node));
|
|
136
|
+
if (page.pageInfo.hasNextPage &&
|
|
137
|
+
(!page.pageInfo.endCursor || page.pageInfo.endCursor === after)) {
|
|
138
|
+
throw new Error("Buffer returned an invalid pagination cursor.");
|
|
139
|
+
}
|
|
140
|
+
after = page.pageInfo.hasNextPage ? page.pageInfo.endCursor : null;
|
|
141
|
+
} while (after);
|
|
142
|
+
const failures = [];
|
|
143
|
+
for (const article of posts) {
|
|
144
|
+
const pending = [];
|
|
145
|
+
for (const channel of connected) {
|
|
146
|
+
const shared = existing.find((post) => post.channelId === channel.id && post.text.split(/\s+/).includes(article.url));
|
|
147
|
+
if (shared) {
|
|
148
|
+
if (!["sent", "scheduled", "sending"].includes(shared.status)) {
|
|
149
|
+
failures.push(new Error(`Review Buffer post ${shared.id} for "${channel.name}": ${shared.status}.`));
|
|
150
|
+
}
|
|
151
|
+
logger.info(`Already in Buffer: "${article.title}" -> "${channel.name}".`);
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
pending.push(channel);
|
|
155
|
+
}
|
|
156
|
+
if (!pending.length)
|
|
157
|
+
continue;
|
|
158
|
+
if (dryRun) {
|
|
159
|
+
logger.info(`Dry run: ${JSON.stringify({
|
|
160
|
+
title: article.title,
|
|
161
|
+
text: article.text,
|
|
162
|
+
channels: pending.map(({ id, name }) => ({ id, name })),
|
|
163
|
+
mode,
|
|
164
|
+
})}`);
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
let live = false;
|
|
168
|
+
const deadline = Date.now() + 300_000;
|
|
169
|
+
while (Date.now() < deadline) {
|
|
170
|
+
let response;
|
|
171
|
+
try {
|
|
172
|
+
response = await fetch(article.url, {
|
|
173
|
+
method: "HEAD",
|
|
174
|
+
signal: AbortSignal.timeout(10_000),
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
if (!(error instanceof TypeError) &&
|
|
179
|
+
!(error instanceof Error && error.name === "TimeoutError")) {
|
|
180
|
+
throw error;
|
|
181
|
+
}
|
|
182
|
+
logger.warn(`Cannot reach ${article.url}: ${error.message}. Retrying.`);
|
|
183
|
+
}
|
|
184
|
+
if (response) {
|
|
185
|
+
if (response.ok && response.url === article.url) {
|
|
186
|
+
live = true;
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
if (![404, 502, 503, 504].includes(response.status)) {
|
|
190
|
+
throw new Error(`Cannot verify ${article.url}: HTTP ${response.status}.`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
logger.info(`Waiting for ${article.url} to be deployed...`);
|
|
194
|
+
await timers.setTimeout(10_000);
|
|
195
|
+
}
|
|
196
|
+
if (!live) {
|
|
197
|
+
throw new Error(`${article.url} is not live yet. Rerun after deployment succeeds.`);
|
|
198
|
+
}
|
|
199
|
+
const { createContentItem } = await request(token, `mutation Publish($input: CreateContentItemInput!) {
|
|
200
|
+
createContentItem(input: $input) {
|
|
201
|
+
... on CreateContentItemSuccess { content { id } }
|
|
202
|
+
... on CreateContentItemFailure {
|
|
203
|
+
message
|
|
204
|
+
errors { ... on MutationError { message } }
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}`, {
|
|
208
|
+
input: {
|
|
209
|
+
organizationId,
|
|
210
|
+
title: article.title,
|
|
211
|
+
posts: pending.map(({ id }) => ({
|
|
212
|
+
channelId: id,
|
|
213
|
+
text: article.text,
|
|
214
|
+
schedulingType: "automatic",
|
|
215
|
+
mode,
|
|
216
|
+
})),
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
if (!createContentItem.content?.id) {
|
|
220
|
+
failures.push(new Error(`"${article.title}": ${[
|
|
221
|
+
createContentItem.message ?? "No content item ID returned.",
|
|
222
|
+
...(createContentItem.errors ?? []).map(({ message }) => message),
|
|
223
|
+
].join("; ")}`));
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
logger.info(`Submitted "${article.title}" to ${pending.length} channels (${createContentItem.content.id}).`);
|
|
227
|
+
}
|
|
228
|
+
if (failures.length) {
|
|
229
|
+
throw new AggregateError(failures, [
|
|
230
|
+
"Some Buffer posts need attention; reruns skip existing posts.",
|
|
231
|
+
...failures.map((error) => error.message),
|
|
232
|
+
].join("\n"));
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,45 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "astro-buffer",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Share newly published Astro articles through Buffer.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"homepage": "https://github.com/HiroAgustin/astro-buffer#readme",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/HiroAgustin/astro-buffer.git"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"astro-integration",
|
|
13
|
+
"withastro",
|
|
14
|
+
"buffer",
|
|
15
|
+
"social-media"
|
|
16
|
+
],
|
|
17
|
+
"type": "module",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"default": "./dist/index.js"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist"
|
|
26
|
+
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsc",
|
|
29
|
+
"test": "npm run build && node --test test/*.test.mjs",
|
|
30
|
+
"format:check": "prettier --check src test",
|
|
31
|
+
"prepare": "npm run build"
|
|
32
|
+
},
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=24"
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"astro": "^7.0.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/node": "^26.6.6",
|
|
41
|
+
"astro": "^7.4.5",
|
|
42
|
+
"prettier": "^3.9.10",
|
|
43
|
+
"typescript": "^6.0.3"
|
|
44
|
+
}
|
|
45
|
+
}
|