mirror of
https://github.com/tauri-apps/tauri-docs.git
synced 2026-01-31 00:35:16 +01:00
Feat: Rebuild Configuration Generator (#1713)
* remove old config generator * initial config generator * Conditionally render properties * change to module type * set up options * more stuff * Add notes * update ts target * Big chunk o' changes * temporarily disable link validation * Cap headings to 6 * Fix frontmatter spacing * rework * commits * even more, yay * Fix validator formatting * Remove config 2 * type formatting styles * cleanup * Enable broken link detection * Cleanup tsconfig * remove type for object * Redo object properties heading * Update array formatting * update package metadata * Add note for contributors --------- Signed-off-by: Lorenzo Lewis <lorenzo_lewis@icloud.com>
This commit is contained in:
@@ -3,12 +3,9 @@ import starlight from '@astrojs/starlight';
|
||||
import { rehypeHeadingIds } from '@astrojs/markdown-remark';
|
||||
import rehypeAutolinkHeadings from 'rehype-autolink-headings';
|
||||
import locales from './locales.json';
|
||||
import configGenerator from './src/plugins/configGenerator';
|
||||
import starlightLinksValidator from 'starlight-links-validator';
|
||||
import starlightBlog from 'starlight-blog';
|
||||
|
||||
await configGenerator();
|
||||
|
||||
const authors = {
|
||||
nothingismagick: {
|
||||
name: 'Daniel Thompson-Yvetot',
|
||||
|
||||
@@ -12,9 +12,10 @@
|
||||
"dev": "astro dev",
|
||||
"format": "prettier -w --cache --plugin prettier-plugin-astro .",
|
||||
"build:reference": "pnpm --filter js-api-generator run build",
|
||||
"build:config": "pnpm --filter config-generator run build",
|
||||
"build:astro": "astro build",
|
||||
"build:i18n": "pnpm --filter docs-i18n-tracker run build",
|
||||
"build": "pnpm dev:setup && pnpm build:reference && pnpm build:astro && pnpm build:i18n",
|
||||
"build": "pnpm dev:setup && pnpm build:reference && pnpm build:config && pnpm build:astro && pnpm build:i18n",
|
||||
"preview": "astro preview"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
496
packages/config-generator/build.ts
Normal file
496
packages/config-generator/build.ts
Normal file
@@ -0,0 +1,496 @@
|
||||
import { JSONSchema7, JSONSchema7Definition, JSONSchema7TypeName } from 'json-schema';
|
||||
import { existsSync, writeFileSync } from 'node:fs';
|
||||
import { slug } from 'github-slugger';
|
||||
|
||||
const schemaFile = '../tauri/core/tauri-config-schema/schema.json';
|
||||
const outputFile = '../../src/content/docs/2/reference/config.md';
|
||||
|
||||
if (!existsSync(schemaFile)) {
|
||||
throw Error('Could not find the Tauri config schema. Is the Tauri submodule initialized?');
|
||||
}
|
||||
|
||||
let schema: JSONSchema7 = (await import(schemaFile)).default;
|
||||
|
||||
const output = ['---\n# NOTE: This file is auto-generated in packages/config-generator/build.ts\n# For corrections please edit https://github.com/tauri-apps/tauri/blob/dev/core/tauri-utils/src/config.rs directly\n\ntitle: Configuration\n---'];
|
||||
|
||||
output.push(
|
||||
...buildSchemaDefinition(schema, {
|
||||
headingLevel: 2,
|
||||
renderTitle: false,
|
||||
})
|
||||
);
|
||||
|
||||
writeFileSync(outputFile, output.join('\n\n'));
|
||||
|
||||
interface Options {
|
||||
headingLevel: number;
|
||||
renderTitle: boolean;
|
||||
leadWithType: boolean;
|
||||
}
|
||||
|
||||
function buildSchemaDefinition(
|
||||
schema: JSONSchema7Definition,
|
||||
passedOptions: Partial<Options> = {}
|
||||
): string[] {
|
||||
// Note: $id, $schema, and $comment are explicitly not rendered
|
||||
|
||||
// Assign default values for any missing options
|
||||
const opts = Object.assign(
|
||||
{
|
||||
headingLevel: 1,
|
||||
renderTitle: true,
|
||||
leadWithType: false,
|
||||
},
|
||||
passedOptions
|
||||
);
|
||||
|
||||
if (typeof schema === 'boolean') {
|
||||
return [`\`${schema}\``];
|
||||
}
|
||||
|
||||
const out: string[] = [];
|
||||
|
||||
out.push(...buildType(schema, opts));
|
||||
out.push(...buildExtendedItems(schema, opts));
|
||||
out.push(...buildConditionalSubschemas(schema, opts));
|
||||
out.push(...buildBooleanSubschemas(schema, opts));
|
||||
out.push(...buildMetadata(schema, opts));
|
||||
out.push(...buildProperties(schema, opts));
|
||||
out.push(...buildExtendedMetadata(schema, opts));
|
||||
out.push(...buildDefinitions(schema, opts));
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds: title, readOnly, writeOnly, description
|
||||
*
|
||||
* Also see: buildExtendedMetadata()
|
||||
*/
|
||||
function buildMetadata(schema: JSONSchema7Definition, opts: Options): string[] {
|
||||
if (typeof schema !== 'object') {
|
||||
return [];
|
||||
}
|
||||
|
||||
const out: string[] = [];
|
||||
|
||||
opts.renderTitle &&
|
||||
schema.title &&
|
||||
out.push(`${'#'.repeat(Math.min(opts.headingLevel, 6))} ${schema.title}`);
|
||||
|
||||
if (schema.readOnly || schema.writeOnly) {
|
||||
const line = [];
|
||||
schema.readOnly && line.push('Read only');
|
||||
schema.writeOnly && line.push('Write only');
|
||||
out.push(line.join(' & '));
|
||||
}
|
||||
|
||||
schema.description &&
|
||||
out.push(
|
||||
schema.description
|
||||
// Set headings to appropriate level
|
||||
.replaceAll(/#{1,6}(?=.+[\n\\n])/g, '#'.repeat(Math.min(opts.headingLevel + 1, 6)))
|
||||
// Fix improperly formatted heading links
|
||||
.replaceAll(/#{1,6}(?=[^\s#])/g, '#')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')
|
||||
// Fix for link at https://github.com/tauri-apps/tauri/blob/713f84db2b5bf17e4217053a229f9c11cbb22c74/core/tauri-config-schema/schema.json#L1863-L1864
|
||||
.replace('#SecurityConfig.devCsp', '#securityconfig')
|
||||
);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds: $ref, type, enum, const, items
|
||||
* Number validation: multipleOf, maximum, exclusiveMaximum, minimum, exclusiveMinimum
|
||||
* String validation: maxLength, minLength, pattern
|
||||
* Array validation: maxItems, minItems, uniqueItems
|
||||
* Object validation: maxProperties, minProperties
|
||||
* Semantic validation: format
|
||||
* Non-JSON data validation: contentMediaType, contentEncoding
|
||||
*/
|
||||
function buildType(schema: JSONSchema7Definition, opts: Options): string[] {
|
||||
if (typeof schema !== 'object') {
|
||||
return [];
|
||||
}
|
||||
|
||||
let line: string = '';
|
||||
|
||||
if (schema.type) {
|
||||
if (Array.isArray(schema.type)) {
|
||||
line += schema.type.map((value) => buildTypeName(value, schema, opts)).join(' | ');
|
||||
} else {
|
||||
line += buildTypeName(schema.type, schema, opts);
|
||||
}
|
||||
}
|
||||
|
||||
if (schema.$ref) {
|
||||
const reference = schema.$ref.split('/').pop();
|
||||
|
||||
if (!reference) {
|
||||
throw Error(`Invalid reference: ${schema.$ref}`);
|
||||
}
|
||||
|
||||
line += `[\`${reference}\`](#${slug(reference)})`;
|
||||
}
|
||||
|
||||
if (schema.const) {
|
||||
switch (typeof schema.const) {
|
||||
case 'string':
|
||||
line += `\`"${schema.const}"\``;
|
||||
break;
|
||||
default:
|
||||
line += `\`${schema.const}\``;
|
||||
}
|
||||
}
|
||||
|
||||
if (schema.enum) {
|
||||
const enumValues: string[] = [];
|
||||
schema.enum.forEach((value) => {
|
||||
switch (typeof value) {
|
||||
case 'string':
|
||||
enumValues.push(`\`"${value}"\``);
|
||||
break;
|
||||
default:
|
||||
enumValues.push(`\`${value}\``);
|
||||
}
|
||||
});
|
||||
line += enumValues.join(' | ');
|
||||
}
|
||||
|
||||
const validation = [];
|
||||
|
||||
// Number validation
|
||||
schema.multipleOf && validation.push(`multiple of \`${schema.multipleOf}\``);
|
||||
schema.maximum && validation.push(`maximum of \`${schema.maximum}\``);
|
||||
schema.exclusiveMaximum && validation.push(`exclusive maximum of \`${schema.exclusiveMaximum}\``);
|
||||
schema.minimum && validation.push(`minimum of \`${schema.minimum}\``);
|
||||
schema.exclusiveMinimum && validation.push(`exclusive minimum of \`${schema.exclusiveMinimum}\``);
|
||||
|
||||
// String validation
|
||||
schema.maxLength && validation.push(`maximum length of \`${schema.maxLength}\``);
|
||||
schema.minLength && validation.push(`minimum length of \`${schema.minLength}\``);
|
||||
schema.pattern && validation.push(`pattern of \`${schema.pattern}\``);
|
||||
|
||||
// Array validation
|
||||
schema.maxItems && validation.push(`maximum of \`${schema.maxItems}\` items`);
|
||||
schema.minItems && validation.push(`minimum of \`${schema.minItems}\` items`);
|
||||
schema.uniqueItems && validation.push(`each item must be unique`);
|
||||
|
||||
// Object validation
|
||||
schema.maxProperties && validation.push(`maximum of \`${schema.maxProperties}\` properties`);
|
||||
schema.minProperties && validation.push(`minimum of \`${schema.minProperties}\` properties`);
|
||||
|
||||
// Semantic validation
|
||||
schema.format && validation.push(`formatted as \`${schema.format}\``);
|
||||
|
||||
// Non-JSON data validation
|
||||
schema.contentMediaType &&
|
||||
validation.push(`content media type of \`${schema.contentMediaType}\``);
|
||||
schema.contentEncoding && validation.push(`content encoding of \`${schema.contentEncoding}\``);
|
||||
|
||||
if (validation.length > 0) {
|
||||
line += ' ' + validation.join(', ');
|
||||
}
|
||||
|
||||
return [line];
|
||||
|
||||
function buildTypeName(
|
||||
typeName: JSONSchema7TypeName,
|
||||
parentSchema: JSONSchema7Definition,
|
||||
opts: Options
|
||||
): string {
|
||||
// Rendering logic for enums and consts are handled separately
|
||||
if (typeof parentSchema === 'object' && (parentSchema.enum || parentSchema.const)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let line = '';
|
||||
switch (typeName) {
|
||||
case 'object':
|
||||
break;
|
||||
case 'array':
|
||||
if (typeof parentSchema !== 'object' || !parentSchema.items) {
|
||||
throw Error('Invalid array');
|
||||
}
|
||||
if (Array.isArray(parentSchema.items)) {
|
||||
line += parentSchema.items
|
||||
.map((value) => {
|
||||
const definition = buildSchemaDefinition(value, opts);
|
||||
if (definition.length > 1) {
|
||||
// Format additional information to be in parenthesis
|
||||
const [first, ...rest] = definition;
|
||||
return [first, ' (', rest.join(', '), ')'];
|
||||
} else {
|
||||
return definition.join();
|
||||
}
|
||||
})
|
||||
.join(' | ');
|
||||
} else {
|
||||
line += buildSchemaDefinition(parentSchema.items, opts);
|
||||
}
|
||||
line += '[]';
|
||||
break;
|
||||
default:
|
||||
line += '`' + typeName + '`';
|
||||
}
|
||||
return line;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds: additionalItems, contains
|
||||
*/
|
||||
function buildExtendedItems(schema: JSONSchema7Definition, opts: Options): string[] {
|
||||
if (typeof schema !== 'object') {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (schema.additionalItems) {
|
||||
throw Error('Not implemented');
|
||||
}
|
||||
if (schema.contains) {
|
||||
throw Error('Not implemented');
|
||||
}
|
||||
|
||||
const out: string[] = [];
|
||||
|
||||
schema.additionalItems && out.push(`additionalItems: ${JSON.stringify(schema.additionalItems)}`);
|
||||
schema.contains && out.push(`contains: ${JSON.stringify(schema.contains)}`);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds: required, properties, patternProperties, additionalProperties, dependencies, propertyNames
|
||||
*/
|
||||
function buildProperties(schema: JSONSchema7Definition, opts: Options): string[] {
|
||||
if (typeof schema !== 'object') {
|
||||
return [];
|
||||
}
|
||||
|
||||
const out: string[] = [];
|
||||
|
||||
if (schema.properties) {
|
||||
out.push(`**Object Properties**:`);
|
||||
|
||||
const properties = Object.entries(schema.properties)
|
||||
.filter(([key]) => key !== '$schema')
|
||||
.sort(([a], [b]) => a.localeCompare(b));
|
||||
|
||||
out.push(
|
||||
properties
|
||||
.map(
|
||||
([key]) =>
|
||||
`- ${key} ${schema.required && schema.required.includes(key) ? '(required)' : ''}`
|
||||
)
|
||||
.join('\n')
|
||||
);
|
||||
|
||||
properties.forEach(([key, value]) => {
|
||||
out.push(`${'#'.repeat(Math.min(6, opts.headingLevel + 1))} ${key}`);
|
||||
out.push(...buildSchemaDefinition(value, { ...opts, headingLevel: opts.headingLevel + 1 }));
|
||||
});
|
||||
}
|
||||
|
||||
schema.additionalProperties &&
|
||||
out.push(
|
||||
`**Allows additional properties**: ${buildSchemaDefinition(
|
||||
schema.additionalProperties,
|
||||
opts
|
||||
)}`
|
||||
);
|
||||
|
||||
if (schema.patternProperties || schema.dependencies || schema.propertyNames) {
|
||||
throw Error('Not implemented');
|
||||
}
|
||||
|
||||
schema.patternProperties &&
|
||||
out.push(`patternProperties: ${JSON.stringify(schema.patternProperties)}`);
|
||||
schema.dependencies && out.push(`dependencies: ${JSON.stringify(schema.dependencies)}`);
|
||||
schema.propertyNames && out.push(`propertyNames: ${JSON.stringify(schema.propertyNames)}`);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds: if, then, else
|
||||
*/
|
||||
function buildConditionalSubschemas(schema: JSONSchema7Definition, opts: Options): string[] {
|
||||
if (typeof schema !== 'object') {
|
||||
return [];
|
||||
}
|
||||
|
||||
const out: string[] = [];
|
||||
|
||||
if (schema.if || schema.then || schema.else) {
|
||||
throw Error('Not implemented');
|
||||
}
|
||||
|
||||
schema.if && out.push(`if: ${JSON.stringify(schema.if)}`);
|
||||
schema.then && out.push(`then: ${JSON.stringify(schema.then)}`);
|
||||
schema.else && out.push(`else: ${JSON.stringify(schema.else)}`);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds: allOf, anyOf, oneOf, not
|
||||
*/
|
||||
function buildBooleanSubschemas(schema: JSONSchema7Definition, opts: Options): string[] {
|
||||
if (typeof schema !== 'object') {
|
||||
return [];
|
||||
}
|
||||
|
||||
const out: string[] = [];
|
||||
|
||||
if (schema.allOf) {
|
||||
out.push(...buildXOf('All', schema.allOf, opts));
|
||||
}
|
||||
|
||||
if (schema.anyOf) {
|
||||
out.push(...buildXOf('Any', schema.anyOf, opts));
|
||||
}
|
||||
|
||||
if (schema.oneOf) {
|
||||
out.push(...buildXOf('One', schema.oneOf, opts));
|
||||
}
|
||||
|
||||
if (schema.not) {
|
||||
throw Error('Not implemented');
|
||||
}
|
||||
|
||||
schema.not && out.push(`not: ${JSON.stringify(schema.not)}`);
|
||||
|
||||
return out;
|
||||
|
||||
function buildXOf(
|
||||
label: 'One' | 'Any' | 'All',
|
||||
schemas: JSONSchema7Definition[],
|
||||
opts: Options
|
||||
): string[] {
|
||||
const definitions = schemas.map((value) =>
|
||||
buildSchemaDefinition(value, { ...opts, leadWithType: true })
|
||||
);
|
||||
|
||||
if (definitions.every((definition) => definition.length == 1)) {
|
||||
// Short definition, can be rendered on a single line
|
||||
return [definitions.join(' | ')];
|
||||
}
|
||||
|
||||
const out: string[] = [];
|
||||
|
||||
if (definitions.length > 1) {
|
||||
// Render as a list
|
||||
out.push(`**${label} of the following**:`);
|
||||
const list: string[] = [];
|
||||
|
||||
const additionalDefinitions: string[][] = [];
|
||||
|
||||
definitions.forEach((definition) => {
|
||||
if (definition[0].startsWith('`object`')) {
|
||||
// Is an object, need to render subdefinitions
|
||||
const [first] = definition;
|
||||
list.push(`- ${first}: Subdefinition ${additionalDefinitions.length + 1}`);
|
||||
additionalDefinitions.push(definition);
|
||||
} else {
|
||||
// Render inline in list
|
||||
list.push(`- ${definition.map((value) => value.replaceAll('\n', ' ')).join(' ')}`);
|
||||
}
|
||||
});
|
||||
|
||||
out.push(list.join('\n'));
|
||||
|
||||
if (additionalDefinitions.length > 0) {
|
||||
out.push(`${'#'.repeat(Math.min(6, opts.headingLevel))} Subdefinitions`);
|
||||
additionalDefinitions.forEach((definition, index) => {
|
||||
// Render heading
|
||||
out.push(`${'#'.repeat(Math.min(6, opts.headingLevel + 1))} Subdefinition ${index + 1}`);
|
||||
|
||||
// Render definition, giving additional heading indention as needed
|
||||
definition.forEach((line) => {
|
||||
out.push(
|
||||
line.replaceAll(/#{1,6}\s(?=.+)/g, '#'.repeat(Math.min(opts.headingLevel + 2, 6)))
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Render as a block
|
||||
// Mapping might need some fixing...
|
||||
out.push(...definitions.map((definition) => definition.join()));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds: default, examples
|
||||
*
|
||||
* https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-00#section-8.2.4
|
||||
* https://datatracker.ietf.org/doc/html/draft-handrews-json-schema-validation-01#section-9
|
||||
*/
|
||||
function buildExtendedMetadata(schema: JSONSchema7Definition, opts: Options): string[] {
|
||||
if (typeof schema !== 'object') {
|
||||
return [];
|
||||
}
|
||||
|
||||
const out: string[] = [];
|
||||
|
||||
if (schema.default) {
|
||||
if (typeof schema.default === 'string') {
|
||||
out.push(`**Default**: \`"${schema.default}"\``);
|
||||
} else if (typeof schema.default !== 'object') {
|
||||
// Render on single line
|
||||
out.push(`**Default**: \`${schema.default}\``);
|
||||
} else if (Object.keys(schema.default).length == 0) {
|
||||
// Empty object, render on a single line
|
||||
out.push(`**Default**: \`${JSON.stringify(schema.default)}\``);
|
||||
} else {
|
||||
// Object with properties, render in code block
|
||||
out.push(
|
||||
`\n\`\`\`json title='Default'\n${JSON.stringify(schema.default, null, '\t')}\n\`\`\`\n`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (schema.examples) {
|
||||
throw Error('Examples not implemented');
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds: $defs, definitions
|
||||
*
|
||||
* https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-00#section-8.2.4
|
||||
* https://datatracker.ietf.org/doc/html/draft-handrews-json-schema-validation-01#section-9
|
||||
*/
|
||||
function buildDefinitions(schema: JSONSchema7Definition, opts: Options): string[] {
|
||||
if (typeof schema !== 'object') {
|
||||
return [];
|
||||
}
|
||||
|
||||
const out: string[] = [];
|
||||
|
||||
// Combine definitions together
|
||||
// `definitions` was renamed to `$defs` in Draft 7 but still allowed in either place
|
||||
const definitions = { ...schema.$defs, ...schema.definitions };
|
||||
if (Object.keys(definitions).length > 0) {
|
||||
out.push(`${'#'.repeat(Math.min(opts.headingLevel, 6))} Definitions`);
|
||||
Object.entries(definitions)
|
||||
.sort(([a], [b]) => a.localeCompare(b))
|
||||
.forEach(([key, value]) => {
|
||||
out.push(`${'#'.repeat(Math.min(opts.headingLevel, 6) + 1)} ${key}`);
|
||||
out.push(
|
||||
...buildSchemaDefinition(value, {
|
||||
...opts,
|
||||
headingLevel: Math.min(opts.headingLevel, 6) + 2,
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
return out;
|
||||
}
|
||||
21
packages/config-generator/package.json
Normal file
21
packages/config-generator/package.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "config-generator",
|
||||
"version": "1.0.0",
|
||||
"private": "true",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsm ./build.ts"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.15",
|
||||
"@types/node": "^20.10.0",
|
||||
"github-slugger": "^2.0.0",
|
||||
"tsm": "^2.3.0",
|
||||
"typescript": "^5.3.2"
|
||||
}
|
||||
}
|
||||
8
packages/config-generator/tsconfig.json
Normal file
8
packages/config-generator/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2021",
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,8 @@
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"license": "MIT",
|
||||
"private": "true",
|
||||
"dependencies": {
|
||||
"github-slugger": "^2.0.0",
|
||||
"tsm": "^2.3.0",
|
||||
|
||||
1131
pnpm-lock.yaml
generated
1131
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,5 @@
|
||||
packages:
|
||||
- 'packages/i18n-tracker'
|
||||
- 'packages/js-api-generator'
|
||||
- 'packages/config-generator'
|
||||
- 'packages/tauri-typedoc-theme'
|
||||
|
||||
@@ -1,470 +0,0 @@
|
||||
import fs from 'node:fs';
|
||||
|
||||
export default async function configGenerator() {
|
||||
if (fs.existsSync('packages/tauri/tooling/api/node_modules')) {
|
||||
const schemaPath = 'packages/tauri/core/tauri-config-schema/schema.json';
|
||||
const schemaString = fs
|
||||
.readFileSync(schemaPath)
|
||||
.toString()
|
||||
// Fixes any angle brackets that aren't escaped propertly
|
||||
.replaceAll('(?<!\\)<', '<')
|
||||
.replaceAll('(?!\\)>', '>');
|
||||
const schema = JSON.parse(schemaString);
|
||||
const targetPath = 'src/content/docs/2/reference/config.md';
|
||||
const nullMarkdown = '_null_';
|
||||
|
||||
const header = `---
|
||||
title: Tauri Configuration
|
||||
---
|
||||
|
||||
`;
|
||||
|
||||
const builtDefinitions = [];
|
||||
|
||||
function buildObject(key, value, headerLevel) {
|
||||
let out = [];
|
||||
headerLevel = Math.min(headerLevel, 6);
|
||||
|
||||
var headerTitle = value.title ? 'Configuration' : key;
|
||||
|
||||
var header = `${'#'.repeat(headerLevel)} ${headerTitle}\n`;
|
||||
|
||||
if (headerLevel === 1) {
|
||||
headerLevel = 2;
|
||||
}
|
||||
|
||||
out.push(header);
|
||||
out.push(`${descriptionConstructor(value.description)}\n`);
|
||||
|
||||
out.push(`${longFormTypeConstructor(key, value, headerLevel)}\n`);
|
||||
out = out.concat(buildProperties(headerTitle, value, headerLevel));
|
||||
|
||||
out = out.concat(inspectRef(value, headerLevel + 1));
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
function buildDef(name, headerLevel) {
|
||||
const def = name.replace('#/definitions/', '');
|
||||
if (!builtDefinitions.includes(def)) {
|
||||
builtDefinitions.push(def);
|
||||
const obj = schema.definitions[def];
|
||||
return buildObject(def, obj, headerLevel);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function inspectRef(object, headerLevel) {
|
||||
let out = [];
|
||||
|
||||
if (object.$ref) {
|
||||
out = out.concat(buildDef(object.$ref, headerLevel));
|
||||
}
|
||||
|
||||
if (object.additionalProperties && object.additionalProperties.$ref) {
|
||||
out = out.concat(buildDef(object.additionalProperties.$ref, headerLevel));
|
||||
}
|
||||
|
||||
if (object.items && object.items.$ref) {
|
||||
out = out.concat(buildDef(object.items.$ref, headerLevel));
|
||||
}
|
||||
|
||||
for (const opt of object.allOf || []) {
|
||||
out = out.concat(inspectRef(opt, headerLevel));
|
||||
}
|
||||
|
||||
for (const opt of object.anyOf || []) {
|
||||
out = out.concat(inspectRef(opt, headerLevel));
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
function buildProperties(parentName, object, headerLevel) {
|
||||
const out = [];
|
||||
if (!object.properties) return out;
|
||||
|
||||
const required = object.required || [];
|
||||
|
||||
// Build table header
|
||||
out.push('| Name | Type | Default | Description |');
|
||||
out.push('| ---- | ---- | ------- | ----------- |');
|
||||
|
||||
let definitions = [];
|
||||
|
||||
// Populate table
|
||||
Object.entries(object.properties).forEach(([key, value]) => {
|
||||
if (key == '$schema') return;
|
||||
|
||||
let propertyType = typeConstructor(value, true);
|
||||
let propertyDefault = defaultConstructor(value);
|
||||
|
||||
if (required.includes(key)) {
|
||||
propertyType += ' (required)';
|
||||
if (propertyDefault === nullMarkdown) {
|
||||
propertyDefault = '';
|
||||
}
|
||||
}
|
||||
|
||||
const url = `${parentName.toLowerCase()}.${key.toLowerCase()}`;
|
||||
const name = `<div className="anchor-with-padding" id="${url}">\`${key}\`<a class="hash-link" href="#${url}"></a></div>`;
|
||||
out.push(
|
||||
`| ${name} | ${propertyType} | ${propertyDefault} | ${descriptionConstructor(
|
||||
value.description,
|
||||
true
|
||||
)} |`
|
||||
);
|
||||
|
||||
definitions = definitions.concat(inspectRef(value, headerLevel + 1));
|
||||
});
|
||||
|
||||
out.push('\n');
|
||||
|
||||
return out.concat(definitions);
|
||||
}
|
||||
|
||||
function descriptionConstructor(description, fixNewlines = false) {
|
||||
if (!description) return;
|
||||
|
||||
// Remove links to current page
|
||||
description = description.replaceAll(
|
||||
/\n\nSee more: https:\/\/tauri\.app\/v[0-9]\/api\/config.*$/g,
|
||||
''
|
||||
);
|
||||
|
||||
// fix Rust doc style links
|
||||
description = description.replaceAll(/\[`Self::(\S+)`\]/g, '`$1`');
|
||||
|
||||
// Fix bullet points not being on a newline
|
||||
description = description.replaceAll(' - ', '\n- ');
|
||||
|
||||
// Parse any json code blocks
|
||||
if (description.includes('```json ')) {
|
||||
let newDescription = '';
|
||||
const s = description.split('```');
|
||||
|
||||
for (const text of s) {
|
||||
if (text.startsWith('json')) {
|
||||
const description = text.match(/([^{]+)/)[0];
|
||||
const json = JSON.stringify(JSON.parse(text.replace(description, '')), null, 2);
|
||||
newDescription += `${description}\n${json}\n`;
|
||||
} else {
|
||||
newDescription += text + '```';
|
||||
}
|
||||
}
|
||||
description = newDescription;
|
||||
}
|
||||
|
||||
const referenceStyleLinksRegex = /(\[[A-Za-z0-9 ]+\]): (.+)/g;
|
||||
const referenceStyleLinksMatches = referenceStyleLinksRegex.exec(description);
|
||||
if (referenceStyleLinksMatches) {
|
||||
let link = referenceStyleLinksMatches[2];
|
||||
// strip `<` and `>` from `<$url>`
|
||||
if (link.startsWith('<')) {
|
||||
link = link.substring(1, link.length - 1);
|
||||
}
|
||||
description = description
|
||||
.replace(referenceStyleLinksMatches[0], '')
|
||||
.replace(referenceStyleLinksMatches[1], `${referenceStyleLinksMatches[1]}(${link})`)
|
||||
.trim();
|
||||
}
|
||||
|
||||
// Fix any embedded new lines
|
||||
if (fixNewlines) {
|
||||
description = description.replaceAll('\n', '<br />');
|
||||
}
|
||||
|
||||
const markdownLinkRegex = /\[([^\[]+)\]\((.*)\)/gm;
|
||||
const markdownLinkMatches = markdownLinkRegex.exec(description);
|
||||
|
||||
if (markdownLinkMatches) {
|
||||
const url = markdownLinkMatches[2];
|
||||
if (!url.startsWith('http')) {
|
||||
description = description.replace(url, url.toLowerCase().replaceAll('_', ''));
|
||||
}
|
||||
}
|
||||
return description;
|
||||
}
|
||||
|
||||
function typeConstructor(object, describeObject = false) {
|
||||
const canBeNull =
|
||||
(object.type && object.type.includes('null')) ||
|
||||
(object.anyOf && object.anyOf.some((item) => item.type === 'null'));
|
||||
|
||||
if (object.$ref) {
|
||||
return refLinkConstructor(object.$ref, canBeNull);
|
||||
}
|
||||
|
||||
if (object.additionalProperties && object.additionalProperties.$ref) {
|
||||
return refLinkConstructor(object.additionalProperties.$ref, canBeNull);
|
||||
}
|
||||
|
||||
if (object.items && object.items.$ref) {
|
||||
return refLinkConstructor(object.items.$ref, canBeNull);
|
||||
}
|
||||
|
||||
if (object.anyOf) {
|
||||
// Removes any null values
|
||||
const items = object.anyOf.filter((item) => !(item.type && item.type == 'null'));
|
||||
|
||||
if (canBeNull && items.length == 1) {
|
||||
return `${items.map((t) => typeConstructor(t, describeObject))}?`;
|
||||
}
|
||||
|
||||
return items.map((t) => typeConstructor(t, describeObject)).join(' \\| ');
|
||||
}
|
||||
|
||||
if (object.allOf) {
|
||||
return refLinkConstructor(object.allOf[0].$ref);
|
||||
}
|
||||
|
||||
if (object.oneOf) {
|
||||
return object.oneOf.map((t) => typeConstructor(t, describeObject)).join(' | ');
|
||||
}
|
||||
|
||||
const m = describeObject ? '' : '`';
|
||||
|
||||
if (object.type) {
|
||||
var typeString = '';
|
||||
|
||||
// See what the type is
|
||||
switch (typeof object.type) {
|
||||
case 'string':
|
||||
// See if referencing a different type
|
||||
switch (object.type) {
|
||||
case 'string':
|
||||
typeString = object.enum
|
||||
? object.enum.map((e) => `"${e}"`).join(', ')
|
||||
: `${m}${object.type}${m}`;
|
||||
break;
|
||||
case 'number':
|
||||
case 'integer':
|
||||
case 'boolean':
|
||||
typeString = `${m}${object.type}${m}`;
|
||||
break;
|
||||
case 'object':
|
||||
if (describeObject && object.properties) {
|
||||
typeString = `${m}{`;
|
||||
const len = Object.keys(object.properties).length;
|
||||
let i = 0;
|
||||
for (const prop in object.properties) {
|
||||
typeString += ` "${prop}": ${typeConstructor(
|
||||
object.properties[prop],
|
||||
describeObject
|
||||
)}`;
|
||||
i++;
|
||||
if (i < len) typeString += ',';
|
||||
}
|
||||
typeString += ` }${m}`;
|
||||
} else {
|
||||
typeString = `${m}${object.type}${m}`;
|
||||
}
|
||||
break;
|
||||
case 'array':
|
||||
if (object.items) {
|
||||
if (describeObject) {
|
||||
typeString = `${typeConstructor(object.items, describeObject)}[]`;
|
||||
} else {
|
||||
const type = typeConstructor(object.items, true);
|
||||
const hasLink = type.includes('(#');
|
||||
typeString = hasLink
|
||||
? type.replace(/\[`(.*)`\]/, '[`$1[]`]')
|
||||
: `${m}${type}[]${m}`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'undefined':
|
||||
typeString = nullMarkdown;
|
||||
break;
|
||||
case 'object':
|
||||
if (Array.isArray(object.type)) {
|
||||
// Check if it should just be an optional value
|
||||
if (object.type.length == 2 && object.type.includes('null')) {
|
||||
typeString = `${m}${object.type.filter((item) => item != 'null')}${m}?`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
var additionalProperties = [];
|
||||
|
||||
if (object.format) {
|
||||
additionalProperties.push(`format: \`${object.format}\``);
|
||||
}
|
||||
|
||||
if (object.multipleOf) {
|
||||
additionalProperties.push(`multiple of: \`${object.multipleOf}\``);
|
||||
}
|
||||
|
||||
if (object.minimum) {
|
||||
additionalProperties.push(`minimum: \`${object.minimum}\``);
|
||||
}
|
||||
|
||||
if (object.exclusiveMinimum) {
|
||||
additionalProperties.push(`exclusive minimum: \`${object.exclusiveMinimum}\``);
|
||||
}
|
||||
|
||||
if (object.maximum) {
|
||||
additionalProperties.push(`maximum: \`${object.maximum}\``);
|
||||
}
|
||||
|
||||
if (object.exclusiveMaximum) {
|
||||
additionalProperties.push(`exclusive maximum: \`${object.exclusiveMaximum}\``);
|
||||
}
|
||||
|
||||
if (typeString != '') {
|
||||
if (additionalProperties.length > 0) {
|
||||
const props = `_(${additionalProperties.join(', ')})_`;
|
||||
return `${typeString} ${props}`;
|
||||
}
|
||||
return typeString;
|
||||
}
|
||||
}
|
||||
|
||||
if (object.enum) {
|
||||
return `${m}${object.enum.map((e) => `"${e}"`).join(', ')}${m}`;
|
||||
}
|
||||
|
||||
if (Array.isArray(object)) {
|
||||
if (describeObject) {
|
||||
const type = [];
|
||||
for (const obj of object) {
|
||||
type.push(typeConstructor(obj));
|
||||
}
|
||||
if (type.every((t) => t === type[0])) {
|
||||
return `${m}${type[0]}${m}`;
|
||||
}
|
||||
return `${m}[${type.join(', ')}]${m}`;
|
||||
} else {
|
||||
return `${m}array${m}`;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('A type was not able to be parsed:', object);
|
||||
return JSON.stringify(object);
|
||||
}
|
||||
|
||||
/** prepares a description to be added to a markdown bullet point list */
|
||||
function listDescription(description) {
|
||||
return description.replace('\n\n', '\n\n\t');
|
||||
}
|
||||
|
||||
function longFormTypeConstructor(key, object, headerLevel) {
|
||||
if (object.enum) {
|
||||
var buffer = [];
|
||||
buffer.push(`Can be any of the following \`${object.type}\` values:`);
|
||||
object.enum.forEach((item) => {
|
||||
buffer.push(`- ${item}`);
|
||||
});
|
||||
return buffer.join('\n');
|
||||
}
|
||||
if (object.anyOf) {
|
||||
var buffer = [];
|
||||
buffer.push('Can be any of the following types:\n');
|
||||
object.anyOf.forEach((item) => {
|
||||
var description = ':';
|
||||
if (item.description) {
|
||||
description = `: ${descriptionConstructor(item.description)}`;
|
||||
}
|
||||
const hasProperties = 'properties' in item;
|
||||
let typeDef = typeConstructor(item, hasProperties);
|
||||
if (hasProperties) {
|
||||
typeDef = '`' + typeDef + '`';
|
||||
}
|
||||
buffer.push(`- ${typeDef}${listDescription(description)}`);
|
||||
if (hasProperties) {
|
||||
buffer.push('\n\t');
|
||||
buffer.push(
|
||||
buildProperties(key, item, headerLevel)
|
||||
.map((line) => `\t${line}`)
|
||||
.join('\n')
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return buffer.join(`\n`);
|
||||
}
|
||||
|
||||
if (object.oneOf) {
|
||||
var buffer = [];
|
||||
buffer.push('Can be any **ONE** of the following types:\n');
|
||||
object.oneOf.forEach((item) => {
|
||||
var description = ':';
|
||||
if (item.description) {
|
||||
description = `: ${descriptionConstructor(item.description)}`;
|
||||
}
|
||||
const hasProperties = 'properties' in item;
|
||||
let typeDef = typeConstructor(item, hasProperties);
|
||||
if (hasProperties) {
|
||||
typeDef = '`' + typeDef + '`';
|
||||
}
|
||||
buffer.push(`- ${typeDef}${listDescription(description)}`);
|
||||
if ('properties' in item) {
|
||||
buffer.push('\n\t');
|
||||
buffer.push(
|
||||
buildProperties(key, item, headerLevel)
|
||||
.map((line) => `\t${line}`)
|
||||
.join('\n')
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return buffer.join(`\n`);
|
||||
}
|
||||
|
||||
return `Type: ${typeConstructor(object)}`;
|
||||
}
|
||||
|
||||
function defaultConstructor(object) {
|
||||
switch (typeof object.default) {
|
||||
case 'boolean':
|
||||
case 'number':
|
||||
return `\`${object.default}\``;
|
||||
case 'object':
|
||||
// Check if empty array
|
||||
if (Array.isArray(object.default) && object.default.length == 0) {
|
||||
return '[]';
|
||||
}
|
||||
default:
|
||||
}
|
||||
|
||||
if (object.$ref) {
|
||||
console.error('Found $ref default:', object.$ref);
|
||||
}
|
||||
|
||||
if (object.anyOf) {
|
||||
const link = object.anyOf[0].$ref.replace('#/definitions/', '').toLowerCase();
|
||||
return `[view](#${link})`;
|
||||
}
|
||||
|
||||
if (object.allOf) {
|
||||
const link = object.allOf[0].$ref.replace('#/definitions/', '').toLowerCase();
|
||||
return `[view](#${link})`;
|
||||
}
|
||||
|
||||
if (object.oneOf) {
|
||||
console.error('Found oneOf default:', object.oneOf);
|
||||
}
|
||||
|
||||
return nullMarkdown;
|
||||
}
|
||||
|
||||
function refLinkConstructor(string, nullable = false) {
|
||||
const name = string.replace('#/definitions/', '');
|
||||
return `[\`${name}\`](#${name.toLowerCase()})${nullable ? '?' : ''}`;
|
||||
}
|
||||
|
||||
const config = buildObject(null, schema, 1).join('\n');
|
||||
fs.writeFileSync(targetPath, `${header}${config}`);
|
||||
} else {
|
||||
console.log(
|
||||
'Tauri submodule is not initialized, respective core config routes will not be rendered.'
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user