master-zapier-plan-draft/vendor/zapier-platform/packages/schema/lib/utils/buildDocs.js
George Lambert b4150c8250 Milestone 0: import zappier billing, Verae middleware, and Zapier research
Compose-ready workspace: packages/zappier (rate card, portal, Stripe),
packages/verae-zapier-middleware (timestamp + NATS), packages/verae-zapier
(CLI app), vendor/zapier-platform, and research/zapier vendor corpus.

Gate 0 structure checks pass. Product code and research are not yet wired.
2026-09-09 02:37:36 -04:00

231 lines
6.1 KiB
JavaScript

'use strict';
const util = require('util');
const _ = require('lodash');
const toc = require('markdown-toc');
const packageJson = require('../../package.json');
const links = require('./links');
const NO_DESCRIPTION = '_No description given._';
const COMBOS = ['anyOf', 'allOf', 'oneOf'];
const { SKIP_KEY } = require('../constants');
const hiddenRefs = [];
const walkSchemas = (InitSchema, callback) => {
const recurse = (Schema, parents) => {
parents = parents || [];
callback(Schema, parents);
Schema.dependencies.forEach((childSchema) => {
const newParents = parents.concat([InitSchema]);
recurse(childSchema, newParents);
});
};
recurse(InitSchema);
};
const collectSchemas = (InitSchema) => {
const schemas = {};
walkSchemas(InitSchema, (Schema) => {
if (!_.get(Schema, 'schema.docAnnotation.hide')) {
schemas[Schema.id] = Schema;
} else {
hiddenRefs.push(Schema.id);
}
});
return schemas;
};
const BREAK_LENGTH = 96;
const prepQuote = (val) => val.replace('`', '');
const quote = (val, triple, indent = '') =>
// either ``` with optional indentation or `
triple && val.length > BREAK_LENGTH
? '```\n' +
val
.match(/[^\r\n]+/g)
.map((line) => indent + line)
.join('\n') +
'\n' +
indent +
'```'
: `\`${prepQuote(val)}\``;
const quoteOrNa = (val, triple = false, indent = '') =>
val ? quote(val, triple, indent) : '_n/a_';
const formatExample = (example) => {
const ex = _.isPlainObject(example) ? _.omit(example, SKIP_KEY) : example;
return `* ${quoteOrNa(
util.inspect(ex, { depth: null, breakLength: BREAK_LENGTH }),
true,
' ',
)}`.replace(/\s+\n/gm, '\n');
};
// Generate a display of the type (or link to a $ref).
const typeOrLink = (schema) => {
if (schema.type === 'array' && schema.items) {
return `${quoteOrNa(schema.type)}[${typeOrLink(schema.items)}]`;
}
if (schema.$ref) {
if (!hiddenRefs.includes(schema.$ref)) {
return `[${schema.$ref}](${links.anchor(schema.$ref)})`;
}
return;
}
for (let i = 0; i < COMBOS.length; i++) {
const key = COMBOS[i];
if (schema[key] && schema[key].length) {
return `${key}(${schema[key]
.map(typeOrLink)
.filter(Boolean)
.join(', ')})`;
}
}
if (schema.enum && schema.enum.length) {
return `${quoteOrNa(schema.type)} in (${schema.enum
.map(util.inspect)
.map(quoteOrNa)
.join(', ')})`;
}
return quoteOrNa(schema.type);
};
// Properly quote and display examples.
const makeExampleSection = (Schema) => {
const examples = Schema.schema.examples || [];
if (!examples.length) {
return '';
}
return `\
#### Examples
${examples.map(formatExample).join('\n')}
`;
};
// Properly quote and display anti-examples.
const makeAntiExampleSection = (Schema) => {
const antiExamples = Schema.schema.antiExamples || [];
if (!antiExamples.length) {
return '';
}
return `\
#### Anti-Examples
${antiExamples
.map(({ example, reason }) => {
const formattedAntiExample = formatExample(example);
// If block quote, newline and indent the reason.
// Otherwise, show the reason inline w/ the anti-example and separated by a dash.
return formattedAntiExample.endsWith('```')
? `${formattedAntiExample}\n _${reason}_`
: `${formattedAntiExample} - _${reason}_`;
})
.join('\n')}
`;
};
const processProperty = (key, property, propIsRequired) => {
let isRequired = propIsRequired ? '**yes**' : 'no';
if (_.get(property, 'docAnnotation.hide')) {
return '';
} else if (_.get(property, 'docAnnotation.required')) {
// can also support keys besides "required"
const annotation = property.docAnnotation.required;
if (annotation.type === 'replace') {
isRequired = annotation.value;
} else if (annotation.type === 'append') {
isRequired += annotation.value;
} else {
throw new Error(`unrecognized docAnnotation type: ${annotation.type}`);
}
}
return `${quoteOrNa(key)} | ${isRequired} | ${typeOrLink(property)} | ${
property.description || NO_DESCRIPTION
}`;
};
// Enumerate the properties as a table.
const makePropertiesSection = (Schema) => {
const properties =
Schema.schema.properties || Schema.schema.patternProperties || {};
if (!Object.keys(properties).length) {
return '';
}
const required = Schema.schema.required || [];
return `\
#### Properties
Key | Required | Type | Description
--- | -------- | ---- | -----------
${Object.keys(properties)
.map((key) => {
const property = properties[key];
return processProperty(key, property, required.includes(key));
})
.join('\n')}
`;
};
// Given a "root" schema, create some markdown.
const makeMarkdownSection = (Schema) => {
return `\
## ${Schema.id}
${Schema.schema.description || NO_DESCRIPTION}
#### Details
* **Type** - ${typeOrLink(Schema.schema)}${
Schema.schema.pattern
? `
* **Pattern** - ${quoteOrNa(Schema.schema.pattern)}`
: ''
}
* [**Source Code**](${links.makeCodeLink(Schema.id)})
${makePropertiesSection(Schema)}
${makeExampleSection(Schema)}
${makeAntiExampleSection(Schema)}
`.trim();
};
// Generate the final markdown.
const buildDocs = (InitSchema) => {
const schemas = collectSchemas(InitSchema);
const markdownSections = _.chain(schemas)
.values()
.sortBy('id')
.map(makeMarkdownSection)
.join('\n\n-----\n\n');
const docs = `\
<!-- {% raw %} -->
# \`zapier-platform-schema\` Generated Documentation
This is automatically generated by the \`npm run docs\` command in \`zapier-platform-schema\` version ${quoteOrNa(
packageJson.version,
)}.
To see the docs for a different version, switch to the corresponding version tag in the GitHub repository.
For example, to view docs for version 17.7.0, switch to the tag 'zapier-platform-schema@17.7.0'.
Alternatively, modify the URL directly: https://github.com/zapier/zapier-platform/blob/zapier-platform-schema@17.7.0/packages/schema/docs/build/schema.md, replacing 17.7.0 with your desired version.
-----
## Index
<!-- toc -->
-----
${markdownSections}
<!-- {% endraw %} -->
`.trim();
return toc.insert(docs, { maxdepth: 2, bullets: '*' });
};
module.exports = buildDocs;