fix(ap): fix reply threading — pre-check AP syndication and resolve in_reply_to_id immediately
Two bugs caused replies-to-replies to be posted as 'note' type without ActivityPub federation: 1. patch-ap-compose-default-checked: The AP reader compose form had defaultChecked hardcoded to '@rick@rmendes.net' (original dev's handle), so the AP syndication checkbox was never pre-checked. Fixed to use target.checked from the Micropub q=config response, which already carries checked: true for the AP syndicator. 2. patch-ap-mastodon-reply-threading: POST /api/v1/statuses deferred ap_timeline insertion until the Eleventy build webhook fired (30–120 s). If the user replied to their own new post before the build finished, findTimelineItemById returned null → inReplyTo = null → no in-reply-to in JF2 → post-type-discovery returned 'note' → reply saved at /notes/ and sent without inReplyTo in the AP activity, breaking thread display on remote servers. Fixed by eagerly inserting the provisional timeline item immediately after postContent.create() ($setOnInsert — idempotent; syndicator upsert later is a no-op). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+2
-2
@@ -4,8 +4,8 @@
|
|||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"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-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",
|
"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-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",
|
||||||
"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-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 --require ./metrics-shim.cjs node_modules/@indiekit/indiekit/bin/cli.js serve --config indiekit.config.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-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 --require ./metrics-shim.cjs node_modules/@indiekit/indiekit/bin/cli.js serve --config indiekit.config.mjs",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
/**
|
||||||
|
* Patch: fix hardcoded defaultChecked handles in AP reader compose controller.
|
||||||
|
*
|
||||||
|
* Root cause:
|
||||||
|
* composeController() in compose.js sets target.defaultChecked using a
|
||||||
|
* hardcoded name comparison:
|
||||||
|
*
|
||||||
|
* target.defaultChecked = name === "@rick@rmendes.net" || name === "@rmendes.net";
|
||||||
|
*
|
||||||
|
* These are the original developer's handles and will never match any target
|
||||||
|
* on this installation. As a result, ALL syndication checkboxes in the AP
|
||||||
|
* reader compose form are rendered unchecked, so replies composed through the
|
||||||
|
* AP reader are never syndicated to ActivityPub.
|
||||||
|
*
|
||||||
|
* Fix:
|
||||||
|
* Replace the hardcoded comparison with `target.checked === true`.
|
||||||
|
* The Micropub config endpoint (q=config) already returns each syndicator's
|
||||||
|
* `checked` state. The AP syndicator has `checked: true` in indiekit.config.mjs,
|
||||||
|
* so the AP checkbox will be pre-checked by default, matching the same behaviour
|
||||||
|
* as the microsub reader compose form.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { access, readFile, writeFile } from "node:fs/promises";
|
||||||
|
|
||||||
|
const MARKER = "// [patch] ap-compose-default-checked";
|
||||||
|
|
||||||
|
const candidates = [
|
||||||
|
"node_modules/@rmdes/indiekit-endpoint-activitypub/lib/controllers/compose.js",
|
||||||
|
"node_modules/@indiekit/indiekit/node_modules/@rmdes/indiekit-endpoint-activitypub/lib/controllers/compose.js",
|
||||||
|
];
|
||||||
|
|
||||||
|
const OLD_SNIPPET = ` // Default-check only AP (Fedify) and Bluesky targets
|
||||||
|
// "@rick@rmendes.net" = AP Fedify, "@rmendes.net" = Bluesky
|
||||||
|
for (const target of syndicationTargets) {
|
||||||
|
const name = target.name || "";
|
||||||
|
target.defaultChecked = name === "@rick@rmendes.net" || name === "@rmendes.net";
|
||||||
|
}`;
|
||||||
|
|
||||||
|
const NEW_SNIPPET = ` // Pre-check syndication targets based on their configured checked state ${MARKER}
|
||||||
|
for (const target of syndicationTargets) { ${MARKER}
|
||||||
|
target.defaultChecked = target.checked === true; ${MARKER}
|
||||||
|
} ${MARKER}`;
|
||||||
|
|
||||||
|
async function exists(filePath) {
|
||||||
|
try {
|
||||||
|
await access(filePath);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let checked = 0;
|
||||||
|
let patched = 0;
|
||||||
|
|
||||||
|
for (const filePath of candidates) {
|
||||||
|
if (!(await exists(filePath))) continue;
|
||||||
|
checked += 1;
|
||||||
|
|
||||||
|
const source = await readFile(filePath, "utf8");
|
||||||
|
|
||||||
|
if (source.includes(MARKER)) {
|
||||||
|
console.log(`[postinstall] patch-ap-compose-default-checked: already applied to ${filePath}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!source.includes(OLD_SNIPPET)) {
|
||||||
|
console.warn(`[postinstall] patch-ap-compose-default-checked: target snippet not found in ${filePath}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updated = source.replace(OLD_SNIPPET, NEW_SNIPPET);
|
||||||
|
|
||||||
|
if (updated === source) {
|
||||||
|
console.log(`[postinstall] patch-ap-compose-default-checked: no changes in ${filePath}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
await writeFile(filePath, updated, "utf8");
|
||||||
|
patched += 1;
|
||||||
|
console.log(`[postinstall] Applied patch-ap-compose-default-checked to ${filePath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (checked === 0) {
|
||||||
|
console.log("[postinstall] patch-ap-compose-default-checked: no target files found");
|
||||||
|
} else if (patched === 0) {
|
||||||
|
console.log("[postinstall] patch-ap-compose-default-checked: already up to date");
|
||||||
|
} else {
|
||||||
|
console.log(`[postinstall] patch-ap-compose-default-checked: patched ${patched} file(s)`);
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
/**
|
||||||
|
* Patch: eagerly insert own post into ap_timeline after Mastodon API POST /statuses.
|
||||||
|
*
|
||||||
|
* Root cause:
|
||||||
|
* When a post is created via POST /api/v1/statuses (Mastodon client API), the
|
||||||
|
* handler creates the post through the Micropub pipeline but intentionally does
|
||||||
|
* NOT insert a timeline item immediately. The comment says:
|
||||||
|
*
|
||||||
|
* "No timeline entry is created here — the post will appear in the timeline
|
||||||
|
* after the normal flow: Eleventy rebuild → syndication webhook → AP delivery."
|
||||||
|
*
|
||||||
|
* This means there is a window (typically 30–120 s while Eleventy rebuilds) where
|
||||||
|
* the own post does NOT exist in ap_timeline. If the user tries to reply to their
|
||||||
|
* own newly-created post during this window, POST /api/v1/statuses receives
|
||||||
|
* `in_reply_to_id` for the new post, but `findTimelineItemById` returns null.
|
||||||
|
* With inReplyTo = null, the JF2 object has no "in-reply-to" property, and
|
||||||
|
* post-type-discovery classifies the reply as "note" instead of "reply". The
|
||||||
|
* reply is then saved at /notes/{slug}/ rather than /replies/{slug}/, and
|
||||||
|
* since there is no in-reply-to, the ActivityPub activity has no inReplyTo
|
||||||
|
* field and the thread is broken on remote Mastodon servers.
|
||||||
|
*
|
||||||
|
* Fix:
|
||||||
|
* After calling postContent.create(), immediately insert a provisional timeline
|
||||||
|
* item into ap_timeline using addTimelineItem() (which uses $setOnInsert —
|
||||||
|
* idempotent). The AP syndicator will later attempt the same upsert after the
|
||||||
|
* build webhook fires, which is a no-op since the document already exists.
|
||||||
|
* This ensures the post is resolvable via in_reply_to_id with zero delay.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { access, readFile, writeFile } from "node:fs/promises";
|
||||||
|
|
||||||
|
const MARKER = "// [patch] ap-mastodon-reply-threading";
|
||||||
|
|
||||||
|
const candidates = [
|
||||||
|
"node_modules/@rmdes/indiekit-endpoint-activitypub/lib/mastodon/routes/statuses.js",
|
||||||
|
"node_modules/@indiekit/indiekit/node_modules/@rmdes/indiekit-endpoint-activitypub/lib/mastodon/routes/statuses.js",
|
||||||
|
];
|
||||||
|
|
||||||
|
const OLD_SNIPPET = ` // Return a minimal status to the Mastodon client.
|
||||||
|
// No timeline entry is created here — the post will appear in the timeline
|
||||||
|
// after the normal flow: Eleventy rebuild → syndication webhook → AP delivery.
|
||||||
|
const profile = await collections.ap_profile.findOne({});
|
||||||
|
const handle = pluginOptions.handle || "user";`;
|
||||||
|
|
||||||
|
const NEW_SNIPPET = ` // Return a minimal status to the Mastodon client. ${MARKER}
|
||||||
|
// Eagerly insert own post into ap_timeline so the Mastodon client can resolve ${MARKER}
|
||||||
|
// in_reply_to_id for this post immediately, without waiting for the build webhook. ${MARKER}
|
||||||
|
// The AP syndicator will upsert the same uid later via $setOnInsert (no-op). ${MARKER}
|
||||||
|
const profile = await collections.ap_profile.findOne({});
|
||||||
|
const handle = pluginOptions.handle || "user";
|
||||||
|
try { ${MARKER}
|
||||||
|
const _ph = (() => { try { return new URL(publicationUrl).hostname; } catch { return ""; } })(); ${MARKER}
|
||||||
|
await addTimelineItem(collections, { ${MARKER}
|
||||||
|
uid: postUrl, ${MARKER}
|
||||||
|
url: postUrl, ${MARKER}
|
||||||
|
type: data.properties["post-type"] || "note", ${MARKER}
|
||||||
|
content: { text: contentText, html: \`<p>\${contentHtml}</p>\` }, ${MARKER}
|
||||||
|
author: { ${MARKER}
|
||||||
|
name: profile?.name || handle, ${MARKER}
|
||||||
|
url: profile?.url || publicationUrl, ${MARKER}
|
||||||
|
photo: profile?.icon || "", ${MARKER}
|
||||||
|
handle: \`@\${handle}@\${_ph}\`, ${MARKER}
|
||||||
|
emojis: [], ${MARKER}
|
||||||
|
bot: false, ${MARKER}
|
||||||
|
}, ${MARKER}
|
||||||
|
published: data.properties.published || new Date().toISOString(), ${MARKER}
|
||||||
|
createdAt: new Date().toISOString(), ${MARKER}
|
||||||
|
inReplyTo: inReplyTo || null, ${MARKER}
|
||||||
|
visibility: jf2.visibility || "public", ${MARKER}
|
||||||
|
sensitive: jf2.sensitive === "true", ${MARKER}
|
||||||
|
category: [], ${MARKER}
|
||||||
|
counts: { likes: 0, boosts: 0, replies: 0 }, ${MARKER}
|
||||||
|
}); ${MARKER}
|
||||||
|
} catch (tlErr) { ${MARKER}
|
||||||
|
console.warn(\`[Mastodon API] Failed to pre-insert own post into timeline: \${tlErr.message}\`); ${MARKER}
|
||||||
|
} ${MARKER}`;
|
||||||
|
|
||||||
|
async function exists(filePath) {
|
||||||
|
try {
|
||||||
|
await access(filePath);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let checked = 0;
|
||||||
|
let patched = 0;
|
||||||
|
|
||||||
|
for (const filePath of candidates) {
|
||||||
|
if (!(await exists(filePath))) continue;
|
||||||
|
checked += 1;
|
||||||
|
|
||||||
|
const source = await readFile(filePath, "utf8");
|
||||||
|
|
||||||
|
if (source.includes(MARKER)) {
|
||||||
|
console.log(`[postinstall] patch-ap-mastodon-reply-threading: already applied to ${filePath}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!source.includes(OLD_SNIPPET)) {
|
||||||
|
console.warn(`[postinstall] patch-ap-mastodon-reply-threading: target snippet not found in ${filePath}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updated = source.replace(OLD_SNIPPET, NEW_SNIPPET);
|
||||||
|
|
||||||
|
if (updated === source) {
|
||||||
|
console.log(`[postinstall] patch-ap-mastodon-reply-threading: no changes in ${filePath}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
await writeFile(filePath, updated, "utf8");
|
||||||
|
patched += 1;
|
||||||
|
console.log(`[postinstall] Applied patch-ap-mastodon-reply-threading to ${filePath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (checked === 0) {
|
||||||
|
console.log("[postinstall] patch-ap-mastodon-reply-threading: no target files found");
|
||||||
|
} else if (patched === 0) {
|
||||||
|
console.log("[postinstall] patch-ap-mastodon-reply-threading: already up to date");
|
||||||
|
} else {
|
||||||
|
console.log(`[postinstall] patch-ap-mastodon-reply-threading: patched ${patched} file(s)`);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user