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 %}
-
- {%- if activeCategory %}
- Filtered by tag: {{ activeCategory }}
- {%- endif %}
- {%- if posts.length > 0 %}
-
- {%- for item in posts %}
- -
-
- {%- if item.photo %}
-
-
 }})
-
- {%- endif %}
-
- {%- if item.title %}
-
- {%- 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 %}
-
- {%- for tl in item.tagLinks %}
- {%- if tl.text != "garden" %}
-
{{ tl.text }}
- {%- endif %}
- {%- endfor %}
-
- {%- endif %}
- {%- if item.published or item.badges %}
-
- {%- endif %}
-
-
-
- {%- endfor %}
-
- {{ 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)`,
- );
-}