Add Podroll OPML file upload support
This commit is contained in:
@@ -4,8 +4,8 @@
|
|||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"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-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-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",
|
"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-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-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-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",
|
"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-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",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
|
|||||||
482
scripts/patch-endpoint-podroll-opml-upload.mjs
Normal file
482
scripts/patch-endpoint-podroll-opml-upload.mjs
Normal file
@@ -0,0 +1,482 @@
|
|||||||
|
import { access, readFile, writeFile } from "node:fs/promises";
|
||||||
|
import path from "node:path";
|
||||||
|
|
||||||
|
const endpointCandidates = [
|
||||||
|
"node_modules/@rmdes/indiekit-endpoint-podroll",
|
||||||
|
"node_modules/@indiekit/indiekit/node_modules/@rmdes/indiekit-endpoint-podroll",
|
||||||
|
];
|
||||||
|
|
||||||
|
const dashboardFormOld =
|
||||||
|
'<form method="post" action="{{ mountPath }}/settings" class="podroll-form">';
|
||||||
|
const dashboardFormNew =
|
||||||
|
'<form method="post" action="{{ mountPath }}/settings" class="podroll-form" enctype="multipart/form-data">';
|
||||||
|
|
||||||
|
const dashboardOpmlFieldOld = [
|
||||||
|
' <div class="podroll-field">',
|
||||||
|
' <label class="label" for="opmlUrl">{{ __("podroll.opmlUrl") }}</label>',
|
||||||
|
' <span class="hint" id="opmlUrl-hint">{{ __("podroll.opmlUrlHelp") }}</span>',
|
||||||
|
' <input class="input" type="url" id="opmlUrl" name="opmlUrl" value="{{ config.opmlUrl }}" aria-describedby="opmlUrl-hint" placeholder="https://...">',
|
||||||
|
' </div>',
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
const dashboardOpmlFieldNew = [
|
||||||
|
' <div class="podroll-field">',
|
||||||
|
' <label class="label" for="opmlUrl">{{ __("podroll.opmlUrl") }}</label>',
|
||||||
|
' <span class="hint" id="opmlUrl-hint">{{ __("podroll.opmlUrlHelp") }}</span>',
|
||||||
|
' <input class="input" type="url" id="opmlUrl" name="opmlUrl" value="{{ config.opmlUrl }}" aria-describedby="opmlUrl-hint" placeholder="https://...">',
|
||||||
|
' </div>',
|
||||||
|
' <div class="podroll-field">',
|
||||||
|
' <label class="label" for="opmlFile">{{ __("podroll.opmlFile") }}</label>',
|
||||||
|
' <span class="hint" id="opmlFile-hint">{{ __("podroll.opmlFileHelp") }}</span>',
|
||||||
|
' <input class="input" type="file" id="opmlFile" name="opmlFile" accept=".opml,.xml,text/xml,application/xml" aria-describedby="opmlFile-hint">',
|
||||||
|
' {% if config.hasOpmlUpload %}',
|
||||||
|
' <p class="hint">{{ __("podroll.opmlFileActive") }}</p>',
|
||||||
|
' {% endif %}',
|
||||||
|
' </div>',
|
||||||
|
' <div class="podroll-field">',
|
||||||
|
' <label class="checkbox" for="clearOpmlFile">',
|
||||||
|
' <input type="checkbox" id="clearOpmlFile" name="clearOpmlFile" value="1">',
|
||||||
|
' {{ __("podroll.clearOpmlFile") }}',
|
||||||
|
' </label>',
|
||||||
|
' </div>',
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
const controllerGetEffectiveOld = [
|
||||||
|
'/**',
|
||||||
|
' * Get effective URLs: DB-stored settings override env var defaults',
|
||||||
|
' * @param {object} db - MongoDB database instance',
|
||||||
|
' * @param {object} podrollConfig - Plugin config from env vars',
|
||||||
|
' * @returns {Promise<object>} Effective episodesUrl and opmlUrl',
|
||||||
|
' */',
|
||||||
|
'async function getEffectiveUrls(db, podrollConfig) {',
|
||||||
|
' let episodesUrl = podrollConfig?.episodesUrl || "";',
|
||||||
|
' let opmlUrl = podrollConfig?.opmlUrl || "";',
|
||||||
|
'',
|
||||||
|
' if (db) {',
|
||||||
|
' const settings = await db',
|
||||||
|
' .collection("podrollMeta")',
|
||||||
|
' .findOne({ key: "settings" });',
|
||||||
|
' if (settings) {',
|
||||||
|
' if (settings.episodesUrl) episodesUrl = settings.episodesUrl;',
|
||||||
|
' if (settings.opmlUrl) opmlUrl = settings.opmlUrl;',
|
||||||
|
' }',
|
||||||
|
' }',
|
||||||
|
'',
|
||||||
|
' return { episodesUrl, opmlUrl };',
|
||||||
|
'}',
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
const controllerGetEffectiveNew = [
|
||||||
|
'/**',
|
||||||
|
' * Get effective podroll configuration: DB-stored settings override env var defaults',
|
||||||
|
' * @param {object} db - MongoDB database instance',
|
||||||
|
' * @param {object} podrollConfig - Plugin config from env vars',
|
||||||
|
' * @returns {Promise<object>} Effective episodesUrl, opmlUrl and opmlUpload',
|
||||||
|
' */',
|
||||||
|
'async function getEffectiveUrls(db, podrollConfig) {',
|
||||||
|
' let episodesUrl = podrollConfig?.episodesUrl || "";',
|
||||||
|
' let opmlUrl = podrollConfig?.opmlUrl || "";',
|
||||||
|
' let opmlUpload = podrollConfig?.opmlUpload || "";',
|
||||||
|
'',
|
||||||
|
' if (db) {',
|
||||||
|
' const settings = await db',
|
||||||
|
' .collection("podrollMeta")',
|
||||||
|
' .findOne({ key: "settings" });',
|
||||||
|
' if (settings) {',
|
||||||
|
' if (settings.episodesUrl) episodesUrl = settings.episodesUrl;',
|
||||||
|
' if (settings.opmlUrl) opmlUrl = settings.opmlUrl;',
|
||||||
|
' if (settings.opmlUpload) opmlUpload = settings.opmlUpload;',
|
||||||
|
' }',
|
||||||
|
' }',
|
||||||
|
'',
|
||||||
|
' return { episodesUrl, opmlUrl, opmlUpload };',
|
||||||
|
'}',
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
const controllerConfigOld = [
|
||||||
|
' config: {',
|
||||||
|
' episodesUrl: urls.episodesUrl,',
|
||||||
|
' opmlUrl: urls.opmlUrl,',
|
||||||
|
' syncInterval: application.podrollConfig?.syncInterval || 900000,',
|
||||||
|
' },',
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
const controllerConfigNew = [
|
||||||
|
' config: {',
|
||||||
|
' episodesUrl: urls.episodesUrl,',
|
||||||
|
' opmlUrl: urls.opmlUrl,',
|
||||||
|
' hasOpmlUpload: Boolean(urls.opmlUpload),',
|
||||||
|
' syncInterval: application.podrollConfig?.syncInterval || 900000,',
|
||||||
|
' },',
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
const controllerSaveOld = [
|
||||||
|
' const { episodesUrl, opmlUrl } = request.body;',
|
||||||
|
'',
|
||||||
|
' await db.collection("podrollMeta").updateOne(',
|
||||||
|
' { key: "settings" },',
|
||||||
|
' {',
|
||||||
|
' $set: {',
|
||||||
|
' key: "settings",',
|
||||||
|
' episodesUrl: episodesUrl || "",',
|
||||||
|
' opmlUrl: opmlUrl || "",',
|
||||||
|
' updatedAt: new Date().toISOString(),',
|
||||||
|
' },',
|
||||||
|
' },',
|
||||||
|
' { upsert: true },',
|
||||||
|
' );',
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
const controllerSaveNew = [
|
||||||
|
' const { episodesUrl, opmlUrl, clearOpmlFile } = request.body;',
|
||||||
|
'',
|
||||||
|
' const existingSettings = await db',
|
||||||
|
' .collection("podrollMeta")',
|
||||||
|
' .findOne({ key: "settings" });',
|
||||||
|
'',
|
||||||
|
' let opmlUpload = existingSettings?.opmlUpload || "";',
|
||||||
|
'',
|
||||||
|
' if (clearOpmlFile === "1") {',
|
||||||
|
' opmlUpload = "";',
|
||||||
|
' }',
|
||||||
|
'',
|
||||||
|
' const uploadedFileRaw = request.files?.opmlFile;',
|
||||||
|
' const uploadedFile = Array.isArray(uploadedFileRaw)',
|
||||||
|
' ? uploadedFileRaw[0]',
|
||||||
|
' : uploadedFileRaw;',
|
||||||
|
'',
|
||||||
|
' if (uploadedFile) {',
|
||||||
|
' const uploadedName = String(uploadedFile.name || "");',
|
||||||
|
' const uploadedType = String(uploadedFile.mimetype || "").toLowerCase();',
|
||||||
|
' const isXmlName = /\\.(opml|xml)$/i.test(uploadedName);',
|
||||||
|
' const isXmlType = uploadedType.includes("xml");',
|
||||||
|
'',
|
||||||
|
' if (!isXmlName && !isXmlType) {',
|
||||||
|
' throw new Error(request.__("podroll.opmlFileInvalidType"));',
|
||||||
|
' }',
|
||||||
|
'',
|
||||||
|
' if (uploadedFile.size > 1_048_576) {',
|
||||||
|
' throw new Error(request.__("podroll.opmlFileTooLarge"));',
|
||||||
|
' }',
|
||||||
|
'',
|
||||||
|
' const opmlText = Buffer.from(uploadedFile.data).toString("utf8").trim();',
|
||||||
|
'',
|
||||||
|
' if (!opmlText || !opmlText.toLowerCase().includes("<opml")) {',
|
||||||
|
' throw new Error(request.__("podroll.opmlFileInvalidContent"));',
|
||||||
|
' }',
|
||||||
|
'',
|
||||||
|
' opmlUpload = opmlText;',
|
||||||
|
' }',
|
||||||
|
'',
|
||||||
|
' await db.collection("podrollMeta").updateOne(',
|
||||||
|
' { key: "settings" },',
|
||||||
|
' {',
|
||||||
|
' $set: {',
|
||||||
|
' key: "settings",',
|
||||||
|
' episodesUrl: episodesUrl || "",',
|
||||||
|
' opmlUrl: opmlUrl || "",',
|
||||||
|
' opmlUpload,',
|
||||||
|
' updatedAt: new Date().toISOString(),',
|
||||||
|
' },',
|
||||||
|
' },',
|
||||||
|
' { upsert: true },',
|
||||||
|
' );',
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
const controllerSyncBlockOld = [
|
||||||
|
' const syncOptions = {',
|
||||||
|
' ...application.podrollConfig,',
|
||||||
|
' episodesUrl: urls.episodesUrl,',
|
||||||
|
' opmlUrl: urls.opmlUrl,',
|
||||||
|
' };',
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
const controllerSyncBlockNew = [
|
||||||
|
' const syncOptions = {',
|
||||||
|
' ...application.podrollConfig,',
|
||||||
|
' episodesUrl: urls.episodesUrl,',
|
||||||
|
' opmlUrl: urls.opmlUrl,',
|
||||||
|
' opmlUpload: urls.opmlUpload,',
|
||||||
|
' };',
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
const syncSourcesHeadOld = [
|
||||||
|
'async function syncSources(db, options) {',
|
||||||
|
' const { opmlUrl, fetchTimeout } = options;',
|
||||||
|
'',
|
||||||
|
' if (!opmlUrl) {',
|
||||||
|
' return { success: false, error: "No opmlUrl configured" };',
|
||||||
|
' }',
|
||||||
|
'',
|
||||||
|
' try {',
|
||||||
|
' console.log("[Podroll] Fetching OPML sources...");',
|
||||||
|
' const sources = await fetchOpmlSources(opmlUrl, fetchTimeout);',
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
const syncSourcesHeadNew = [
|
||||||
|
'async function syncSources(db, options) {',
|
||||||
|
' const { opmlUrl, opmlUpload, fetchTimeout } = options;',
|
||||||
|
'',
|
||||||
|
' const hasUploadedOpml = Boolean(opmlUpload && opmlUpload.trim());',
|
||||||
|
' const hasRemoteOpml = Boolean(opmlUrl);',
|
||||||
|
'',
|
||||||
|
' if (!hasUploadedOpml && !hasRemoteOpml) {',
|
||||||
|
' return { success: false, error: "No opml source configured" };',
|
||||||
|
' }',
|
||||||
|
'',
|
||||||
|
' try {',
|
||||||
|
' const opmlSource = hasUploadedOpml',
|
||||||
|
' ? `data:text/xml;charset=utf-8,${encodeURIComponent(opmlUpload)}`',
|
||||||
|
' : opmlUrl;',
|
||||||
|
'',
|
||||||
|
' if (hasUploadedOpml) {',
|
||||||
|
' console.log("[Podroll] Parsing uploaded OPML sources...");',
|
||||||
|
' } else {',
|
||||||
|
' console.log("[Podroll] Fetching OPML sources...");',
|
||||||
|
' }',
|
||||||
|
'',
|
||||||
|
' const sources = await fetchOpmlSources(opmlSource, fetchTimeout);',
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
const runSyncOld =
|
||||||
|
' options.opmlUrl ? syncSources(db, options) : { success: true, skipped: true },';
|
||||||
|
const runSyncNew =
|
||||||
|
' options.opmlUrl || options.opmlUpload ? syncSources(db, options) : { success: true, skipped: true },';
|
||||||
|
|
||||||
|
const effectiveOptionsOld = [
|
||||||
|
' return {',
|
||||||
|
' ...options,',
|
||||||
|
' episodesUrl: settings.episodesUrl || options.episodesUrl,',
|
||||||
|
' opmlUrl: settings.opmlUrl || options.opmlUrl,',
|
||||||
|
' };',
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
const effectiveOptionsNew = [
|
||||||
|
' return {',
|
||||||
|
' ...options,',
|
||||||
|
' episodesUrl: settings.episodesUrl || options.episodesUrl,',
|
||||||
|
' opmlUrl: settings.opmlUrl || options.opmlUrl,',
|
||||||
|
' opmlUpload: settings.opmlUpload || options.opmlUpload,',
|
||||||
|
' };',
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
const indexDefaultsOld = ' opmlUrl: "",';
|
||||||
|
const indexDefaultsNew = ' opmlUrl: "",\n opmlUpload: "",';
|
||||||
|
|
||||||
|
const localeDefaults = {
|
||||||
|
opmlFile: "Upload OPML file",
|
||||||
|
opmlFileHelp:
|
||||||
|
"Upload an OPML file to sync podcast subscriptions without a remote OPML URL",
|
||||||
|
opmlFileActive:
|
||||||
|
"An uploaded OPML file is currently saved and will be used during source sync",
|
||||||
|
clearOpmlFile: "Remove saved uploaded OPML file",
|
||||||
|
opmlFileInvalidType: "Please upload an .opml or .xml file",
|
||||||
|
opmlFileInvalidContent: "Uploaded file is not valid OPML content",
|
||||||
|
opmlFileTooLarge: "Uploaded OPML file is too large (max 1 MB)",
|
||||||
|
};
|
||||||
|
|
||||||
|
const deLocaleOverrides = {
|
||||||
|
opmlFile: "OPML-Datei hochladen",
|
||||||
|
opmlFileHelp:
|
||||||
|
"Laden Sie eine OPML-Datei hoch, um Podcast-Abonnements ohne externe OPML-URL zu synchronisieren",
|
||||||
|
opmlFileActive:
|
||||||
|
"Eine hochgeladene OPML-Datei ist gespeichert und wird bei der Quellen-Synchronisierung verwendet",
|
||||||
|
clearOpmlFile: "Gespeicherte hochgeladene OPML-Datei entfernen",
|
||||||
|
opmlFileInvalidType: "Bitte eine .opml- oder .xml-Datei hochladen",
|
||||||
|
opmlFileInvalidContent: "Die hochgeladene Datei enthaelt keinen gueltigen OPML-Inhalt",
|
||||||
|
opmlFileTooLarge: "Die hochgeladene OPML-Datei ist zu gross (max. 1 MB)",
|
||||||
|
};
|
||||||
|
|
||||||
|
async function exists(filePath) {
|
||||||
|
try {
|
||||||
|
await access(filePath);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceOnce(source, from, to) {
|
||||||
|
if (source.includes(to)) {
|
||||||
|
return { updated: source, changed: false, status: "already" };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!source.includes(from)) {
|
||||||
|
return { updated: source, changed: false, status: "missing" };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
updated: source.replace(from, to),
|
||||||
|
changed: true,
|
||||||
|
status: "patched",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function patchLocale(source, overrides = {}) {
|
||||||
|
const parsed = JSON.parse(source);
|
||||||
|
const podroll =
|
||||||
|
parsed && parsed.podroll && typeof parsed.podroll === "object"
|
||||||
|
? parsed.podroll
|
||||||
|
: {};
|
||||||
|
|
||||||
|
let changed = false;
|
||||||
|
for (const [key, value] of Object.entries(localeDefaults)) {
|
||||||
|
if (!(key in podroll)) {
|
||||||
|
podroll[key] = value;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(overrides)) {
|
||||||
|
if (podroll[key] !== value) {
|
||||||
|
podroll[key] = value;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!changed) {
|
||||||
|
return { updated: source, changed: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed.podroll = podroll;
|
||||||
|
return { updated: `${JSON.stringify(parsed, null, 2)}\n`, changed: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
let endpointsChecked = 0;
|
||||||
|
let filesPatched = 0;
|
||||||
|
|
||||||
|
for (const endpointPath of endpointCandidates) {
|
||||||
|
if (!(await exists(endpointPath))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
endpointsChecked += 1;
|
||||||
|
|
||||||
|
const dashboardPath = path.join(endpointPath, "views", "dashboard.njk");
|
||||||
|
const controllerPath = path.join(
|
||||||
|
endpointPath,
|
||||||
|
"lib",
|
||||||
|
"controllers",
|
||||||
|
"dashboard.js",
|
||||||
|
);
|
||||||
|
const syncPath = path.join(endpointPath, "lib", "sync.js");
|
||||||
|
const indexPath = path.join(endpointPath, "index.js");
|
||||||
|
const enLocalePath = path.join(endpointPath, "locales", "en.json");
|
||||||
|
const deLocalePath = path.join(endpointPath, "locales", "de.json");
|
||||||
|
|
||||||
|
if (await exists(dashboardPath)) {
|
||||||
|
let source = await readFile(dashboardPath, "utf8");
|
||||||
|
let changed = false;
|
||||||
|
|
||||||
|
const formResult = replaceOnce(source, dashboardFormOld, dashboardFormNew);
|
||||||
|
source = formResult.updated;
|
||||||
|
changed = changed || formResult.changed;
|
||||||
|
|
||||||
|
const fieldResult = replaceOnce(source, dashboardOpmlFieldOld, dashboardOpmlFieldNew);
|
||||||
|
source = fieldResult.updated;
|
||||||
|
changed = changed || fieldResult.changed;
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
await writeFile(dashboardPath, source, "utf8");
|
||||||
|
filesPatched += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await exists(controllerPath)) {
|
||||||
|
let source = await readFile(controllerPath, "utf8");
|
||||||
|
let changed = false;
|
||||||
|
|
||||||
|
const effectiveResult = replaceOnce(
|
||||||
|
source,
|
||||||
|
controllerGetEffectiveOld,
|
||||||
|
controllerGetEffectiveNew,
|
||||||
|
);
|
||||||
|
source = effectiveResult.updated;
|
||||||
|
changed = changed || effectiveResult.changed;
|
||||||
|
|
||||||
|
const configResult = replaceOnce(source, controllerConfigOld, controllerConfigNew);
|
||||||
|
source = configResult.updated;
|
||||||
|
changed = changed || configResult.changed;
|
||||||
|
|
||||||
|
const saveResult = replaceOnce(source, controllerSaveOld, controllerSaveNew);
|
||||||
|
source = saveResult.updated;
|
||||||
|
changed = changed || saveResult.changed;
|
||||||
|
|
||||||
|
if (source.includes(controllerSyncBlockOld)) {
|
||||||
|
source = source.replaceAll(controllerSyncBlockOld, controllerSyncBlockNew);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
await writeFile(controllerPath, source, "utf8");
|
||||||
|
filesPatched += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await exists(syncPath)) {
|
||||||
|
let source = await readFile(syncPath, "utf8");
|
||||||
|
let changed = false;
|
||||||
|
|
||||||
|
const syncSourcesResult = replaceOnce(
|
||||||
|
source,
|
||||||
|
syncSourcesHeadOld,
|
||||||
|
syncSourcesHeadNew,
|
||||||
|
);
|
||||||
|
source = syncSourcesResult.updated;
|
||||||
|
changed = changed || syncSourcesResult.changed;
|
||||||
|
|
||||||
|
const runSyncResult = replaceOnce(source, runSyncOld, runSyncNew);
|
||||||
|
source = runSyncResult.updated;
|
||||||
|
changed = changed || runSyncResult.changed;
|
||||||
|
|
||||||
|
const effectiveOptsResult = replaceOnce(
|
||||||
|
source,
|
||||||
|
effectiveOptionsOld,
|
||||||
|
effectiveOptionsNew,
|
||||||
|
);
|
||||||
|
source = effectiveOptsResult.updated;
|
||||||
|
changed = changed || effectiveOptsResult.changed;
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
await writeFile(syncPath, source, "utf8");
|
||||||
|
filesPatched += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await exists(indexPath)) {
|
||||||
|
const source = await readFile(indexPath, "utf8");
|
||||||
|
const result = replaceOnce(source, indexDefaultsOld, indexDefaultsNew);
|
||||||
|
if (result.changed) {
|
||||||
|
await writeFile(indexPath, result.updated, "utf8");
|
||||||
|
filesPatched += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await exists(enLocalePath)) {
|
||||||
|
const source = await readFile(enLocalePath, "utf8");
|
||||||
|
const result = patchLocale(source);
|
||||||
|
if (result.changed) {
|
||||||
|
await writeFile(enLocalePath, result.updated, "utf8");
|
||||||
|
filesPatched += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await exists(deLocalePath)) {
|
||||||
|
const source = await readFile(deLocalePath, "utf8");
|
||||||
|
const result = patchLocale(source, deLocaleOverrides);
|
||||||
|
if (result.changed) {
|
||||||
|
await writeFile(deLocalePath, result.updated, "utf8");
|
||||||
|
filesPatched += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endpointsChecked === 0) {
|
||||||
|
console.log("[postinstall] No endpoint-podroll directories found");
|
||||||
|
} else if (filesPatched === 0) {
|
||||||
|
console.log("[postinstall] endpoint-podroll OPML upload patch already applied");
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
`[postinstall] Patched endpoint-podroll OPML upload in ${filesPatched} file(s)`,
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user