n8n/.github/scripts/validate-docs-links.js
2024-03-12 16:43:40 +01:00

91 lines
2.6 KiB
JavaScript

#!/usr/bin/env node
const packages = ['nodes-base', '@n8n/nodes-langchain'];
const concurrency = 20;
let exitCode = 0;
const debug = require('debug')('n8n');
const path = require('path');
const https = require('https');
const glob = require('glob');
const pLimit = require('p-limit');
const picocolors = require('picocolors');
const Lookup = require('cacheable-lookup').default;
const agent = new https.Agent({ keepAlive: true, keepAliveMsecs: 5000 });
new Lookup().install(agent);
const limiter = pLimit(concurrency);
const validateUrl = async (packageName, kind, type) =>
new Promise((resolve, reject) => {
const name = type.displayName;
const documentationUrl =
kind === 'credentials'
? type.documentationUrl
: type.codex?.resources?.primaryDocumentation?.[0]?.url;
if (!documentationUrl) resolve([name, null]);
const url = new URL(
/^https?:\/\//.test(documentationUrl)
? documentationUrl
: `https://docs.n8n.io/integrations/builtin/${kind}/${documentationUrl.toLowerCase()}/`,
);
https
.request(
{
hostname: url.hostname,
port: 443,
path: url.pathname,
method: 'HEAD',
agent,
},
(res) => {
debug(picocolors.green('✓'), packageName, kind, name);
resolve([name, res.statusCode]);
},
)
.on('error', (e) => {
debug(picocolors.red('✘'), packageName, kind, name);
reject(e);
})
.end();
});
const checkLinks = async (packageName, kind) => {
const baseDir = path.resolve(__dirname, '../../packages', packageName);
let types = require(path.join(baseDir, `dist/types/${kind}.json`));
if (kind === 'nodes')
types = types.filter(
({ codex, hidden }) => !!codex?.resources?.primaryDocumentation && !hidden,
);
debug(packageName, kind, types.length);
const statuses = await Promise.all(
types.map((type) =>
limiter(() => {
return validateUrl(packageName, kind, type);
}),
),
);
const missingDocs = [];
const invalidUrls = [];
for (const [name, statusCode] of statuses) {
if (statusCode === null) missingDocs.push(name);
if (statusCode !== 200) invalidUrls.push(name);
}
if (missingDocs.length)
console.log('Documentation URL missing in %s for %s', packageName, kind, missingDocs);
if (invalidUrls.length)
console.log('Documentation URL invalid in %s for %s', packageName, kind, invalidUrls);
if (missingDocs.length || invalidUrls.length) exitCode = 1;
};
(async () => {
for (const packageName of packages) {
await Promise.all([checkLinks(packageName, 'credentials'), checkLinks(packageName, 'nodes')]);
if (exitCode !== 0) process.exit(exitCode);
}
})();