diff --git a/indiekit.config.mjs b/indiekit.config.mjs index 7db39854..5256ff2d 100644 --- a/indiekit.config.mjs +++ b/indiekit.config.mjs @@ -207,10 +207,6 @@ export default { }, fields: { gardenStage: {}, - aiTextLevel: {}, - aiCodeLevel: {}, - aiTools: {}, - aiDescription: {}, }, }, note: { @@ -221,10 +217,6 @@ export default { }, fields: { gardenStage: {}, - aiTextLevel: {}, - aiCodeLevel: {}, - aiTools: {}, - aiDescription: {}, }, }, bookmark: { @@ -235,10 +227,6 @@ export default { }, fields: { gardenStage: {}, - aiTextLevel: {}, - aiCodeLevel: {}, - aiTools: {}, - aiDescription: {}, }, }, like: { @@ -259,10 +247,6 @@ export default { }, fields: { gardenStage: {}, - aiTextLevel: {}, - aiCodeLevel: {}, - aiTools: {}, - aiDescription: {}, }, }, photo: { @@ -277,10 +261,6 @@ export default { }, fields: { gardenStage: {}, - aiTextLevel: {}, - aiCodeLevel: {}, - aiTools: {}, - aiDescription: {}, }, }, reply: { @@ -291,10 +271,6 @@ export default { }, fields: { gardenStage: {}, - aiTextLevel: {}, - aiCodeLevel: {}, - aiTools: {}, - aiDescription: {}, }, }, page: { @@ -305,10 +281,6 @@ export default { }, fields: { gardenStage: {}, - aiTextLevel: {}, - aiCodeLevel: {}, - aiTools: {}, - aiDescription: {}, }, }, }, diff --git a/package-lock.json b/package-lock.json index dc95f8b6..013fafd7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "@rmdes/indiekit-endpoint-lastfm": "^1.0.12", "@rmdes/indiekit-endpoint-microsub": "github:svemagie/indiekit-endpoint-microsub#bookmarks-import", "@rmdes/indiekit-endpoint-podroll": "^1.0.11", - "@rmdes/indiekit-endpoint-posts": "^1.0.0-beta.25", + "@rmdes/indiekit-endpoint-posts": "^1.0.0-beta.44", "@rmdes/indiekit-endpoint-readlater": "github:rmdes/indiekit-endpoint-readlater", "@rmdes/indiekit-endpoint-rss": "^1.0.14", "@rmdes/indiekit-endpoint-webmention-io": "^1.0.7", @@ -1515,14 +1515,14 @@ }, "node_modules/@indiekit/endpoint-posts": { "name": "@rmdes/indiekit-endpoint-posts", - "version": "1.0.0-beta.41", - "resolved": "https://registry.npmjs.org/@rmdes/indiekit-endpoint-posts/-/indiekit-endpoint-posts-1.0.0-beta.41.tgz", - "integrity": "sha512-lJgDVGHvHT0bYLstBmHWMdLWLZzhC0bclspLM/NFVvBqu104DEcRwHm4k5H6n/+JUHQCmopsaWMHIe0Umgzv2w==", + "version": "1.0.0-beta.44", + "resolved": "https://registry.npmjs.org/@rmdes/indiekit-endpoint-posts/-/indiekit-endpoint-posts-1.0.0-beta.44.tgz", + "integrity": "sha512-xGvsmqIOiQU0tpocDmNJw0CRvdj98zOVzpX+qzpPHBcYXSUZmbhRHd9/OaOD4dOWsmNT4dHYhmJ0OjTdUMTtUw==", "license": "MIT", "dependencies": { - "@indiekit/endpoint-micropub": "^1.0.0-beta.25", - "@indiekit/error": "^1.0.0-beta.25", - "@indiekit/frontend": "^1.0.0-beta.25", + "@indiekit/endpoint-micropub": "^1.0.0-beta.27", + "@indiekit/error": "^1.0.0-beta.27", + "@indiekit/frontend": "^1.0.0-beta.27", "@indiekit/util": "^1.0.0-beta.25", "@paulrobertlloyd/mf2tojf2": "^3.0.0", "express": "^5.0.0", @@ -2563,14 +2563,14 @@ } }, "node_modules/@rmdes/indiekit-endpoint-posts": { - "version": "1.0.0-beta.25", - "resolved": "https://registry.npmjs.org/@rmdes/indiekit-endpoint-posts/-/indiekit-endpoint-posts-1.0.0-beta.25.tgz", - "integrity": "sha512-Rh519QYIOQK3pipUESfp1lv2mDZSwVX0W32KPsMwZrs77LFCu4KCI+YjedgSr7dwZZO0qH0j24QhJ7SOkn+DaA==", + "version": "1.0.0-beta.44", + "resolved": "https://registry.npmjs.org/@rmdes/indiekit-endpoint-posts/-/indiekit-endpoint-posts-1.0.0-beta.44.tgz", + "integrity": "sha512-xGvsmqIOiQU0tpocDmNJw0CRvdj98zOVzpX+qzpPHBcYXSUZmbhRHd9/OaOD4dOWsmNT4dHYhmJ0OjTdUMTtUw==", "license": "MIT", "dependencies": { - "@indiekit/endpoint-micropub": "^1.0.0-beta.25", - "@indiekit/error": "^1.0.0-beta.25", - "@indiekit/frontend": "^1.0.0-beta.25", + "@indiekit/endpoint-micropub": "^1.0.0-beta.27", + "@indiekit/error": "^1.0.0-beta.27", + "@indiekit/frontend": "^1.0.0-beta.27", "@indiekit/util": "^1.0.0-beta.25", "@paulrobertlloyd/mf2tojf2": "^3.0.0", "express": "^5.0.0", diff --git a/package.json b/package.json index 135e5273..9c4c09e8 100644 --- a/package.json +++ b/package.json @@ -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-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-blogroll-feeds-alias.mjs && node scripts/patch-endpoint-posts-uid-lookup.mjs && node scripts/patch-endpoint-posts-prefill-url.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-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-allow-private-address.mjs && node scripts/patch-ap-repost-commentary.mjs && node scripts/patch-endpoint-posts-fetch-diagnostic.mjs && node scripts/patch-micropub-fetch-internal-url.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", - "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-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-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-conversations-bluesky-self-filter.mjs && node scripts/patch-conversations-bluesky-cursor-fix.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-allow-private-address.mjs && node scripts/patch-ap-repost-commentary.mjs && node scripts/patch-endpoint-posts-fetch-diagnostic.mjs && node scripts/patch-micropub-fetch-internal-url.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 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-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-ap-url-lookup-api.mjs && node scripts/patch-ap-allow-private-address.mjs && node scripts/patch-ap-repost-commentary.mjs && node scripts/patch-endpoint-posts-fetch-diagnostic.mjs && node scripts/patch-micropub-fetch-internal-url.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", + "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-microsub-reader-ap-dispatch.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-ap-url-lookup-api.mjs && node scripts/patch-ap-allow-private-address.mjs && node scripts/patch-ap-repost-commentary.mjs && node scripts/patch-endpoint-posts-fetch-diagnostic.mjs && node scripts/patch-micropub-fetch-internal-url.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 node_modules/@indiekit/indiekit/bin/cli.js serve --config indiekit.config.mjs", "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], @@ -16,7 +16,7 @@ "@indiekit/endpoint-auth": "npm:@rmdes/indiekit-endpoint-auth@^1.0.0-beta.27", "@indiekit/endpoint-files": "npm:@rmdes/indiekit-endpoint-files@^1.0.0", "@indiekit/endpoint-micropub": "npm:@rmdes/indiekit-endpoint-micropub@^1.0.0-beta.26", - "@indiekit/endpoint-posts": "npm:@rmdes/indiekit-endpoint-posts@^1.0.0-beta.41", + "@indiekit/endpoint-posts": "npm:@rmdes/indiekit-endpoint-posts@^1.0.0-beta.44", "@indiekit/endpoint-share": "npm:@rmdes/indiekit-endpoint-share@^1.0.0", "@indiekit/endpoint-syndicate": "npm:@rmdes/indiekit-endpoint-syndicate@^1.0.0-beta.32", "@indiekit/frontend": "npm:@rmdes/indiekit-frontend@^1.0.0-beta.37" @@ -38,7 +38,7 @@ "@rmdes/indiekit-endpoint-lastfm": "^1.0.12", "@rmdes/indiekit-endpoint-microsub": "github:svemagie/indiekit-endpoint-microsub#bookmarks-import", "@rmdes/indiekit-endpoint-podroll": "^1.0.11", - "@rmdes/indiekit-endpoint-posts": "^1.0.0-beta.25", + "@rmdes/indiekit-endpoint-posts": "^1.0.0-beta.44", "@rmdes/indiekit-endpoint-readlater": "github:rmdes/indiekit-endpoint-readlater", "@rmdes/indiekit-endpoint-rss": "^1.0.14", "@rmdes/indiekit-endpoint-webmention-io": "^1.0.7", diff --git a/scripts/patch-endpoint-posts-ai-cleanup.mjs b/scripts/patch-endpoint-posts-ai-cleanup.mjs deleted file mode 100644 index 3114f6c8..00000000 --- a/scripts/patch-endpoint-posts-ai-cleanup.mjs +++ /dev/null @@ -1,120 +0,0 @@ -import { access, readFile, writeFile } from "node:fs/promises"; - -const candidates = [ - "node_modules/@rmdes/indiekit-endpoint-posts/lib/controllers/form.js", - "node_modules/@indiekit/endpoint-posts/lib/controllers/form.js", - "node_modules/@indiekit/indiekit/node_modules/@rmdes/indiekit-endpoint-posts/lib/controllers/form.js", - "node_modules/@indiekit/indiekit/node_modules/@indiekit/endpoint-posts/lib/controllers/form.js", -]; - -const marker = "Always remove legacy hyphenated keys — superseded by camelCase equivalents."; - -const oldSnippet = [ - " // Easy MDE appends `image` value to formData for last image uploaded", - " delete values.image;", - "", - " // Remove empty AI metadata fields so Micropub payload stays lean.", - " for (const key of [", - " \"aiTextLevel\",", - " \"aiCodeLevel\",", - " \"aiTools\",", - " \"aiDescription\",", - " \"ai-text-level\",", - " \"ai-code-level\",", - " \"ai-tools\",", - " \"ai-description\",", - " ]) {", - " if (", - " values[key] === undefined ||", - " values[key] === null ||", - " String(values[key]).trim() === \"\"", - " ) {", - " delete values[key];", - " }", - " }", - "", - " const mf2 = jf2ToMf2({ properties: sanitise(values) });", -].join("\n"); - -const newSnippet = [ - " // Easy MDE appends `image` value to formData for last image uploaded", - " delete values.image;", - "", - " // Remove empty AI metadata fields so Micropub payload stays lean.", - " for (const key of [", - " \"aiTextLevel\",", - " \"aiCodeLevel\",", - " \"aiTools\",", - " \"aiDescription\",", - " ]) {", - " if (", - " values[key] === undefined ||", - " values[key] === null ||", - " String(values[key]).trim() === \"\"", - " ) {", - " delete values[key];", - " }", - " }", - " // Always remove legacy hyphenated keys — superseded by camelCase equivalents.", - " delete values[\"ai-text-level\"];", - " delete values[\"ai-code-level\"];", - " delete values[\"ai-tools\"];", - " delete values[\"ai-description\"];", - "", - " const mf2 = jf2ToMf2({ properties: sanitise(values) });", -].join("\n"); - -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(oldSnippet)) { - // Already has AI field cleanup in some form — skip silently - if ( - source.includes('"ai-text-level"') || - source.includes('"aiTextLevel"') || - !source.includes("jf2ToMf2") - ) { - continue; - } - console.warn( - `[postinstall] Skipping endpoint-posts AI cleanup patch for ${filePath}: upstream format changed`, - ); - continue; - } - - const updated = source.replace(oldSnippet, newSnippet); - await writeFile(filePath, updated, "utf8"); - patched += 1; -} - -if (checked === 0) { - console.log("[postinstall] No endpoint-posts form controller files found"); -} else if (patched === 0) { - console.log("[postinstall] endpoint-posts AI cleanup patch already applied"); -} else { - console.log( - `[postinstall] Patched endpoint-posts AI cleanup in ${patched} file(s)`, - ); -} diff --git a/scripts/patch-endpoint-posts-ai-fields.mjs b/scripts/patch-endpoint-posts-ai-fields.mjs deleted file mode 100644 index 79bb3d7f..00000000 --- a/scripts/patch-endpoint-posts-ai-fields.mjs +++ /dev/null @@ -1,135 +0,0 @@ -import { access, mkdir, readFile, writeFile } from "node:fs/promises"; -import path from "node:path"; - -const endpointCandidates = [ - "node_modules/@rmdes/indiekit-endpoint-posts", - "node_modules/@indiekit/endpoint-posts", - "node_modules/@indiekit/indiekit/node_modules/@rmdes/indiekit-endpoint-posts", - "node_modules/@indiekit/indiekit/node_modules/@indiekit/endpoint-posts", -]; - -const templates = { - "aiTextLevel-field.njk": [ - '{% set aiTextLevelValue = fieldData("aiTextLevel").value or fieldData("ai-text-level").value or (properties.ai.textLevel if properties.ai and properties.ai.textLevel is defined else properties.aiTextLevel) or properties["ai-text-level"] or "0" %}', - "{{ radios({", - ' name: "aiTextLevel",', - " values: aiTextLevelValue,", - " fieldset: {", - ' legend: "AI text level",', - " optional: true", - " },", - " items: [{", - ' label: "0 - None",', - ' value: "0"', - " }, {", - ' label: "1 - Editorial assistance",', - ' value: "1"', - " }, {", - ' label: "2 - Co-drafting",', - ' value: "2"', - " }, {", - ' label: "3 - AI-generated (human reviewed)",', - ' value: "3"', - " }]", - "}) }}", - ].join("\n"), - "aiCodeLevel-field.njk": [ - '{% set aiCodeLevelValue = fieldData("aiCodeLevel").value or fieldData("ai-code-level").value or (properties.ai.codeLevel if properties.ai and properties.ai.codeLevel is defined else properties.aiCodeLevel) or properties["ai-code-level"] or "0" %}', - "{{ radios({", - ' name: "aiCodeLevel",', - " values: aiCodeLevelValue,", - " fieldset: {", - ' legend: "AI code level",', - " optional: true", - " },", - " items: [{", - ' label: "0 - Human-written",', - ' value: "0"', - " }, {", - ' label: "1 - AI-assisted",', - ' value: "1"', - " }, {", - ' label: "2 - Primarily AI-generated",', - ' value: "2"', - " }]", - "}) }}", - ].join("\n"), - "aiTools-field.njk": [ - '{% set aiToolsValue = fieldData("aiTools").value or fieldData("ai-tools").value or (properties.ai.aiTools if properties.ai and properties.ai.aiTools is defined else properties.aiTools) or properties["ai-tools"] %}', - "{{ input({", - ' name: "aiTools",', - " value: aiToolsValue,", - ' label: "AI Tools",', - ' hint: "Optional, comma-separated (e.g. Claude, ChatGPT, Copilot)",', - " optional: true", - "}) }}", - ].join("\n"), - "aiDescription-field.njk": [ - '{% set aiDescriptionValue = fieldData("aiDescription").value or fieldData("ai-description").value or (properties.ai.aiDescription if properties.ai and properties.ai.aiDescription is defined else properties.aiDescription) or properties["ai-description"] %}', - "{{ textarea({", - ' name: "aiDescription",', - " value: aiDescriptionValue,", - ' label: "AI usage note",', - ' hint: "Optional: short note describing how AI was used",', - " optional: true", - "}) }}", - ].join("\n"), -}; - -async function exists(filePath) { - try { - await access(filePath); - return true; - } catch { - return false; - } -} - -let checkedEndpoints = 0; -let checkedFiles = 0; -let patchedFiles = 0; - -for (const endpointPath of endpointCandidates) { - if (!(await exists(endpointPath))) { - continue; - } - - const includeDir = path.join(endpointPath, "includes", "post-types"); - if (!(await exists(includeDir))) { - continue; - } - - checkedEndpoints += 1; - await mkdir(includeDir, { recursive: true }); - - for (const [fileName, template] of Object.entries(templates)) { - checkedFiles += 1; - - const filePath = path.join(includeDir, fileName); - const desired = `${template}\n`; - - let current = ""; - if (await exists(filePath)) { - current = await readFile(filePath, "utf8"); - } - - if (current === desired) { - continue; - } - - await writeFile(filePath, desired, "utf8"); - patchedFiles += 1; - } -} - -if (checkedEndpoints === 0) { - console.log("[postinstall] No endpoint-posts package directories found"); -} else if (checkedFiles === 0) { - console.log("[postinstall] No endpoint-posts AI field templates checked"); -} else if (patchedFiles === 0) { - console.log("[postinstall] endpoint-posts AI field templates already patched"); -} else { - console.log( - `[postinstall] Patched endpoint-posts AI field templates in ${patchedFiles}/${checkedFiles} file(s)`, - ); -} diff --git a/scripts/patch-endpoint-posts-prefill-url.mjs b/scripts/patch-endpoint-posts-prefill-url.mjs deleted file mode 100644 index b6ca1cc9..00000000 --- a/scripts/patch-endpoint-posts-prefill-url.mjs +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Patch: pre-fill reference URL when creating posts from /news "Post" button. - * - * share-post.js opens /posts/create?type=like&url=&name= - * but postData.create only reads request.body for properties, ignoring query params. - * - * Fix: in postData.create, when properties is empty and request.query.url is present, - * seed properties with the correct field name for that post type: - * like → like-of - * bookmark → bookmark-of - * reply → in-reply-to - * repost → repost-of - * and optionally seed name/bookmark title from request.query.name. - */ -import { access, readFile, writeFile } from "node:fs/promises"; - -const patchSpecs = [ - { - name: "posts-prefill-url-from-query", - marker: "prefill reference URL from query param", - candidates: [ - "node_modules/@rmdes/indiekit-endpoint-posts/lib/middleware/post-data.js", - "node_modules/@indiekit/endpoint-posts/lib/middleware/post-data.js", - "node_modules/@indiekit/indiekit/node_modules/@rmdes/indiekit-endpoint-posts/lib/middleware/post-data.js", - ], - oldSnippet: ` const postType = request.query.type || "note"; - const properties = request.body || {};`, - newSnippet: ` const postType = request.query.type || "note"; - // prefill reference URL from query param when opening from share-post button - let properties = request.body || {}; - if (Object.entries(properties).length === 0 && request.query.url) { - const refUrl = request.query.url; - const refName = request.query.name || ""; - const urlFieldByType = { - like: "like-of", - bookmark: "bookmark-of", - reply: "in-reply-to", - repost: "repost-of", - }; - const urlField = urlFieldByType[postType]; - if (urlField) { - properties = { [urlField]: refUrl }; - if (postType === "bookmark" && refName) { - properties.name = refName; - } - } - }`, - }, -]; - -async function exists(filePath) { - try { - await access(filePath); - return true; - } catch { - return false; - } -} - -let filesChecked = 0; -let filesPatched = 0; - -for (const spec of patchSpecs) { - let foundAnyTarget = false; - - for (const filePath of spec.candidates) { - if (!(await exists(filePath))) continue; - - foundAnyTarget = true; - filesChecked += 1; - - const source = await readFile(filePath, "utf8"); - - if (source.includes(spec.marker)) continue; - if (!source.includes(spec.oldSnippet)) { - console.log(`[postinstall] ${spec.name}: snippet not found in ${filePath}`); - continue; - } - - const updated = source.replace(spec.oldSnippet, spec.newSnippet); - await writeFile(filePath, updated, "utf8"); - filesPatched += 1; - } - - if (!foundAnyTarget) { - console.log(`[postinstall] ${spec.name}: no target files found`); - } -} - -if (filesChecked === 0) { - console.log("[postinstall] No posts endpoint post-data.js found"); -} else if (filesPatched === 0) { - console.log("[postinstall] posts prefill-url already patched"); -} else { - console.log( - `[postinstall] Patched posts prefill-url in ${filesPatched}/${filesChecked} file(s)`, - ); -} diff --git a/scripts/patch-endpoint-posts-search-tags.mjs b/scripts/patch-endpoint-posts-search-tags.mjs deleted file mode 100644 index 77561165..00000000 --- a/scripts/patch-endpoint-posts-search-tags.mjs +++ /dev/null @@ -1,299 +0,0 @@ -/** - * Patch: add search input and tag chips to the /posts list page. - * - * Part A – posts.js controller: - * - forward `category` and `search` query params to the Micropub source query - * - expose `item.tagLinks` (pre-encoded href + text) for each post - * - pass `activeCategory` and `activeSearch` to the template - * - * Part B – posts.njk view: - * - replace the cardGrid call with a custom loop that appends clickable - * tag chips under each card - * - add a search form above the grid - */ -import { access, readFile, writeFile } from "node:fs/promises"; - -async function exists(filePath) { - try { - await access(filePath); - return true; - } catch { - return false; - } -} - -// ─── Part A: posts controller ──────────────────────────────────────────────── - -const controllerCandidates = [ - "node_modules/@rmdes/indiekit-endpoint-posts/lib/controllers/posts.js", - "node_modules/@indiekit/endpoint-posts/lib/controllers/posts.js", - "node_modules/@indiekit/indiekit/node_modules/@rmdes/indiekit-endpoint-posts/lib/controllers/posts.js", - "node_modules/@indiekit/indiekit/node_modules/@indiekit/endpoint-posts/lib/controllers/posts.js", -]; - -const controllerMarker = "// search-tags controller patch"; - -// 1) forward category + search to the micropub URL -const oldForward = ` const { after, before, success } = request.query; - const limit = Number(request.query.limit) || 12; - - const micropubUrl = new URL(application.micropubEndpoint); - micropubUrl.searchParams.append("q", "source"); - micropubUrl.searchParams.append("limit", String(limit)); - - if (after) { - micropubUrl.searchParams.append("after", String(after)); - } - - if (before) { - micropubUrl.searchParams.append("before", String(before)); - }`; - -const newForward = ` // search-tags controller patch - const { after, before, category, search, success } = request.query; - const limit = Number(request.query.limit) || 12; - - const micropubUrl = new URL(application.micropubEndpoint); - micropubUrl.searchParams.append("q", "source"); - micropubUrl.searchParams.append("limit", String(limit)); - - if (after) { - micropubUrl.searchParams.append("after", String(after)); - } - - if (before) { - micropubUrl.searchParams.append("before", String(before)); - } - - if (category) { - micropubUrl.searchParams.append("category", String(category)); - } - - if (search) { - micropubUrl.searchParams.append("search", String(search)); - }`; - -// 2) add tagLinks to each post item -const oldBadges = ` item.badges = getPostStatusBadges(item, response); - - return item;`; - -const newBadges = ` item.badges = getPostStatusBadges(item, response); - const rawTags = Array.isArray(item.category) - ? item.category - : item.category - ? [item.category] - : []; - item.tagLinks = rawTags.map((t) => ({ - text: t, - href: \`?category=\${encodeURIComponent(t)}\`, - })); - - return item;`; - -// 3) pass activeCategory + activeSearch to the render call -const oldRender = ` statusTypes, - success, - });`; - -const newRender = ` statusTypes, - success, - activeCategory: category || "", - activeSearch: search || "", - });`; - -// ─── Part B: posts view ─────────────────────────────────────────────────────── - -const viewCandidates = [ - "node_modules/@rmdes/indiekit-endpoint-posts/views/posts.njk", - "node_modules/@indiekit/endpoint-posts/views/posts.njk", - "node_modules/@indiekit/indiekit/node_modules/@rmdes/indiekit-endpoint-posts/views/posts.njk", - "node_modules/@indiekit/indiekit/node_modules/@indiekit/endpoint-posts/views/posts.njk", -]; - -const viewMarker = "{# search-tags view patch #}"; - -const oldView = `{% extends "document.njk" %} - -{% block content %} - {%- if posts.length > 0 %} - {{ cardGrid({ - cardSize: "16rem", - items: posts - }) }} - {{ pagination(cursor) }} - {%- else -%} - {{ prose({ text: __("posts.posts.none") }) }} - {%- endif %} -{% endblock %}`; - -const newView = `{% extends "document.njk" %} -{# search-tags view patch #} -{% block content %} - <form method="get" action=""> - <div style="display:flex;gap:0.5rem;align-items:flex-end;flex-wrap:wrap;margin-bottom:1.5rem"> - {{ input({ - name: "search", - label: "Search", - value: activeSearch, - type: "search", - optional: true, - field: { classes: "-!-flex-grow" } - }) }} - {{ button({ text: "Search", type: "submit" }) }} - {%- if activeSearch or activeCategory %} - <a href="{{ parentUrl }}" class="button" style="align-self:flex-end">Clear</a> - {%- endif %} - </div> - </form> - {%- if activeCategory %} - <p style="margin-bottom:1rem">Filtered by tag: <strong>{{ activeCategory }}</strong></p> - {%- endif %} - {%- if posts.length > 0 %} - <ol class="card-grid" role="list" style="--min-card-size: 16rem"> - {%- for item in posts %} - <li class="card-grid__item"> - <article class="card"> - {%- if item.photo %} - <div class="card__photo"> - <img src="{{ item.photo.url | imageUrl(application, width=240, height=240) }}" alt="{{ item.photo.alt }}" width="240" height="240" decoding="async" loading="lazy" onerror="this.src='/assets/not-found.svg'"> - </div> - {%- endif %} - <div class="card__body"> - {%- if item.title %} - <h2 class="card__title"> - {%- if item.url %} - <a href="{{ item.url }}" rel="bookmark"> - {%- endif %} - {{- icon(item.icon) if item.icon -}} - {{- item.title | safe -}} - {%- if item.url %} - </a> - {%- endif %} - </h2> - {%- endif %} - {{ prose({ classes: "card__meta", text: item.description.text, html: item.description.html }) | indent(10) if item.description }} - {%- set hasNonGarden = false %} - {%- for tl in item.tagLinks %} - {%- if tl.text != "garden" %}{%- set hasNonGarden = true %}{%- endif %} - {%- endfor %} - {%- if hasNonGarden %} - <div style="padding:0.25rem 0 0.5rem;display:flex;flex-wrap:wrap;gap:0.25rem"> - {%- for tl in item.tagLinks %} - {%- if tl.text != "garden" %} - <a href="{{ tl.href }}" class="tag" style="font-size:0.75em">{{ tl.text }}</a> - {%- endif %} - {%- endfor %} - </div> - {%- endif %} - {%- if item.published or item.badges %} - <footer class="card__footer" style="display:flex;align-items:center;gap:0.5rem"> - {%- for badgeItem in item.badges %} - {{ badge(badgeItem) | indent(12) if badgeItem }} - {%- endfor %} - {%- for tl in item.tagLinks %} - {%- if tl.text == "garden" %} - <a href="{{ tl.href }}" class="tag" style="margin-left:auto;font-size:0.75em;flex-shrink:0">garden</a> - {%- endif %} - {%- endfor %} - <time datetime="{{ item.published }}"> - {{ item.published | date("PPp", { locale: item.locale, timeZone: application.timeZone }) }} - </time> - </footer> - {%- endif %} - </div> - </article> - </li> - {%- endfor %} - </ol> - {{ pagination(cursor) }} - {%- else -%} - {{ prose({ text: __("posts.posts.none") }) }} - {%- endif %} -{% endblock %}`; - -// ─── Apply patches ──────────────────────────────────────────────────────────── - -let controllerChecked = 0; -let controllerPatched = 0; - -for (const filePath of controllerCandidates) { - if (!(await exists(filePath))) continue; - controllerChecked += 1; - - let source = await readFile(filePath, "utf8"); - if (source.includes(controllerMarker)) continue; - - let changed = false; - - for (const [oldSnip, newSnip] of [ - [oldForward, newForward], - [oldBadges, newBadges], - [oldRender, newRender], - ]) { - if (!source.includes(oldSnip)) { - // Beta.41+ has native filter/search/sort built in — skip silently - if (source.includes("buildFilterQuery") || source.includes("filters.postType")) { - changed = false; - break; - } - console.warn( - `[postinstall] posts search-tags: snippet not found in ${filePath}, skipping`, - ); - changed = false; - break; - } - source = source.replace(oldSnip, newSnip); - changed = true; - } - - if (changed) { - await writeFile(filePath, source, "utf8"); - controllerPatched += 1; - } -} - -let viewChecked = 0; -let viewPatched = 0; - -for (const filePath of viewCandidates) { - if (!(await exists(filePath))) continue; - viewChecked += 1; - - const source = await readFile(filePath, "utf8"); - if (source.includes(viewMarker)) continue; - - if (!source.includes(oldView)) { - // Beta.41+ has native filter/sort UI built in — skip silently - if (source.includes("posts-filter-row") || source.includes("posts.filter.type")) { - continue; - } - console.warn( - `[postinstall] posts search-tags: view not found in ${filePath}, skipping`, - ); - continue; - } - - const updated = source.replace(oldView, newView); - await writeFile(filePath, updated, "utf8"); - viewPatched += 1; -} - -if (controllerChecked === 0 && viewChecked === 0) { - console.log("[postinstall] No endpoint-posts files found"); -} else { - if (controllerPatched === 0 && controllerChecked > 0) { - console.log("[postinstall] posts search-tags controller already patched"); - } else if (controllerPatched > 0) { - console.log( - `[postinstall] Patched posts search-tags controller in ${controllerPatched} file(s)`, - ); - } - if (viewPatched === 0 && viewChecked > 0) { - console.log("[postinstall] posts search-tags view already patched"); - } else if (viewPatched > 0) { - console.log( - `[postinstall] Patched posts search-tags view in ${viewPatched} file(s)`, - ); - } -} diff --git a/scripts/patch-micropub-ai-block-resync.mjs b/scripts/patch-micropub-ai-block-resync.mjs deleted file mode 100644 index d1899363..00000000 --- a/scripts/patch-micropub-ai-block-resync.mjs +++ /dev/null @@ -1,115 +0,0 @@ -/** - * Patch @indiekit/endpoint-micropub/lib/post-data.js to detect stale AI block files. - * - * Problem: The v3 patch bug (supportsAiDisclosure always false) caused Indiekit to update - * MongoDB with AI field values (aiTextLevel, aiCodeLevel, etc.) but write the post file - * WITHOUT the ai: frontmatter block. Now when the user re-saves with the same AI values, - * Indiekit's isDeepStrictEqual check says "no properties changed" and skips the file write. - * The file remains stale (missing ai: block) even though MongoDB has the right data. - * - * Fix: Store an `_aiBlockVersion` field in MongoDB alongside each post. On update, if the - * stored version doesn't match the current patch version AND the post has AI fields, bypass - * the no-change check and force a file re-write. This triggers exactly once per affected - * post, then every subsequent no-change save correctly skips the write. - */ - -import { access, readFile, writeFile } from "node:fs/promises"; - -const AI_BLOCK_VERSION = "v4"; - -const candidates = [ - "node_modules/@indiekit/endpoint-micropub/lib/post-data.js", - "node_modules/@indiekit/indiekit/node_modules/@indiekit/endpoint-micropub/lib/post-data.js", -]; - -const marker = "AI block version resync patch"; - -// --- Old: simple destructuring that ignores _aiBlockVersion --- -const oldDestructure = `let { path: _originalPath, properties } = await this.read(application, url);`; - -const newDestructure = `let { path: _originalPath, properties, _aiBlockVersion: storedAiBlockVersion } = await this.read(application, url); // AI block version resync patch`; - -// --- Old: early return when no properties changed --- -const oldNoChange = ` // Return if no changes to template properties detected - const newProperties = getPostTemplateProperties(properties); - oldProperties = getPostTemplateProperties(oldProperties); - if (isDeepStrictEqual(newProperties, oldProperties)) { - return; - }`; - -const newNoChange = ` // Return if no changes to template properties detected - const newProperties = getPostTemplateProperties(properties); - oldProperties = getPostTemplateProperties(oldProperties); - if (isDeepStrictEqual(newProperties, oldProperties)) { - // AI block version resync patch: if post has AI fields and the file was written by an - // older patch version (or never written with the ai: block), force a one-time re-write. - const hasAiFields = - newProperties.aiTextLevel !== undefined || - newProperties.aiCodeLevel !== undefined; - const currentAiBlockVersion = "${AI_BLOCK_VERSION}"; - if (!hasAiFields || storedAiBlockVersion === currentAiBlockVersion) { - return; - } - // Fall through: force re-write to fix stale ai: block - }`; - -// --- Old: postData construction without _aiBlockVersion --- -const oldPostData = ` // Update data in posts collection - const postData = { _originalPath, path, properties };`; - -const newPostData = ` // Update data in posts collection - const postData = { _originalPath, path, properties, _aiBlockVersion: "${AI_BLOCK_VERSION}" }; // AI block version resync patch`; - -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(oldDestructure) || - !source.includes(oldNoChange) || - !source.includes(oldPostData) - ) { - console.warn( - `[postinstall] Skipping micropub AI block resync patch for ${filePath}: upstream format changed`, - ); - continue; - } - - const updated = source - .replace(oldDestructure, newDestructure) - .replace(oldNoChange, newNoChange) - .replace(oldPostData, newPostData); - - await writeFile(filePath, updated, "utf8"); - patched += 1; -} - -if (checked === 0) { - console.log("[postinstall] No endpoint-micropub post-data.js found"); -} else if (patched === 0) { - console.log("[postinstall] micropub AI block resync patch already applied"); -} else { - console.log( - `[postinstall] Patched micropub AI block resync in ${patched} file(s)`, - ); -} diff --git a/scripts/patch-preset-eleventy-ai-frontmatter.mjs b/scripts/patch-preset-eleventy-ai-frontmatter.mjs deleted file mode 100644 index c228be20..00000000 --- a/scripts/patch-preset-eleventy-ai-frontmatter.mjs +++ /dev/null @@ -1,577 +0,0 @@ -import { access, readFile, writeFile } from "node:fs/promises"; - -const candidates = [ - "node_modules/@rmdes/indiekit-preset-eleventy/lib/post-template.js", - "node_modules/@indiekit/preset-eleventy/lib/post-template.js", - "node_modules/@indiekit/indiekit/node_modules/@rmdes/indiekit-preset-eleventy/lib/post-template.js", - "node_modules/@indiekit/indiekit/node_modules/@indiekit/preset-eleventy/lib/post-template.js", -]; - -const patchMarker = - "Indiekit removes post-type before calling postTemplate; fall back to permalink-based detection."; - -const upstreamBlock = [ - " // Convert url to Eleventy permalink so generated URL matches Indiekit's stored URL", - " // Add trailing slash to generate /path/index.html instead of /path.html", - " if (properties.url) {", - " const url = properties.url;", - " properties.permalink = url.endsWith(\"/\") ? url : `${url}/`;", - " }", - " delete properties.url;", - "", - " const frontMatter = YAML.stringify(properties, { lineWidth: 0 });", - " return `---\\n${frontMatter}---\\n`;", - "};", -].join("\n"); - -const v1PatchedBlock = [ - " // Convert url to Eleventy permalink so generated URL matches Indiekit's stored URL", - " // Add trailing slash to generate /path/index.html instead of /path.html", - " if (properties.url) {", - " const url = properties.url;", - " properties.permalink = url.endsWith(\"/\") ? url : `${url}/`;", - " }", - " delete properties.url;", - "", - " // Normalize AI disclosure metadata and default to no AI usage.", - " const aiSource =", - " properties.ai && typeof properties.ai === \"object\" && !Array.isArray(properties.ai)", - " ? properties.ai", - " : {};", - "", - " const aiTextLevel = String(", - " aiSource.textLevel ?? aiSource.aiTextLevel ?? properties.aiTextLevel ?? \"0\",", - " );", - "", - " const aiCodeLevel = String(", - " aiSource.codeLevel ?? aiSource.aiCodeLevel ?? properties.aiCodeLevel ?? \"0\",", - " );", - "", - " const aiTools = aiSource.aiTools ?? aiSource.tools ?? properties.aiTools;", - "", - " const aiDescription =", - " aiSource.aiDescription ?? aiSource.description ?? properties.aiDescription;", - "", - " delete properties.ai;", - " delete properties.aiTextLevel;", - " delete properties.aiCodeLevel;", - " delete properties.aiTools;", - " delete properties.aiDescription;", - "", - " const frontMatter = YAML.stringify(properties, { lineWidth: 0 });", - "", - " let aiFrontMatter = `ai:\\n textLevel: \\\"${aiTextLevel}\\\"\\n codeLevel: \\\"${aiCodeLevel}\\\"\\n # aiTools: \\\"Claude, ChatGPT, Copilot\\\"\\n # aiDescription: \\\"Optional disclosure about how AI was used\\\"\\n`;", - "", - " if (aiTools !== undefined && aiTools !== null && aiTools !== \"\") {", - " aiFrontMatter = aiFrontMatter.replace(", - " ' # aiTools: \\\"Claude, ChatGPT, Copilot\\\"\\n',", - " ` aiTools: ${JSON.stringify(String(aiTools))}\\n`,", - " );", - " }", - "", - " if (aiDescription !== undefined && aiDescription !== null && aiDescription !== \"\") {", - " aiFrontMatter = aiFrontMatter.replace(", - " ' # aiDescription: \\\"Optional disclosure about how AI was used\\\"\\n',", - " ` aiDescription: ${JSON.stringify(String(aiDescription))}\\n`,", - " );", - " }", - "", - " return `---\\n${frontMatter}${aiFrontMatter}---\\n`;", - "};", -].join("\n"); - -const v2PatchedBlock = [ - " // Convert url to Eleventy permalink so generated URL matches Indiekit's stored URL", - " // Add trailing slash to generate /path/index.html instead of /path.html", - " if (properties.url) {", - " const url = properties.url;", - " properties.permalink = url.endsWith(\"/\") ? url : `${url}/`;", - " }", - " delete properties.url;", - "", - " // Normalize AI disclosure metadata for articles and notes only, defaulting to no AI usage.", - " const aiSource =", - " properties.ai && typeof properties.ai === \"object\" && !Array.isArray(properties.ai)", - " ? properties.ai", - " : {};", - "", - " const aiTextLevel = String(", - " aiSource.textLevel ?? aiSource.aiTextLevel ?? properties.aiTextLevel ?? \"0\",", - " );", - "", - " const aiCodeLevel = String(", - " aiSource.codeLevel ?? aiSource.aiCodeLevel ?? properties.aiCodeLevel ?? \"0\",", - " );", - "", - " const aiTools = aiSource.aiTools ?? aiSource.tools ?? properties.aiTools;", - "", - " const aiDescription =", - " aiSource.aiDescription ?? aiSource.description ?? properties.aiDescription;", - "", - " delete properties.ai;", - " delete properties.aiTextLevel;", - " delete properties.aiCodeLevel;", - " delete properties.aiTools;", - " delete properties.aiDescription;", - "", - " const postType = String(", - " properties.postType ?? properties[\"post-type\"] ?? \"\",", - " ).toLowerCase();", - " const supportsAiDisclosure = postType === \"article\" || postType === \"note\";", - "", - " const frontMatter = YAML.stringify(properties, { lineWidth: 0 });", - "", - " if (!supportsAiDisclosure) {", - " return `---\\n${frontMatter}---\\n`;", - " }", - "", - " let aiFrontMatter = `ai:\\n textLevel: \\\"${aiTextLevel}\\\"\\n codeLevel: \\\"${aiCodeLevel}\\\"\\n # aiTools: \\\"Claude, ChatGPT, Copilot\\\"\\n # aiDescription: \\\"Optional disclosure about how AI was used\\\"\\n`;", - "", - " if (aiTools !== undefined && aiTools !== null && aiTools !== \"\") {", - " aiFrontMatter = aiFrontMatter.replace(", - " ' # aiTools: \\\"Claude, ChatGPT, Copilot\\\"\\n',", - " ` aiTools: ${JSON.stringify(String(aiTools))}\\n`,", - " );", - " }", - "", - " if (aiDescription !== undefined && aiDescription !== null && aiDescription !== \"\") {", - " aiFrontMatter = aiFrontMatter.replace(", - " ' # aiDescription: \\\"Optional disclosure about how AI was used\\\"\\n',", - " ` aiDescription: ${JSON.stringify(String(aiDescription))}\\n`,", - " );", - " }", - "", - " return `---\\n${frontMatter}${aiFrontMatter}---\\n`;", - "};", -].join("\n"); - -const v3Block = [ - " // Convert url to Eleventy permalink so generated URL matches Indiekit's stored URL", - " // Add trailing slash to generate /path/index.html instead of /path.html", - " if (properties.url) {", - " const url = properties.url;", - " properties.permalink = url.endsWith(\"/\") ? url : `${url}/`;", - " }", - " delete properties.url;", - "", - " // Normalize and sanitize AI disclosure metadata for articles and notes only.", - " const aiSource =", - " properties.ai && typeof properties.ai === \"object\" && !Array.isArray(properties.ai)", - " ? properties.ai", - " : {};", - "", - " const normaliseString = (value) => {", - " if (value === undefined || value === null) {", - " return undefined;", - " }", - "", - " const text = String(value).trim();", - " return text === \"\" ? undefined : text;", - " };", - "", - " const normaliseLevel = (value, allowedValues, fallback = \"0\") => {", - " const candidate = normaliseString(value);", - "", - " if (!candidate) {", - " return fallback;", - " }", - "", - " return allowedValues.includes(candidate) ? candidate : fallback;", - " };", - "", - " const aiTextLevelRaw =", - " aiSource.textLevel ??", - " aiSource.aiTextLevel ??", - " properties.aiTextLevel ??", - " properties[\"ai-text-level\"] ??", - " \"0\";", - "", - " const aiCodeLevelRaw =", - " aiSource.codeLevel ??", - " aiSource.aiCodeLevel ??", - " properties.aiCodeLevel ??", - " properties[\"ai-code-level\"] ??", - " \"0\";", - "", - " const aiTextLevel = normaliseLevel(aiTextLevelRaw, [\"0\", \"1\", \"2\", \"3\"]);", - " // Legacy value \"3\" is folded into \"2\" for code-level taxonomy compatibility.", - " const aiCodeLevel = normaliseLevel(", - " aiCodeLevelRaw === \"3\" ? \"2\" : aiCodeLevelRaw,", - " [\"0\", \"1\", \"2\"],", - " );", - "", - " const aiTools = normaliseString(", - " aiSource.aiTools ?? aiSource.tools ?? properties.aiTools ?? properties[\"ai-tools\"],", - " );", - "", - " const aiDescription = normaliseString(", - " aiSource.aiDescription ??", - " aiSource.description ??", - " properties.aiDescription ??", - " properties[\"ai-description\"],", - " );", - "", - " delete properties.ai;", - " delete properties.aiTextLevel;", - " delete properties.aiCodeLevel;", - " delete properties.aiTools;", - " delete properties.aiDescription;", - " delete properties[\"ai-text-level\"];", - " delete properties[\"ai-code-level\"];", - " delete properties[\"ai-tools\"];", - " delete properties[\"ai-description\"];", - "", - " const postType = String(", - " properties.postType ?? properties[\"post-type\"] ?? properties.type ?? \"\",", - " ).toLowerCase();", - " const supportsAiDisclosure = postType === \"article\" || postType === \"note\";", - "", - " const frontMatter = YAML.stringify(properties, { lineWidth: 0 });", - "", - " if (!supportsAiDisclosure) {", - " return `---\\n${frontMatter}---\\n`;", - " }", - "", - " let aiFrontMatter = `ai:\\n textLevel: \\\"${aiTextLevel}\\\"\\n codeLevel: \\\"${aiCodeLevel}\\\"\\n # aiTools: \\\"Claude, ChatGPT, Copilot\\\"\\n # aiDescription: \\\"Optional disclosure about how AI was used\\\"\\n`;", - "", - " if (aiTools) {", - " aiFrontMatter = aiFrontMatter.replace(", - " ' # aiTools: \\\"Claude, ChatGPT, Copilot\\\"\\n',", - " ` aiTools: ${JSON.stringify(aiTools)}\\n`,", - " );", - " }", - "", - " if (aiDescription) {", - " aiFrontMatter = aiFrontMatter.replace(", - " ' # aiDescription: \\\"Optional disclosure about how AI was used\\\"\\n',", - " ` aiDescription: ${JSON.stringify(aiDescription)}\\n`,", - " );", - " }", - "", - " return `---\\n${frontMatter}${aiFrontMatter}---\\n`;", - "};", -].join("\n"); - -// v4: fix post-type detection — Indiekit removes post-type before calling postTemplate, -// so fall back to permalink URL pattern to detect articles and notes. -const v4Block = [ - " // Convert url to Eleventy permalink so generated URL matches Indiekit's stored URL", - " // Add trailing slash to generate /path/index.html instead of /path.html", - " if (properties.url) {", - " const url = properties.url;", - " properties.permalink = url.endsWith(\"/\") ? url : `${url}/`;", - " }", - " delete properties.url;", - "", - " // Normalize and sanitize AI disclosure metadata for articles and notes only.", - " const aiSource =", - " properties.ai && typeof properties.ai === \"object\" && !Array.isArray(properties.ai)", - " ? properties.ai", - " : {};", - "", - " const normaliseString = (value) => {", - " if (value === undefined || value === null) {", - " return undefined;", - " }", - "", - " const text = String(value).trim();", - " return text === \"\" ? undefined : text;", - " };", - "", - " const normaliseLevel = (value, allowedValues, fallback = \"0\") => {", - " const candidate = normaliseString(value);", - "", - " if (!candidate) {", - " return fallback;", - " }", - "", - " return allowedValues.includes(candidate) ? candidate : fallback;", - " };", - "", - " const aiTextLevelRaw =", - " aiSource.textLevel ??", - " aiSource.aiTextLevel ??", - " properties.aiTextLevel ??", - " properties[\"ai-text-level\"] ??", - " \"0\";", - "", - " const aiCodeLevelRaw =", - " aiSource.codeLevel ??", - " aiSource.aiCodeLevel ??", - " properties.aiCodeLevel ??", - " properties[\"ai-code-level\"] ??", - " \"0\";", - "", - " const aiTextLevel = normaliseLevel(aiTextLevelRaw, [\"0\", \"1\", \"2\", \"3\"]);", - " // Legacy value \"3\" is folded into \"2\" for code-level taxonomy compatibility.", - " const aiCodeLevel = normaliseLevel(", - " aiCodeLevelRaw === \"3\" ? \"2\" : aiCodeLevelRaw,", - " [\"0\", \"1\", \"2\"],", - " );", - "", - " const aiTools = normaliseString(", - " aiSource.aiTools ?? aiSource.tools ?? properties.aiTools ?? properties[\"ai-tools\"],", - " );", - "", - " const aiDescription = normaliseString(", - " aiSource.aiDescription ??", - " aiSource.description ??", - " properties.aiDescription ??", - " properties[\"ai-description\"],", - " );", - "", - " delete properties.ai;", - " delete properties.aiTextLevel;", - " delete properties.aiCodeLevel;", - " delete properties.aiTools;", - " delete properties.aiDescription;", - " delete properties[\"ai-text-level\"];", - " delete properties[\"ai-code-level\"];", - " delete properties[\"ai-tools\"];", - " delete properties[\"ai-description\"];", - "", - " // Indiekit removes post-type before calling postTemplate; fall back to permalink-based detection.", - " const postType = String(", - " properties.postType ?? properties[\"post-type\"] ?? properties.type ?? \"\",", - " ).toLowerCase();", - " const permalink = String(properties.permalink ?? \"\");", - " const supportsAiDisclosure =", - " postType === \"article\" || postType === \"note\" ||", - " /\\/articles(?:\\/|$)/.test(permalink) || /\\/notes(?:\\/|$)/.test(permalink);", - "", - " const frontMatter = YAML.stringify(properties, { lineWidth: 0 });", - "", - " if (!supportsAiDisclosure) {", - " return `---\\n${frontMatter}---\\n`;", - " }", - "", - " let aiFrontMatter = `ai:\\n textLevel: \\\"${aiTextLevel}\\\"\\n codeLevel: \\\"${aiCodeLevel}\\\"\\n # aiTools: \\\"Claude, ChatGPT, Copilot\\\"\\n # aiDescription: \\\"Optional disclosure about how AI was used\\\"\\n`;", - "", - " if (aiTools) {", - " aiFrontMatter = aiFrontMatter.replace(", - " ' # aiTools: \\\"Claude, ChatGPT, Copilot\\\"\\n',", - " ` aiTools: ${JSON.stringify(aiTools)}\\n`,", - " );", - " }", - "", - " if (aiDescription) {", - " aiFrontMatter = aiFrontMatter.replace(", - " ' # aiDescription: \\\"Optional disclosure about how AI was used\\\"\\n',", - " ` aiDescription: ${JSON.stringify(aiDescription)}\\n`,", - " );", - " }", - "", - " return `---\\n${frontMatter}${aiFrontMatter}---\\n`;", - "};", -].join("\n"); - -// v5: upstream added mpUrl storage + URL normalization (pathname extraction). -// Matches the new block structure added after v4. -const v5UpstreamBlock = [ - " // Store the Micropub URL for frontend edit links before deleting it", - " if (properties.url) {", - " properties.mpUrl = properties.url;", - " }", - "", - " // Convert Indiekit URL to Eleventy permalink so pages generate", - " // at the canonical URL (e.g., /notes/2026/02/22/slug/) instead of", - " // the file-path-based URL (e.g., /content/notes/2026-02-22-slug/).", - " if (properties.url) {", - " let url = properties.url;", - " if (url.startsWith(\"http://\") || url.startsWith(\"https://\")) {", - " try {", - " url = new URL(url).pathname;", - " } catch {", - " // If URL parsing fails, use as-is", - " }", - " }", - " properties.permalink = url.endsWith(\"/\") ? url : `${url}/`;", - " }", - " delete properties.url;", - "", - " const frontMatter = YAML.stringify(properties, { lineWidth: 0 });", - " return `---\\n${frontMatter}---\\n`;", - "};", -].join("\n"); - -const v5PatchedBlock = [ - " // Store the Micropub URL for frontend edit links before deleting it", - " if (properties.url) {", - " properties.mpUrl = properties.url;", - " }", - "", - " // Convert Indiekit URL to Eleventy permalink so pages generate", - " // at the canonical URL (e.g., /notes/2026/02/22/slug/) instead of", - " // the file-path-based URL (e.g., /content/notes/2026-02-22-slug/).", - " if (properties.url) {", - " let url = properties.url;", - " if (url.startsWith(\"http://\") || url.startsWith(\"https://\")) {", - " try {", - " url = new URL(url).pathname;", - " } catch {", - " // If URL parsing fails, use as-is", - " }", - " }", - " properties.permalink = url.endsWith(\"/\") ? url : `${url}/`;", - " }", - " delete properties.url;", - "", - " // Normalize and sanitize AI disclosure metadata for articles and notes only.", - " const aiSource =", - " properties.ai && typeof properties.ai === \"object\" && !Array.isArray(properties.ai)", - " ? properties.ai", - " : {};", - "", - " const normaliseString = (value) => {", - " if (value === undefined || value === null) {", - " return undefined;", - " }", - "", - " const text = String(value).trim();", - " return text === \"\" ? undefined : text;", - " };", - "", - " const normaliseLevel = (value, allowedValues, fallback = \"0\") => {", - " const candidate = normaliseString(value);", - "", - " if (!candidate) {", - " return fallback;", - " }", - "", - " return allowedValues.includes(candidate) ? candidate : fallback;", - " };", - "", - " const aiTextLevelRaw =", - " aiSource.textLevel ??", - " aiSource.aiTextLevel ??", - " properties.aiTextLevel ??", - " properties[\"ai-text-level\"] ??", - " \"0\";", - "", - " const aiCodeLevelRaw =", - " aiSource.codeLevel ??", - " aiSource.aiCodeLevel ??", - " properties.aiCodeLevel ??", - " properties[\"ai-code-level\"] ??", - " \"0\";", - "", - " const aiTextLevel = normaliseLevel(aiTextLevelRaw, [\"0\", \"1\", \"2\", \"3\"]);", - " // Legacy value \"3\" is folded into \"2\" for code-level taxonomy compatibility.", - " const aiCodeLevel = normaliseLevel(", - " aiCodeLevelRaw === \"3\" ? \"2\" : aiCodeLevelRaw,", - " [\"0\", \"1\", \"2\"],", - " );", - "", - " const aiTools = normaliseString(", - " aiSource.aiTools ?? aiSource.tools ?? properties.aiTools ?? properties[\"ai-tools\"],", - " );", - "", - " const aiDescription = normaliseString(", - " aiSource.aiDescription ??", - " aiSource.description ??", - " properties.aiDescription ??", - " properties[\"ai-description\"],", - " );", - "", - " delete properties.ai;", - " delete properties.aiTextLevel;", - " delete properties.aiCodeLevel;", - " delete properties.aiTools;", - " delete properties.aiDescription;", - " delete properties[\"ai-text-level\"];", - " delete properties[\"ai-code-level\"];", - " delete properties[\"ai-tools\"];", - " delete properties[\"ai-description\"];", - "", - " // Indiekit removes post-type before calling postTemplate; fall back to permalink-based detection.", - " const postType = String(", - " properties.postType ?? properties[\"post-type\"] ?? properties.type ?? \"\",", - " ).toLowerCase();", - " const permalink = String(properties.permalink ?? \"\");", - " const supportsAiDisclosure =", - " postType === \"article\" || postType === \"note\" ||", - " /\\/articles(?:\\/|$)/.test(permalink) || /\\/notes(?:\\/|$)/.test(permalink);", - "", - " const frontMatter = YAML.stringify(properties, { lineWidth: 0 });", - "", - " if (!supportsAiDisclosure) {", - " return `---\\n${frontMatter}---\\n`;", - " }", - "", - " let aiFrontMatter = `ai:\\n textLevel: \\\"${aiTextLevel}\\\"\\n codeLevel: \\\"${aiCodeLevel}\\\"\\n # aiTools: \\\"Claude, ChatGPT, Copilot\\\"\\n # aiDescription: \\\"Optional disclosure about how AI was used\\\"\\n`;", - "", - " if (aiTools) {", - " aiFrontMatter = aiFrontMatter.replace(", - " ' # aiTools: \\\"Claude, ChatGPT, Copilot\\\"\\n',", - " ` aiTools: ${JSON.stringify(aiTools)}\\n`,", - " );", - " }", - "", - " if (aiDescription) {", - " aiFrontMatter = aiFrontMatter.replace(", - " ' # aiDescription: \\\"Optional disclosure about how AI was used\\\"\\n',", - " ` aiDescription: ${JSON.stringify(aiDescription)}\\n`,", - " );", - " }", - "", - " return `---\\n${frontMatter}${aiFrontMatter}---\\n`;", - "};", -].join("\n"); - -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(patchMarker)) { - continue; - } - - let updated = source; - - if (source.includes(v5UpstreamBlock)) { - updated = source.replace(v5UpstreamBlock, v5PatchedBlock); - } else if (source.includes(v3Block)) { - updated = source.replace(v3Block, v4Block); - } else if (source.includes(v2PatchedBlock)) { - updated = source.replace(v2PatchedBlock, v4Block); - } else if (source.includes(v1PatchedBlock)) { - updated = source.replace(v1PatchedBlock, v4Block); - } else if (source.includes(upstreamBlock)) { - updated = source.replace(upstreamBlock, v4Block); - } else { - console.warn( - `[postinstall] Skipping preset-eleventy AI frontmatter patch for ${filePath}: upstream format changed`, - ); - continue; - } - - await writeFile(filePath, updated, "utf8"); - patched += 1; -} - -if (checked === 0) { - console.log("[postinstall] No preset-eleventy post-template files found"); -} else if (patched === 0) { - console.log("[postinstall] preset-eleventy AI frontmatter patch already applied"); -} else { - console.log( - `[postinstall] Patched preset-eleventy AI frontmatter in ${patched} file(s)`, - ); -}