fix: add AP inbox digest patch and AP URL lookup API for fediverse interaction
- patch-ap-inbox-raw-body-digest: preserve raw request bytes through the AP inbox buffer guard so Fedify's HTTP Signature Digest verification passes (JSON.stringify re-encoding broke SHA-256 digest check, causing Mastodon likes/replies/boosts to be silently rejected) - patch-ap-url-lookup-api: add GET /activitypub/api/ap-url endpoint that maps a blog post URL to its Fedify-served AP object URL, enabling reliable content negotiation for authorize_interaction redirects - wire both patches into postinstall and serve scripts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,8 +4,8 @@
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"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-posts-ai-fields.mjs && node scripts/patch-endpoint-posts-ai-cleanup.mjs && node scripts/patch-endpoint-podroll-opml-upload.mjs && node scripts/patch-preset-eleventy-ai-frontmatter.mjs && node scripts/patch-micropub-ai-block-resync.mjs && node scripts/patch-frontend-serviceworker-file.mjs && node scripts/patch-endpoint-comments-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-microsub-reader-ap-dispatch.mjs && node scripts/patch-endpoint-blogroll-feeds-alias.mjs && node scripts/patch-endpoint-posts-uid-lookup.mjs && node scripts/patch-endpoint-posts-prefill-url.mjs && node scripts/patch-microsub-feed-discovery.mjs && node scripts/patch-conversations-bluesky-self-filter.mjs && node scripts/patch-conversations-bluesky-cursor-fix.mjs && node scripts/patch-inbox-ignore-view-activity.mjs && node scripts/patch-inbox-skip-view-activity-parse.mjs && node scripts/patch-webmention-sender-content-scope.mjs && node scripts/patch-endpoint-micropub-source-filter.mjs && node scripts/patch-endpoint-posts-search-tags.mjs && node scripts/patch-syndicate-force-checked-default.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-posts-ai-fields.mjs && node scripts/patch-endpoint-posts-ai-cleanup.mjs && node scripts/patch-endpoint-podroll-opml-upload.mjs && node scripts/patch-preset-eleventy-ai-frontmatter.mjs && node scripts/patch-micropub-ai-block-resync.mjs && node scripts/patch-frontend-serviceworker-file.mjs && node scripts/patch-endpoint-comments-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-microsub-reader-ap-dispatch.mjs && node scripts/patch-endpoint-blogroll-feeds-alias.mjs && node scripts/patch-endpoint-posts-uid-lookup.mjs && node scripts/patch-endpoint-posts-prefill-url.mjs && node scripts/patch-microsub-feed-discovery.mjs && node scripts/patch-conversations-bluesky-self-filter.mjs && node scripts/patch-conversations-bluesky-cursor-fix.mjs && node scripts/patch-webmention-sender-content-scope.mjs && node scripts/patch-endpoint-micropub-source-filter.mjs && node scripts/patch-endpoint-posts-search-tags.mjs && node scripts/patch-syndicate-force-checked-default.mjs && node 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-posts-ai-fields.mjs && node scripts/patch-endpoint-posts-ai-cleanup.mjs && node scripts/patch-endpoint-podroll-opml-upload.mjs && node scripts/patch-preset-eleventy-ai-frontmatter.mjs && node scripts/patch-micropub-ai-block-resync.mjs && node scripts/patch-frontend-serviceworker-file.mjs && node scripts/patch-endpoint-comments-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-microsub-reader-ap-dispatch.mjs && node scripts/patch-endpoint-blogroll-feeds-alias.mjs && node scripts/patch-endpoint-posts-uid-lookup.mjs && node scripts/patch-endpoint-posts-prefill-url.mjs && node scripts/patch-microsub-feed-discovery.mjs && node scripts/patch-conversations-bluesky-self-filter.mjs && node scripts/patch-conversations-bluesky-cursor-fix.mjs && node scripts/patch-inbox-ignore-view-activity.mjs && node scripts/patch-inbox-skip-view-activity-parse.mjs && node scripts/patch-webmention-sender-content-scope.mjs && node scripts/patch-endpoint-micropub-source-filter.mjs && node scripts/patch-endpoint-posts-search-tags.mjs && node scripts/patch-syndicate-force-checked-default.mjs && node scripts/patch-ap-url-lookup-api.mjs && node scripts/patch-ap-inbox-raw-body-digest.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-posts-ai-fields.mjs && node scripts/patch-endpoint-posts-ai-cleanup.mjs && node scripts/patch-endpoint-podroll-opml-upload.mjs && node scripts/patch-preset-eleventy-ai-frontmatter.mjs && node scripts/patch-micropub-ai-block-resync.mjs && node scripts/patch-frontend-serviceworker-file.mjs && node scripts/patch-endpoint-comments-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-microsub-reader-ap-dispatch.mjs && node scripts/patch-endpoint-blogroll-feeds-alias.mjs && node scripts/patch-endpoint-posts-uid-lookup.mjs && node scripts/patch-endpoint-posts-prefill-url.mjs && node scripts/patch-microsub-feed-discovery.mjs && node scripts/patch-conversations-bluesky-self-filter.mjs && node scripts/patch-conversations-bluesky-cursor-fix.mjs && node scripts/patch-webmention-sender-content-scope.mjs && node scripts/patch-endpoint-micropub-source-filter.mjs && node scripts/patch-endpoint-posts-search-tags.mjs && node scripts/patch-syndicate-force-checked-default.mjs && node scripts/patch-ap-url-lookup-api.mjs && node scripts/patch-ap-inbox-raw-body-digest.mjs && node node_modules/@indiekit/indiekit/bin/cli.js serve --config indiekit.config.mjs",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
|
||||
123
scripts/patch-ap-inbox-raw-body-digest.mjs
Normal file
123
scripts/patch-ap-inbox-raw-body-digest.mjs
Normal file
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* Patch: preserve raw body bytes through the AP inbox buffer guard so that
|
||||
* Fedify's HTTP Signature Digest verification passes.
|
||||
*
|
||||
* Root cause:
|
||||
* patch-inbox-skip-view-activity-parse.mjs buffers the request body for
|
||||
* application/activity+json requests (needed to detect PeerTube View
|
||||
* activities before Fedify parses them). It stores the parsed JSON in
|
||||
* req.body. fromExpressRequest() then reconstructs the body for Fedify via
|
||||
* JSON.stringify(req.body).
|
||||
*
|
||||
* Fedify 2.x verifies the HTTP Signature "Digest: SHA-256=..." header that
|
||||
* Mastodon (and most other AP servers) include with every inbox POST.
|
||||
* The digest is computed over the EXACT original request bytes. Re-encoding
|
||||
* the body via JSON.stringify() produces different bytes (different key
|
||||
* ordering, whitespace, Unicode escaping), so the digest check fails and
|
||||
* Fedify silently rejects every inbound Like, Announce, and Create activity
|
||||
* from Mastodon. The activity never reaches the inbox handlers and is never
|
||||
* stored in ap_activities — so conversations/AP shows zero interactions.
|
||||
*
|
||||
* Fix (two changes to federation-bridge.js):
|
||||
*
|
||||
* 1. In createFedifyMiddleware buffer guard: after the for-await loop, store
|
||||
* the original Buffer in req._rawBody before JSON-parsing it into req.body.
|
||||
*
|
||||
* 2. In fromExpressRequest: when req._rawBody is available, pass it directly
|
||||
* to new Request() instead of JSON.stringify(req.body). This gives Fedify
|
||||
* the original bytes so its SHA-256 digest check matches the Digest header.
|
||||
*/
|
||||
|
||||
import { access, readFile, writeFile } from "node:fs/promises";
|
||||
|
||||
const candidates = [
|
||||
"node_modules/@rmdes/indiekit-endpoint-activitypub/lib/federation-bridge.js",
|
||||
"node_modules/@indiekit/indiekit/node_modules/@rmdes/indiekit-endpoint-activitypub/lib/federation-bridge.js",
|
||||
];
|
||||
|
||||
const MARKER = "// raw body digest fix";
|
||||
|
||||
const patchSpecs = [
|
||||
// Patch A: store raw bytes in req._rawBody alongside req.body
|
||||
{
|
||||
name: "raw-body-store",
|
||||
oldSnippet: ` try {
|
||||
req.body = JSON.parse(Buffer.concat(_chunks).toString("utf8"));
|
||||
} catch {
|
||||
req.body = {};
|
||||
}`,
|
||||
newSnippet: ` const _raw = Buffer.concat(_chunks); // raw body digest fix
|
||||
req._rawBody = _raw; // Preserve original bytes for Fedify HTTP Signature Digest verification
|
||||
try {
|
||||
req.body = JSON.parse(_raw.toString("utf8"));
|
||||
} catch {
|
||||
req.body = {};
|
||||
}`,
|
||||
},
|
||||
|
||||
// Patch B: use req._rawBody in fromExpressRequest when available
|
||||
{
|
||||
name: "from-express-request-use-raw-body",
|
||||
oldSnippet: ` // PeerTube activity+json body fix
|
||||
if (ct.includes("application/json") || ct.includes("activity+json") || ct.includes("ld+json")) {
|
||||
body = JSON.stringify(req.body);
|
||||
}`,
|
||||
newSnippet: ` // PeerTube activity+json body fix
|
||||
if (ct.includes("application/json") || ct.includes("activity+json") || ct.includes("ld+json")) {
|
||||
// Use original raw bytes when available (set by createFedifyMiddleware buffer guard).
|
||||
// JSON.stringify() changes byte layout, breaking Fedify's HTTP Signature Digest check.
|
||||
body = req._rawBody || JSON.stringify(req.body); // raw body digest fix
|
||||
}`,
|
||||
},
|
||||
];
|
||||
|
||||
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;
|
||||
let source = await readFile(filePath, "utf8");
|
||||
|
||||
if (source.includes(MARKER)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let filePatched = false;
|
||||
|
||||
for (const spec of patchSpecs) {
|
||||
if (!source.includes(spec.oldSnippet)) {
|
||||
console.log(`[postinstall] patch-ap-inbox-raw-body-digest: ${spec.name} snippet not found in ${filePath}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
source = source.replace(spec.oldSnippet, spec.newSnippet);
|
||||
filePatched = true;
|
||||
console.log(`[postinstall] Applied ${spec.name} to ${filePath}`);
|
||||
}
|
||||
|
||||
if (filePatched) {
|
||||
await writeFile(filePath, source, "utf8");
|
||||
patched += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (checked === 0) {
|
||||
console.log("[postinstall] patch-ap-inbox-raw-body-digest: no target files found");
|
||||
} else if (patched === 0) {
|
||||
console.log("[postinstall] patch-ap-inbox-raw-body-digest: already up to date");
|
||||
} else {
|
||||
console.log(`[postinstall] patch-ap-inbox-raw-body-digest: patched ${patched}/${checked} file(s)`);
|
||||
}
|
||||
188
scripts/patch-ap-url-lookup-api.mjs
Normal file
188
scripts/patch-ap-url-lookup-api.mjs
Normal file
@@ -0,0 +1,188 @@
|
||||
/**
|
||||
* Patch: add a public GET /api/ap-url endpoint to the ActivityPub endpoint.
|
||||
*
|
||||
* Problem:
|
||||
* The "Also on fediverse" widget on blog post pages passes the blog post URL
|
||||
* (e.g. https://blog.giersig.eu/replies/bd78a/) to the Mastodon
|
||||
* authorize_interaction flow:
|
||||
* https://{instance}/authorize_interaction?uri={blog-post-url}
|
||||
*
|
||||
* When the remote instance fetches that URI with Accept: application/activity+json,
|
||||
* it may hit a static file server (nginx/Caddy) that returns HTML instead of
|
||||
* AP JSON, causing the interaction to fail with "Could not connect to the given
|
||||
* address" or a similar error.
|
||||
*
|
||||
* Fix:
|
||||
* Add a public API route to the AP endpoint:
|
||||
* GET /activitypub/api/ap-url?post={blog-post-url}
|
||||
*
|
||||
* This resolves the post in MongoDB, determines its object type (Note or Article),
|
||||
* and returns the canonical Fedify-served AP object URL:
|
||||
* { apUrl: "https://blog.giersig.eu/activitypub/objects/note/replies/bd78a/" }
|
||||
*
|
||||
* The "Also on fediverse" JS widget can then call this API and use the returned
|
||||
* apUrl in the authorize_interaction redirect instead of the blog post URL.
|
||||
* Fedify-served URLs (/activitypub/objects/…) are always proxied to Node.js and
|
||||
* will reliably return AP JSON with correct content negotiation.
|
||||
*
|
||||
* The patch inserts the new route in the `routesPublic` getter of index.js,
|
||||
* just before the closing `return router` statement.
|
||||
*/
|
||||
|
||||
import { access, readFile, writeFile } from "node:fs/promises";
|
||||
|
||||
const candidates = [
|
||||
"node_modules/@rmdes/indiekit-endpoint-activitypub/index.js",
|
||||
"node_modules/@indiekit/indiekit/node_modules/@rmdes/indiekit-endpoint-activitypub/index.js",
|
||||
];
|
||||
|
||||
const MARKER = "// AP URL lookup endpoint";
|
||||
|
||||
const OLD_SNIPPET = ` router.all("/inbox", (req, res) => {
|
||||
res
|
||||
.status(405)
|
||||
.set("Allow", "POST")
|
||||
.type("application/activity+json")
|
||||
.json({
|
||||
error: "Method Not Allowed",
|
||||
message: "The shared inbox only accepts POST requests",
|
||||
});
|
||||
});
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticated admin routes — mounted at mountPath, behind IndieAuth.
|
||||
*/`;
|
||||
|
||||
const NEW_SNIPPET = ` router.all("/inbox", (req, res) => {
|
||||
res
|
||||
.status(405)
|
||||
.set("Allow", "POST")
|
||||
.type("application/activity+json")
|
||||
.json({
|
||||
error: "Method Not Allowed",
|
||||
message: "The shared inbox only accepts POST requests",
|
||||
});
|
||||
});
|
||||
|
||||
// AP URL lookup endpoint
|
||||
// Public API: resolve a blog post URL → its Fedify-served AP object URL.
|
||||
// GET /api/ap-url?post=https://blog.example.com/notes/foo/
|
||||
// Returns { apUrl: "https://blog.example.com/activitypub/objects/note/notes/foo/" }
|
||||
//
|
||||
// Use this in "Also on fediverse" widgets so that authorize_interaction
|
||||
// uses a URL that is always routed to Node.js (never intercepted by a
|
||||
// static file server), ensuring reliable AP content negotiation.
|
||||
router.get("/api/ap-url", async (req, res) => {
|
||||
try {
|
||||
const postParam = req.query.post;
|
||||
if (!postParam) {
|
||||
return res.status(400).json({ error: "post parameter required" });
|
||||
}
|
||||
|
||||
const { application } = req.app.locals;
|
||||
const postsCollection = application.collections?.get("posts");
|
||||
|
||||
if (!postsCollection) {
|
||||
return res.status(503).json({ error: "Database unavailable" });
|
||||
}
|
||||
|
||||
const publicationUrl = (self._publicationUrl || application.url || "").replace(/\\/$/, "");
|
||||
|
||||
// Match with or without trailing slash
|
||||
const postUrl = postParam.replace(/\\/$/, "");
|
||||
const post = await postsCollection.findOne({
|
||||
"properties.url": { $in: [postUrl, postUrl + "/"] },
|
||||
});
|
||||
|
||||
if (!post) {
|
||||
return res.status(404).json({ error: "Post not found" });
|
||||
}
|
||||
|
||||
// Draft and unlisted posts are not federated
|
||||
if (post?.properties?.["post-status"] === "draft") {
|
||||
return res.status(404).json({ error: "Post not found" });
|
||||
}
|
||||
if (post?.properties?.visibility === "unlisted") {
|
||||
return res.status(404).json({ error: "Post not found" });
|
||||
}
|
||||
|
||||
// Determine the AP object type (mirrors jf2-to-as2.js logic)
|
||||
const postType = post.properties?.["post-type"];
|
||||
const isArticle = postType === "article" && !!post.properties?.name;
|
||||
const objectType = isArticle ? "article" : "note";
|
||||
|
||||
// Extract the path portion after the publication base URL
|
||||
const resolvedUrl = (post.properties?.url || "").replace(/\\/$/, "");
|
||||
if (!resolvedUrl.startsWith(publicationUrl)) {
|
||||
return res.status(500).json({ error: "Post URL does not match publication base" });
|
||||
}
|
||||
const postPath = resolvedUrl.slice(publicationUrl.length).replace(/^\\//, "");
|
||||
|
||||
const mp = (self.options.mountPath || "").replace(/\\/$/, "");
|
||||
const apBase = publicationUrl;
|
||||
const apUrl = \`\${apBase}\${mp}/objects/\${objectType}/\${postPath}\`;
|
||||
|
||||
res.set("Cache-Control", "public, max-age=300");
|
||||
res.json({ apUrl });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticated admin routes — mounted at mountPath, behind IndieAuth.
|
||||
*/`;
|
||||
|
||||
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)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!source.includes(OLD_SNIPPET)) {
|
||||
console.log(`[postinstall] patch-ap-url-lookup-api: old snippet not found in ${filePath}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const updated = source.replace(OLD_SNIPPET, NEW_SNIPPET);
|
||||
|
||||
if (updated === source) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await writeFile(filePath, updated, "utf8");
|
||||
patched += 1;
|
||||
console.log(`[postinstall] Applied patch-ap-url-lookup-api to ${filePath}`);
|
||||
}
|
||||
|
||||
if (checked === 0) {
|
||||
console.log("[postinstall] patch-ap-url-lookup-api: no target files found");
|
||||
} else if (patched === 0) {
|
||||
console.log("[postinstall] patch-ap-url-lookup-api: already up to date");
|
||||
} else {
|
||||
console.log(`[postinstall] patch-ap-url-lookup-api: patched ${patched}/${checked} file(s)`);
|
||||
}
|
||||
Reference in New Issue
Block a user