Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s
114 lines
4.6 KiB
JavaScript
114 lines
4.6 KiB
JavaScript
#!/usr/bin/env node
|
|
// Generates docs/site/data/plugin-jsdocs.json, the data source consumed by the <PluginJsDocs />
|
|
// component that renders the web app plugin SDK reference
|
|
// (docs/develop/integrate/reference/webapp/index.md).
|
|
//
|
|
// Reads webapp/channels/src/plugins/registry.ts directly from this monorepo instead of fetching it
|
|
// from GitHub over HTTP.
|
|
//
|
|
// Usage: node scripts/gen-plugin-jsdocs.mjs (from docs/site/)
|
|
|
|
import {parse} from '@typescript-eslint/typescript-estree';
|
|
import {readFileSync, writeFileSync, mkdirSync} from 'node:fs';
|
|
import {resolve, dirname} from 'node:path';
|
|
import {fileURLToPath} from 'node:url';
|
|
|
|
const HERE = dirname(fileURLToPath(import.meta.url)); // docs/site/scripts
|
|
const SITE_ROOT = resolve(HERE, '..'); // docs/site
|
|
const REPO_ROOT = resolve(SITE_ROOT, '../..'); // mattermost/
|
|
const REGISTRY_PATH = resolve(REPO_ROOT, 'webapp/channels/src/plugins/registry.ts');
|
|
const OUT_PATH = resolve(SITE_ROOT, 'data/plugin-jsdocs.json');
|
|
|
|
function paramNamesFromPattern(pattern) {
|
|
if (pattern.type === 'Identifier') return [pattern.name];
|
|
if (pattern.type === 'ObjectPattern') {
|
|
return pattern.properties.flatMap((prop) => (prop.type === 'Property' ? paramNamesFromPattern(prop.value) : paramNamesFromPattern(prop)));
|
|
}
|
|
if (pattern.type === 'ArrayPattern') {
|
|
return pattern.elements.flatMap((el) => (el ? paramNamesFromPattern(el) : []));
|
|
}
|
|
if (pattern.type === 'AssignmentPattern') return paramNamesFromPattern(pattern.left);
|
|
if (pattern.type === 'RestElement') return paramNamesFromPattern(pattern.argument);
|
|
return [];
|
|
}
|
|
|
|
// Comments attached to `node`: walk backward from its start, collecting comments as long as only
|
|
// whitespace separates them from each other and from the node. Node-scoped, so unlike a global
|
|
// "which comments sit on consecutive lines" pass, it can't attribute a comment to the wrong member.
|
|
function leadingComments(sourceText, comments, node) {
|
|
const attached = [];
|
|
let cursor = node.range[0];
|
|
for (let i = comments.length - 1; i >= 0; i--) {
|
|
const comment = comments[i];
|
|
if (comment.range[1] > cursor) continue;
|
|
if (!/^\s*$/.test(sourceText.slice(comment.range[1], cursor))) break;
|
|
attached.unshift(comment);
|
|
cursor = comment.range[0];
|
|
}
|
|
return attached.flatMap((comment) =>
|
|
comment.value
|
|
.split('\n')
|
|
.map((line) => line.replace(/^\s*\*\s?/, '').trimEnd())
|
|
.filter((line) => line.length > 0),
|
|
);
|
|
}
|
|
|
|
// reArg(['name', ...], handler) documents its public parameter names explicitly in that array —
|
|
// that's the contract callers see, so prefer it over inferring names from the handler's own
|
|
// (possibly renamed or destructured) parameters.
|
|
function reArgParameterNames(callExpr) {
|
|
const [firstArg, ...rest] = callExpr.arguments;
|
|
if (callExpr.callee.type === 'Identifier' && callExpr.callee.name === 'reArg' && firstArg?.type === 'ArrayExpression') {
|
|
return firstArg.elements.filter((el) => el?.type === 'Literal' && typeof el.value === 'string').map((el) => el.value);
|
|
}
|
|
const handler = rest.find((arg) => arg.type === 'ArrowFunctionExpression' || arg.type === 'FunctionExpression');
|
|
return handler ? handler.params.flatMap(paramNamesFromPattern) : [];
|
|
}
|
|
|
|
function findPluginRegistryClass(program) {
|
|
return program.body.find(
|
|
(statement) =>
|
|
statement.type === 'ExportDefaultDeclaration' &&
|
|
statement.declaration.type === 'ClassDeclaration' &&
|
|
statement.declaration.id?.name === 'PluginRegistry',
|
|
)?.declaration;
|
|
}
|
|
|
|
function main() {
|
|
const sourceText = readFileSync(REGISTRY_PATH, 'utf8');
|
|
const ast = parse(sourceText, {comment: true, range: true});
|
|
|
|
const classDecl = findPluginRegistryClass(ast);
|
|
if (!classDecl) {
|
|
throw new Error(`Could not find "export default class PluginRegistry" in ${REGISTRY_PATH}`);
|
|
}
|
|
|
|
const methods = [];
|
|
for (const member of classDecl.body.body) {
|
|
if (member.key?.type !== 'Identifier') continue;
|
|
|
|
let params;
|
|
if (member.type === 'MethodDefinition' && member.kind !== 'constructor') {
|
|
params = member.value.params.flatMap(paramNamesFromPattern);
|
|
} else if (member.type === 'PropertyDefinition' && member.value?.type === 'CallExpression') {
|
|
params = reArgParameterNames(member.value);
|
|
} else {
|
|
continue;
|
|
}
|
|
|
|
methods.push({
|
|
Name: member.key.name,
|
|
Parameters: params,
|
|
Comments: leadingComments(sourceText, ast.comments, member),
|
|
});
|
|
}
|
|
|
|
const output = {Interface: {Methods: methods}};
|
|
|
|
mkdirSync(dirname(OUT_PATH), {recursive: true});
|
|
writeFileSync(OUT_PATH, JSON.stringify(output, null, 2));
|
|
console.log(`[plugin-jsdocs] wrote ${methods.length} methods to ${OUT_PATH}`);
|
|
}
|
|
|
|
main();
|