feat: propagate Micropub deletes to ActivityPub and Bluesky
All checks were successful
Deploy Indiekit Server / deploy (push) Successful in 1m13s
All checks were successful
Deploy Indiekit Server / deploy (push) Successful in 1m13s
When a post is deleted from the web backend (Micropub action=delete), call each registered syndicator's delete() method so the post is also removed from the Fediverse (AP Delete/Tombstone) and Bluesky (com.atproto.repo.deleteRecord). - patch-bluesky-syndicator-delete: adds Bluesky#deletePost(bskyUrl) to lib/bluesky.js and BlueskySyndicator#delete(url, syndication) to index.js; the bsky.app URL is resolved from the syndication array that postData.delete() preserves in _deletedProperties - patch-micropub-delete-propagation: patches action.js case "delete" to iterate publication.syndicationTargets after postContent.delete() and fire syndicator.delete() fire-and-forget for any syndicator that exposes the method (errors logged, never break the 200 response) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,8 +5,8 @@
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"preinstall": "node scripts/setup-gitea-url-rewrite.mjs",
|
||||
"postinstall": "xattr -w com.apple.fileprovider.ignore#P 1 node_modules 2>/dev/null || true && node scripts/patch-lightningcss.mjs && node scripts/patch-endpoint-media-scope.mjs && node scripts/patch-endpoint-media-sharp-runtime.mjs && node scripts/patch-frontend-sharp-runtime.mjs && node scripts/patch-endpoint-files-upload-route.mjs && node scripts/patch-endpoint-files-upload-locales.mjs && node scripts/patch-endpoint-activitypub-locales.mjs && node scripts/patch-endpoint-homepage-locales.mjs && node scripts/patch-endpoint-homepage-identity-defaults.mjs && node scripts/patch-federation-unlisted-guards.mjs && node scripts/patch-endpoint-micropub-where-note-visibility.mjs && node scripts/patch-endpoint-podroll-opml-upload.mjs && node scripts/patch-frontend-serviceworker-file.mjs && node scripts/patch-endpoint-comments-locales.mjs && node scripts/patch-endpoint-posts-locales.mjs && node scripts/patch-endpoint-conversations-locales.mjs && node scripts/patch-conversations-collection-guards.mjs && node scripts/patch-indiekit-routes-rate-limits.mjs && node scripts/patch-indiekit-error-production-stack.mjs && node scripts/patch-indieauth-devmode-guard.mjs && node scripts/patch-listening-endpoint-runtime-guards.mjs && node scripts/patch-endpoint-github-changelog-categories.mjs && node scripts/patch-endpoint-github-contributions-log.mjs && node scripts/patch-store-github-error-message.mjs && node scripts/patch-store-github-update-fallback.mjs && node scripts/patch-store-github-gitea-methods.mjs && node scripts/patch-store-github-content-type.mjs && node scripts/patch-endpoint-blogroll-feeds-alias.mjs && node scripts/patch-endpoint-posts-uid-lookup.mjs && node scripts/patch-conversations-bluesky-self-filter.mjs && node scripts/patch-conversations-bluesky-cursor-fix.mjs && node scripts/patch-endpoint-micropub-source-filter.mjs && node scripts/patch-syndicate-force-checked-default.mjs && node scripts/patch-syndicate-normalize-syndication-array.mjs && node scripts/patch-endpoint-posts-fetch-diagnostic.mjs && node scripts/patch-micropub-fetch-internal-url.mjs && node scripts/patch-micropub-session-token.mjs && node scripts/patch-indiekit-endpoint-urls-protocol.mjs && node scripts/patch-webmention-sender-hentry-syntax.mjs && node scripts/patch-webmention-sender-retry.mjs && node scripts/patch-webmention-sender-livefetch.mjs && node scripts/patch-webmention-sender-empty-details.mjs && node scripts/patch-bluesky-syndicator-internal-url.mjs && node scripts/patch-bluesky-syndicator-media-type-guard.mjs && node scripts/patch-ap-skip-draft-syndication.mjs && node scripts/patch-ap-og-image.mjs && node scripts/patch-ap-webfinger-before-auth.mjs && node scripts/patch-ap-federation-bridge-base-url.mjs && node scripts/patch-ap-compose-default-checked.mjs && node scripts/patch-ap-mastodon-reply-threading.mjs && node scripts/patch-ap-mastodon-status-id.mjs && node scripts/patch-ap-interactions-send-guard.mjs && node scripts/patch-ap-syndicate-dedup.mjs && node scripts/patch-ap-mastodon-delete-fix.mjs && node scripts/patch-ap-status-reply-id.mjs && node scripts/patch-ap-inbox-publication-url.mjs && node scripts/patch-micropub-gitea-dispatch-conditional.mjs",
|
||||
"serve": "export NODE_ENV=${NODE_ENV:-production} INDIEKIT_DEBUG=${INDIEKIT_DEBUG:-0} && node scripts/preflight-production-security.mjs && node scripts/preflight-mongo-connection.mjs && node scripts/preflight-activitypub-rsa-key.mjs && node scripts/preflight-activitypub-profile-urls.mjs && node scripts/patch-lightningcss.mjs && node scripts/patch-endpoint-media-scope.mjs && node scripts/patch-endpoint-media-sharp-runtime.mjs && node scripts/patch-frontend-sharp-runtime.mjs && node scripts/patch-endpoint-files-upload-route.mjs && node scripts/patch-endpoint-files-upload-locales.mjs && node scripts/patch-endpoint-activitypub-locales.mjs && node scripts/patch-endpoint-homepage-locales.mjs && node scripts/patch-endpoint-homepage-identity-defaults.mjs && node scripts/patch-federation-unlisted-guards.mjs && node scripts/patch-endpoint-micropub-where-note-visibility.mjs && node scripts/patch-endpoint-podroll-opml-upload.mjs && node scripts/patch-frontend-serviceworker-file.mjs && node scripts/patch-endpoint-comments-locales.mjs && node scripts/patch-endpoint-posts-locales.mjs && node scripts/patch-endpoint-conversations-locales.mjs && node scripts/patch-conversations-collection-guards.mjs && node scripts/patch-indiekit-routes-rate-limits.mjs && node scripts/patch-indiekit-error-production-stack.mjs && node scripts/patch-indieauth-devmode-guard.mjs && node scripts/patch-listening-endpoint-runtime-guards.mjs && node scripts/patch-endpoint-github-changelog-categories.mjs && node scripts/patch-endpoint-github-contributions-log.mjs && node scripts/patch-store-github-error-message.mjs && node scripts/patch-store-github-update-fallback.mjs && node scripts/patch-store-github-gitea-methods.mjs && node scripts/patch-store-github-content-type.mjs && node scripts/patch-microsub-reader-ap-dispatch.mjs && node scripts/patch-microsub-compose-draft-guard.mjs && node scripts/patch-endpoint-blogroll-feeds-alias.mjs && node scripts/patch-endpoint-posts-uid-lookup.mjs && node scripts/patch-conversations-bluesky-self-filter.mjs && node scripts/patch-conversations-bluesky-cursor-fix.mjs && node scripts/patch-endpoint-micropub-source-filter.mjs && node scripts/patch-syndicate-force-checked-default.mjs && node scripts/patch-syndicate-normalize-syndication-array.mjs && node scripts/patch-endpoint-posts-fetch-diagnostic.mjs && node scripts/patch-micropub-fetch-internal-url.mjs && node scripts/patch-micropub-session-token.mjs && node scripts/patch-indiekit-endpoint-urls-protocol.mjs && node scripts/patch-webmention-sender-hentry-syntax.mjs && node scripts/patch-webmention-sender-retry.mjs && node scripts/patch-webmention-sender-livefetch.mjs && node scripts/patch-webmention-sender-empty-details.mjs && node scripts/patch-bluesky-syndicator-internal-url.mjs && node scripts/patch-bluesky-syndicator-media-type-guard.mjs && node scripts/patch-ap-skip-draft-syndication.mjs && node scripts/patch-ap-og-image.mjs && node scripts/patch-ap-webfinger-before-auth.mjs && node scripts/patch-ap-federation-bridge-base-url.mjs && node scripts/patch-ap-compose-default-checked.mjs && node scripts/patch-ap-mastodon-reply-threading.mjs && node scripts/patch-ap-mastodon-status-id.mjs && node scripts/patch-ap-interactions-send-guard.mjs && node scripts/patch-ap-syndicate-dedup.mjs && node scripts/patch-ap-mastodon-delete-fix.mjs && node scripts/patch-ap-status-reply-id.mjs && node scripts/patch-ap-inbox-publication-url.mjs && node --require ./metrics-shim.cjs node_modules/@indiekit/indiekit/bin/cli.js serve --config indiekit.config.mjs",
|
||||
"postinstall": "xattr -w com.apple.fileprovider.ignore#P 1 node_modules 2>/dev/null || true && node scripts/patch-lightningcss.mjs && node scripts/patch-endpoint-media-scope.mjs && node scripts/patch-endpoint-media-sharp-runtime.mjs && node scripts/patch-frontend-sharp-runtime.mjs && node scripts/patch-endpoint-files-upload-route.mjs && node scripts/patch-endpoint-files-upload-locales.mjs && node scripts/patch-endpoint-activitypub-locales.mjs && node scripts/patch-endpoint-homepage-locales.mjs && node scripts/patch-endpoint-homepage-identity-defaults.mjs && node scripts/patch-federation-unlisted-guards.mjs && node scripts/patch-endpoint-micropub-where-note-visibility.mjs && node scripts/patch-endpoint-podroll-opml-upload.mjs && node scripts/patch-frontend-serviceworker-file.mjs && node scripts/patch-endpoint-comments-locales.mjs && node scripts/patch-endpoint-posts-locales.mjs && node scripts/patch-endpoint-conversations-locales.mjs && node scripts/patch-conversations-collection-guards.mjs && node scripts/patch-indiekit-routes-rate-limits.mjs && node scripts/patch-indiekit-error-production-stack.mjs && node scripts/patch-indieauth-devmode-guard.mjs && node scripts/patch-listening-endpoint-runtime-guards.mjs && node scripts/patch-endpoint-github-changelog-categories.mjs && node scripts/patch-endpoint-github-contributions-log.mjs && node scripts/patch-store-github-error-message.mjs && node scripts/patch-store-github-update-fallback.mjs && node scripts/patch-store-github-gitea-methods.mjs && node scripts/patch-store-github-content-type.mjs && node scripts/patch-endpoint-blogroll-feeds-alias.mjs && node scripts/patch-endpoint-posts-uid-lookup.mjs && node scripts/patch-conversations-bluesky-self-filter.mjs && node scripts/patch-conversations-bluesky-cursor-fix.mjs && node scripts/patch-endpoint-micropub-source-filter.mjs && node scripts/patch-syndicate-force-checked-default.mjs && node scripts/patch-syndicate-normalize-syndication-array.mjs && node scripts/patch-endpoint-posts-fetch-diagnostic.mjs && node scripts/patch-micropub-fetch-internal-url.mjs && node scripts/patch-micropub-session-token.mjs && node scripts/patch-indiekit-endpoint-urls-protocol.mjs && node scripts/patch-webmention-sender-hentry-syntax.mjs && node scripts/patch-webmention-sender-retry.mjs && node scripts/patch-webmention-sender-livefetch.mjs && node scripts/patch-webmention-sender-empty-details.mjs && node scripts/patch-bluesky-syndicator-internal-url.mjs && node scripts/patch-bluesky-syndicator-media-type-guard.mjs && node scripts/patch-ap-skip-draft-syndication.mjs && node scripts/patch-ap-og-image.mjs && node scripts/patch-ap-webfinger-before-auth.mjs && node scripts/patch-ap-federation-bridge-base-url.mjs && node scripts/patch-ap-compose-default-checked.mjs && node scripts/patch-ap-mastodon-reply-threading.mjs && node scripts/patch-ap-mastodon-status-id.mjs && node scripts/patch-ap-interactions-send-guard.mjs && node scripts/patch-ap-syndicate-dedup.mjs && node scripts/patch-ap-mastodon-delete-fix.mjs && node scripts/patch-ap-status-reply-id.mjs && node scripts/patch-ap-inbox-publication-url.mjs && node scripts/patch-bluesky-syndicator-delete.mjs && node scripts/patch-micropub-delete-propagation.mjs && node scripts/patch-micropub-gitea-dispatch-conditional.mjs",
|
||||
"serve": "export NODE_ENV=${NODE_ENV:-production} INDIEKIT_DEBUG=${INDIEKIT_DEBUG:-0} && node scripts/preflight-production-security.mjs && node scripts/preflight-mongo-connection.mjs && node scripts/preflight-activitypub-rsa-key.mjs && node scripts/preflight-activitypub-profile-urls.mjs && node scripts/patch-lightningcss.mjs && node scripts/patch-endpoint-media-scope.mjs && node scripts/patch-endpoint-media-sharp-runtime.mjs && node scripts/patch-frontend-sharp-runtime.mjs && node scripts/patch-endpoint-files-upload-route.mjs && node scripts/patch-endpoint-files-upload-locales.mjs && node scripts/patch-endpoint-activitypub-locales.mjs && node scripts/patch-endpoint-homepage-locales.mjs && node scripts/patch-endpoint-homepage-identity-defaults.mjs && node scripts/patch-federation-unlisted-guards.mjs && node scripts/patch-endpoint-micropub-where-note-visibility.mjs && node scripts/patch-endpoint-podroll-opml-upload.mjs && node scripts/patch-frontend-serviceworker-file.mjs && node scripts/patch-endpoint-comments-locales.mjs && node scripts/patch-endpoint-posts-locales.mjs && node scripts/patch-endpoint-conversations-locales.mjs && node scripts/patch-conversations-collection-guards.mjs && node scripts/patch-indiekit-routes-rate-limits.mjs && node scripts/patch-indiekit-error-production-stack.mjs && node scripts/patch-indieauth-devmode-guard.mjs && node scripts/patch-listening-endpoint-runtime-guards.mjs && node scripts/patch-endpoint-github-changelog-categories.mjs && node scripts/patch-endpoint-github-contributions-log.mjs && node scripts/patch-store-github-error-message.mjs && node scripts/patch-store-github-update-fallback.mjs && node scripts/patch-store-github-gitea-methods.mjs && node scripts/patch-store-github-content-type.mjs && node scripts/patch-microsub-reader-ap-dispatch.mjs && node scripts/patch-microsub-compose-draft-guard.mjs && node scripts/patch-endpoint-blogroll-feeds-alias.mjs && node scripts/patch-endpoint-posts-uid-lookup.mjs && node scripts/patch-conversations-bluesky-self-filter.mjs && node scripts/patch-conversations-bluesky-cursor-fix.mjs && node scripts/patch-endpoint-micropub-source-filter.mjs && node scripts/patch-syndicate-force-checked-default.mjs && node scripts/patch-syndicate-normalize-syndication-array.mjs && node scripts/patch-endpoint-posts-fetch-diagnostic.mjs && node scripts/patch-micropub-fetch-internal-url.mjs && node scripts/patch-micropub-session-token.mjs && node scripts/patch-indiekit-endpoint-urls-protocol.mjs && node scripts/patch-webmention-sender-hentry-syntax.mjs && node scripts/patch-webmention-sender-retry.mjs && node scripts/patch-webmention-sender-livefetch.mjs && node scripts/patch-webmention-sender-empty-details.mjs && node scripts/patch-bluesky-syndicator-internal-url.mjs && node scripts/patch-bluesky-syndicator-media-type-guard.mjs && node scripts/patch-ap-skip-draft-syndication.mjs && node scripts/patch-ap-og-image.mjs && node scripts/patch-ap-webfinger-before-auth.mjs && node scripts/patch-ap-federation-bridge-base-url.mjs && node scripts/patch-ap-compose-default-checked.mjs && node scripts/patch-ap-mastodon-reply-threading.mjs && node scripts/patch-ap-mastodon-status-id.mjs && node scripts/patch-ap-interactions-send-guard.mjs && node scripts/patch-ap-syndicate-dedup.mjs && node scripts/patch-ap-mastodon-delete-fix.mjs && node scripts/patch-ap-status-reply-id.mjs && node scripts/patch-ap-inbox-publication-url.mjs && node scripts/patch-bluesky-syndicator-delete.mjs && node scripts/patch-micropub-delete-propagation.mjs && node --require ./metrics-shim.cjs node_modules/@indiekit/indiekit/bin/cli.js serve --config indiekit.config.mjs",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
|
||||
165
scripts/patch-bluesky-syndicator-delete.mjs
Normal file
165
scripts/patch-bluesky-syndicator-delete.mjs
Normal file
@@ -0,0 +1,165 @@
|
||||
/**
|
||||
* Patch: add delete() support to the Bluesky syndicator.
|
||||
*
|
||||
* When a Micropub delete fires, Indiekit should be able to call
|
||||
* syndicator.delete(url, syndication) to remove the corresponding post from
|
||||
* Bluesky. This syndicator currently has no delete() method at all.
|
||||
*
|
||||
* Fix A (bluesky.js):
|
||||
* Add Bluesky#deletePost(bskyUrl) — resolves the AT-URI from the bsky.app
|
||||
* profile URL (using the existing getPostParts helper) and calls
|
||||
* com.atproto.repo.deleteRecord to permanently delete the record.
|
||||
*
|
||||
* Fix B (index.js):
|
||||
* Add BlueskySyndicator#delete(url, syndication) — finds the bsky.app URL
|
||||
* in the syndication array passed by the Micropub delete action and delegates
|
||||
* to Bluesky#deletePost().
|
||||
*/
|
||||
|
||||
import { access, readFile, writeFile } from "node:fs/promises";
|
||||
|
||||
const MARKER = "// [patch] bluesky-syndicator-delete";
|
||||
|
||||
// ── Fix A: bluesky.js — add deletePost() method ───────────────────────────────
|
||||
|
||||
const blueskyClassCandidates = [
|
||||
"node_modules/@rmdes/indiekit-syndicator-bluesky/lib/bluesky.js",
|
||||
];
|
||||
|
||||
const OLD_POST_REPOST = ` async postRepost(postUrl) {
|
||||
const client = await this.#client();
|
||||
const post = await this.getPost(postUrl);
|
||||
const repost = await client.repost(post.uri, post.cid);
|
||||
return uriToPostUrl(this.profileUrl, repost.uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Post a quote post`;
|
||||
|
||||
const NEW_POST_REPOST = ` async postRepost(postUrl) {
|
||||
const client = await this.#client();
|
||||
const post = await this.getPost(postUrl);
|
||||
const repost = await client.repost(post.uri, post.cid);
|
||||
return uriToPostUrl(this.profileUrl, repost.uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a Bluesky post by its bsky.app profile URL. ${MARKER}
|
||||
* @param {string} bskyUrl - bsky.app post URL (e.g. https://bsky.app/profile/handle/post/rkey)
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async deletePost(bskyUrl) { ${MARKER}
|
||||
const client = await this.#client();
|
||||
const postParts = getPostParts(bskyUrl);
|
||||
await client.com.atproto.repo.deleteRecord({
|
||||
repo: postParts.did,
|
||||
collection: "app.bsky.feed.post",
|
||||
rkey: postParts.rkey,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Post a quote post`;
|
||||
|
||||
// ── Fix B: index.js — add delete() method to BlueskySyndicator ───────────────
|
||||
|
||||
const indexCandidates = [
|
||||
"node_modules/@rmdes/indiekit-syndicator-bluesky/index.js",
|
||||
];
|
||||
|
||||
const OLD_SYNDICATE_END = ` } catch (error) {
|
||||
throw new IndiekitError(error.message, {
|
||||
cause: error,
|
||||
plugin: this.name,
|
||||
status: error.statusCode,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
init(Indiekit) {`;
|
||||
|
||||
const NEW_SYNDICATE_END = ` } catch (error) {
|
||||
throw new IndiekitError(error.message, {
|
||||
cause: error,
|
||||
plugin: this.name,
|
||||
status: error.statusCode,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a previously syndicated post from Bluesky. ${MARKER}
|
||||
* @param {string} url - Original blog post URL (unused, for API parity)
|
||||
* @param {string[]} [syndication=[]] - Array of syndication URLs from the deleted post
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async delete(url, syndication = []) { ${MARKER}
|
||||
try {
|
||||
const bskyUrl = syndication.find((s) => s.includes("bsky.app"));
|
||||
if (!bskyUrl) {
|
||||
console.warn(\`[Bluesky] No Bluesky URL found in syndication for \${url} — skipping delete\`);
|
||||
return;
|
||||
}
|
||||
const bluesky = new Bluesky({
|
||||
identifier: this.options?.handle,
|
||||
password: this.options?.password,
|
||||
profileUrl: this.#profileUrl,
|
||||
serviceUrl: this.#serviceUrl,
|
||||
});
|
||||
await bluesky.deletePost(bskyUrl);
|
||||
console.log(\`[Bluesky] Deleted syndicated post: \${bskyUrl}\`);
|
||||
} catch (error) {
|
||||
throw new IndiekitError(error.message, {
|
||||
cause: error,
|
||||
plugin: this.name,
|
||||
status: error.statusCode,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
init(Indiekit) {`;
|
||||
|
||||
async function exists(p) {
|
||||
try { await access(p); return true; } catch { return false; }
|
||||
}
|
||||
|
||||
async function applyPatch(candidates, oldSnippet, newSnippet, label) {
|
||||
let checked = 0;
|
||||
let patched = 0;
|
||||
|
||||
for (const filePath of candidates) {
|
||||
if (!(await exists(filePath))) continue;
|
||||
checked++;
|
||||
|
||||
const source = await readFile(filePath, "utf8");
|
||||
if (source.includes(MARKER)) {
|
||||
console.log(`[postinstall] patch-bluesky-syndicator-delete: ${label} already applied to ${filePath}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!source.includes(oldSnippet)) {
|
||||
console.warn(`[postinstall] patch-bluesky-syndicator-delete: ${label} snippet not found in ${filePath}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
await writeFile(filePath, source.replace(oldSnippet, newSnippet), "utf8");
|
||||
patched++;
|
||||
console.log(`[postinstall] Applied patch-bluesky-syndicator-delete (${label}) to ${filePath}`);
|
||||
}
|
||||
|
||||
return { checked, patched };
|
||||
}
|
||||
|
||||
const a = await applyPatch(blueskyClassCandidates, OLD_POST_REPOST, NEW_POST_REPOST, "Bluesky#deletePost");
|
||||
const b = await applyPatch(indexCandidates, OLD_SYNDICATE_END, NEW_SYNDICATE_END, "BlueskySyndicator#delete");
|
||||
|
||||
const totalChecked = a.checked + b.checked;
|
||||
const totalPatched = a.patched + b.patched;
|
||||
|
||||
if (totalChecked === 0) {
|
||||
console.log("[postinstall] patch-bluesky-syndicator-delete: no target files found");
|
||||
} else if (totalPatched === 0) {
|
||||
console.log("[postinstall] patch-bluesky-syndicator-delete: already up to date");
|
||||
} else {
|
||||
console.log(`[postinstall] patch-bluesky-syndicator-delete: patched ${totalPatched} file(s)`);
|
||||
}
|
||||
85
scripts/patch-micropub-delete-propagation.mjs
Normal file
85
scripts/patch-micropub-delete-propagation.mjs
Normal file
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Patch: propagate Micropub deletes to registered syndicators.
|
||||
*
|
||||
* Bug:
|
||||
* action.js case "delete" only calls postData.delete() + postContent.delete().
|
||||
* It never notifies syndicators, so deleting a post from the web backend
|
||||
* leaves orphaned copies on ActivityPub (Fediverse) and Bluesky.
|
||||
*
|
||||
* Fix:
|
||||
* After postContent.delete() returns, iterate publication.syndicationTargets
|
||||
* and call syndicator.delete(url, syndication) on any syndicator that exposes
|
||||
* the method. The syndication URLs are read from data._deletedProperties
|
||||
* (preserved by postData.delete() before stripping the properties).
|
||||
*
|
||||
* Errors from individual syndicators are caught and logged — a failed remote
|
||||
* delete must never break the 200 response for the local delete.
|
||||
*/
|
||||
|
||||
import { access, readFile, writeFile } from "node:fs/promises";
|
||||
|
||||
const MARKER = "// [patch] micropub-delete-propagation";
|
||||
|
||||
const candidates = [
|
||||
"node_modules/@indiekit/endpoint-micropub/lib/controllers/action.js",
|
||||
];
|
||||
|
||||
const OLD_DELETE_CASE = ` case "delete": {
|
||||
data = await postData.delete(application, publication, url);
|
||||
content = await postContent.delete(publication, data);
|
||||
break;
|
||||
}`;
|
||||
|
||||
const NEW_DELETE_CASE = ` case "delete": { ${MARKER}
|
||||
data = await postData.delete(application, publication, url);
|
||||
content = await postContent.delete(publication, data);
|
||||
// Propagate delete to syndicators (AP, Bluesky, …) ${MARKER}
|
||||
const _deletedSyndication = data?._deletedProperties?.syndication
|
||||
? [data._deletedProperties.syndication].flat()
|
||||
: [];
|
||||
if (_deletedSyndication.length > 0 && publication.syndicationTargets?.length > 0) {
|
||||
for (const _syndicator of publication.syndicationTargets) {
|
||||
if (typeof _syndicator.delete === "function") {
|
||||
_syndicator.delete(url, _deletedSyndication).catch((err) =>
|
||||
console.warn(\`[Micropub] Syndicator delete failed (\${_syndicator.name}): \${err.message}\`),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}`;
|
||||
|
||||
async function exists(p) {
|
||||
try { await access(p); return true; } catch { return false; }
|
||||
}
|
||||
|
||||
let checked = 0;
|
||||
let patched = 0;
|
||||
|
||||
for (const filePath of candidates) {
|
||||
if (!(await exists(filePath))) continue;
|
||||
checked++;
|
||||
|
||||
const source = await readFile(filePath, "utf8");
|
||||
if (source.includes(MARKER)) {
|
||||
console.log(`[postinstall] patch-micropub-delete-propagation: already applied to ${filePath}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!source.includes(OLD_DELETE_CASE)) {
|
||||
console.warn(`[postinstall] patch-micropub-delete-propagation: snippet not found in ${filePath}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
await writeFile(filePath, source.replace(OLD_DELETE_CASE, NEW_DELETE_CASE), "utf8");
|
||||
patched++;
|
||||
console.log(`[postinstall] Applied patch-micropub-delete-propagation to ${filePath}`);
|
||||
}
|
||||
|
||||
if (checked === 0) {
|
||||
console.log("[postinstall] patch-micropub-delete-propagation: no target files found");
|
||||
} else if (patched === 0) {
|
||||
console.log("[postinstall] patch-micropub-delete-propagation: already up to date");
|
||||
} else {
|
||||
console.log(`[postinstall] patch-micropub-delete-propagation: patched ${patched} file(s)`);
|
||||
}
|
||||
Reference in New Issue
Block a user