Optimize AI metadata pipeline for editor and frontmatter
This commit is contained in:
@@ -4,8 +4,8 @@
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"postinstall": "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-activitypub-docloader-loglevel.mjs && node scripts/patch-endpoint-activitypub-migrate-alias-clear.mjs && node scripts/patch-endpoint-homepage-locales.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-podroll-opml-upload.mjs && node scripts/patch-preset-eleventy-ai-frontmatter.mjs && node scripts/patch-frontend-serviceworker-file.mjs && node scripts/patch-conversations-collection-guards.mjs && node scripts/patch-conversations-mastodon-disconnect.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",
|
||||
"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-activitypub-docloader-loglevel.mjs && node scripts/patch-endpoint-activitypub-migrate-alias-clear.mjs && node scripts/patch-endpoint-homepage-locales.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-podroll-opml-upload.mjs && node scripts/patch-preset-eleventy-ai-frontmatter.mjs && node scripts/patch-frontend-serviceworker-file.mjs && node scripts/patch-conversations-collection-guards.mjs && node scripts/patch-conversations-mastodon-disconnect.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 node_modules/@indiekit/indiekit/bin/cli.js serve --config indiekit.config.mjs",
|
||||
"postinstall": "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-activitypub-docloader-loglevel.mjs && node scripts/patch-endpoint-activitypub-migrate-alias-clear.mjs && node scripts/patch-endpoint-homepage-locales.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-frontend-serviceworker-file.mjs && node scripts/patch-conversations-collection-guards.mjs && node scripts/patch-conversations-mastodon-disconnect.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",
|
||||
"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-activitypub-docloader-loglevel.mjs && node scripts/patch-endpoint-activitypub-migrate-alias-clear.mjs && node scripts/patch-endpoint-homepage-locales.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-frontend-serviceworker-file.mjs && node scripts/patch-conversations-collection-guards.mjs && node scripts/patch-conversations-mastodon-disconnect.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 node_modules/@indiekit/indiekit/bin/cli.js serve --config indiekit.config.mjs",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
|
||||
91
scripts/patch-endpoint-posts-ai-cleanup.mjs
Normal file
91
scripts/patch-endpoint-posts-ai-cleanup.mjs
Normal file
@@ -0,0 +1,91 @@
|
||||
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 = "Remove empty AI metadata fields so Micropub payload stays lean.";
|
||||
|
||||
const oldSnippet = [
|
||||
" // Easy MDE appends `image` value to formData for last image uploaded",
|
||||
" delete values.image;",
|
||||
"",
|
||||
" 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\",",
|
||||
" \"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");
|
||||
|
||||
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)) {
|
||||
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)`,
|
||||
);
|
||||
}
|
||||
@@ -10,70 +10,67 @@ const endpointCandidates = [
|
||||
|
||||
const templates = {
|
||||
"aiTextLevel-field.njk": [
|
||||
'{% set aiTextLevelValue = fieldData("aiTextLevel").value or (properties.ai.textLevel if properties.ai and properties.ai.textLevel is defined else properties.aiTextLevel) or "0" %}',
|
||||
'{% 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-Einsatz",',
|
||||
' legend: "AI text level",',
|
||||
" optional: true",
|
||||
" },",
|
||||
" items: [{",
|
||||
' label: "0 - Kein KI-Text",',
|
||||
' label: "0 - None",',
|
||||
' value: "0"',
|
||||
" }, {",
|
||||
' label: "1 - Leichte KI-Hilfe",',
|
||||
' label: "1 - Editorial assistance",',
|
||||
' value: "1"',
|
||||
" }, {",
|
||||
' label: "2 - Teilweise KI-generiert",',
|
||||
' label: "2 - Co-drafting",',
|
||||
' value: "2"',
|
||||
" }, {",
|
||||
' label: "3 - Ueberwiegend KI-generiert",',
|
||||
' label: "3 - AI-generated (human reviewed)",',
|
||||
' value: "3"',
|
||||
" }]",
|
||||
"}) }}",
|
||||
].join("\n"),
|
||||
"aiCodeLevel-field.njk": [
|
||||
'{% set aiCodeLevelValue = fieldData("aiCodeLevel").value or (properties.ai.codeLevel if properties.ai and properties.ai.codeLevel is defined else properties.aiCodeLevel) or "0" %}',
|
||||
'{% 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-Einsatz",',
|
||||
' legend: "AI code level",',
|
||||
" optional: true",
|
||||
" },",
|
||||
" items: [{",
|
||||
' label: "0 - Kein KI-Code",',
|
||||
' label: "0 - Human-written",',
|
||||
' value: "0"',
|
||||
" }, {",
|
||||
' label: "1 - Leichte KI-Hilfe",',
|
||||
' label: "1 - AI-assisted",',
|
||||
' value: "1"',
|
||||
" }, {",
|
||||
' label: "2 - Teilweise KI-generiert",',
|
||||
' label: "2 - Primarily AI-generated",',
|
||||
' value: "2"',
|
||||
" }, {",
|
||||
' label: "3 - Ueberwiegend KI-generiert",',
|
||||
' value: "3"',
|
||||
" }]",
|
||||
"}) }}",
|
||||
].join("\n"),
|
||||
"aiTools-field.njk": [
|
||||
'{% set aiToolsValue = fieldData("aiTools").value or (properties.ai.aiTools if properties.ai and properties.ai.aiTools is defined else properties.aiTools) %}',
|
||||
'{% 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, komma-separiert (z. B. Claude, ChatGPT, Copilot)",',
|
||||
' hint: "Optional, comma-separated (e.g. Claude, ChatGPT, Copilot)",',
|
||||
" optional: true",
|
||||
"}) }}",
|
||||
].join("\n"),
|
||||
"aiDescription-field.njk": [
|
||||
'{% set aiDescriptionValue = fieldData("aiDescription").value or (properties.ai.aiDescription if properties.ai and properties.ai.aiDescription is defined else properties.aiDescription) %}',
|
||||
'{% 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 Beschreibung",',
|
||||
' hint: "Optional: kurze Erlaeuterung, wie KI verwendet wurde",',
|
||||
' label: "AI usage note",',
|
||||
' hint: "Optional: short note describing how AI was used",',
|
||||
" optional: true",
|
||||
"}) }}",
|
||||
].join("\n"),
|
||||
|
||||
@@ -8,7 +8,7 @@ const candidates = [
|
||||
];
|
||||
|
||||
const patchMarker =
|
||||
"Normalize AI disclosure metadata for articles and notes only, defaulting to no AI usage.";
|
||||
"Normalize and sanitize AI disclosure metadata for articles and notes only.";
|
||||
|
||||
const upstreamBlock = [
|
||||
" // Convert url to Eleventy permalink so generated URL matches Indiekit's stored URL",
|
||||
@@ -80,7 +80,7 @@ const v1PatchedBlock = [
|
||||
"};",
|
||||
].join("\n");
|
||||
|
||||
const v2Block = [
|
||||
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) {",
|
||||
@@ -145,6 +145,113 @@ const v2Block = [
|
||||
"};",
|
||||
].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");
|
||||
|
||||
async function exists(filePath) {
|
||||
try {
|
||||
await access(filePath);
|
||||
@@ -172,10 +279,12 @@ for (const filePath of candidates) {
|
||||
|
||||
let updated = source;
|
||||
|
||||
if (source.includes(v1PatchedBlock)) {
|
||||
updated = source.replace(v1PatchedBlock, v2Block);
|
||||
if (source.includes(v2PatchedBlock)) {
|
||||
updated = source.replace(v2PatchedBlock, v3Block);
|
||||
} else if (source.includes(v1PatchedBlock)) {
|
||||
updated = source.replace(v1PatchedBlock, v3Block);
|
||||
} else if (source.includes(upstreamBlock)) {
|
||||
updated = source.replace(upstreamBlock, v2Block);
|
||||
updated = source.replace(upstreamBlock, v3Block);
|
||||
} else {
|
||||
console.warn(
|
||||
`[postinstall] Skipping preset-eleventy AI frontmatter patch for ${filePath}: upstream format changed`,
|
||||
|
||||
Reference in New Issue
Block a user