From 4e3b366e640e3b75cc96e8e3f7da270b7fd63b8d Mon Sep 17 00:00:00 2001 From: Lucas Fernandes Nogueira Date: Sun, 14 Nov 2021 09:26:41 -0300 Subject: [PATCH] feat(ci): automate `config.md` docs generation (#2887) --- .changes/config.json | 2 +- .scripts/covector/generate-config-doc.js | 114 +++++++++++++++++++++++ docs/.templates/config.md | 103 ++++++++++++++++++++ tooling/cli.rs/config_definition.rs | 8 +- tooling/cli.rs/schema.json | 15 ++- 5 files changed, 233 insertions(+), 9 deletions(-) create mode 100644 .scripts/covector/generate-config-doc.js create mode 100644 docs/.templates/config.md diff --git a/.changes/config.json b/.changes/config.json index 1cff960aa..77134069f 100644 --- a/.changes/config.json +++ b/.changes/config.json @@ -269,7 +269,7 @@ "path": "./tooling/cli.rs", "manager": "rust", "dependencies": ["tauri-bundler"], - "postversion": "node ../../.scripts/covector/generate-cli-doc.js && cargo check" + "postversion": "node ../../.scripts/covector/generate-cli-doc.js && node ../../.scripts/covector/generate-config-doc.js && cargo check" }, "create-tauri-app": { "path": "./tooling/create-tauri-app", diff --git a/.scripts/covector/generate-config-doc.js b/.scripts/covector/generate-config-doc.js new file mode 100644 index 000000000..2264b8043 --- /dev/null +++ b/.scripts/covector/generate-config-doc.js @@ -0,0 +1,114 @@ +const fs = require('fs') +const path = require('path') +const schema = JSON.parse(fs.readFileSync('tooling/cli.rs/schema.json').toString()) +const templatePath = path.join(__dirname, '../../docs/.templates/config.md') +const targetPath = path.join(__dirname, '../../docs/api/config.md') +const template = fs.readFileSync(templatePath, 'utf8') + +function formatDescription(description) { + return description ? + description + .replace(/`/g, '\\`') + .replace(/\n/g, ' ') + .replace(/ /g, ' ') + .replace(/{/g, '\\{') + .replace(/}/g, '\\}') : + '' +} + +function generatePropertiesEl(schema, anchorRoot, definition, tab) { + const previousTabLevel = tab.replace(' ', '') + const fields = [`anchorRoot="${anchorRoot}"`] + + if (definition.additionalProperties) { + fields.push(`type="${definition.type}"`) + fields.push(`description="${formatDescription(definition.description)}"`) + } + + const rows = [] + for (const propertyName in definition.properties) { + const property = definition.properties[propertyName] + if ('type' in property) { + let type + if ('items' in property) { + if (property.items.type) { + type = `${property.items.type}[]` + } else { + const typeName = property.items.$ref.replace('#/definitions/', '') + const propDefinition = schema.definitions[typeName] + const propertyEl = generatePropertiesEl(schema, `${anchorRoot}.${propertyName}`, propDefinition, `${tab} `) + rows.push({ + property: propertyName, + optional: ('default' in property) || property.type.includes('null'), + type: `${typeName}[]`, + description: property.description, + child: `\n${tab}${propertyEl}\n${previousTabLevel}` + }) + continue + } + } else if (Array.isArray(property.type)) { + type = property.type.join(' | ') + } else { + type = property.type + } + rows.push({ + property: propertyName, + optional: true, + type, + description: property.description, + default: property.default + }) + } else if ('anyOf' in property) { + const subType = property.anyOf[0].$ref.replace('#/definitions/', '') + const propDefinition = schema.definitions[subType] + const propertyEl = generatePropertiesEl(schema, `${anchorRoot}.${propertyName}`, propDefinition, `${tab} `) + rows.push({ + property: propertyName, + optional: property.anyOf.length > 1 && property.anyOf[1].type === 'null', + type: subType, + description: property.description, + child: propertyEl + }) + } else if ('allOf' in property) { + const subType = property.allOf[0].$ref.replace('#/definitions/', '') + const propDefinition = schema.definitions[subType] + const propertyEl = propDefinition.properties ? generatePropertiesEl(schema, `${anchorRoot}.${propertyName}`, propDefinition, `${tab} `) : undefined + rows.push({ + property: propertyName, + optional: 'default' in property, + type: property.type || subType, + description: property.description, + child: propertyEl + }) + } + } + + if (rows.length > 0) { + const serializedRows = rows + .map(row => { + const fields = [`property: "${row.property}"`, `optional: ${row.optional}`, `type: "${row.type}"`, `description: \`${formatDescription(row.description)}\``] + if (row.child) { + fields.push(`child: ${row.child}`) + } + return `{ ${fields.join(', ')} },` + }) + .join(`\n${tab}`) + fields.push(`rows={[\n${tab}${serializedRows}\n${previousTabLevel}]}`) + } else { + fields.push('rows={[]}') + } + + return `` +} + +const output = [] + +for (const propertyName in schema.properties) { + const property = schema.properties[propertyName] + const definitionName = property.allOf[0].$ref.replace('#/definitions/', '') + const definition = schema.definitions[definitionName] + let contents = `## \`${propertyName}\`\n\n${generatePropertiesEl(schema, propertyName, definition, ' ')}` + output.push(contents) +} + +fs.writeFileSync(targetPath, template.replace('{properties}', output.join('\n\n'))) diff --git a/docs/.templates/config.md b/docs/.templates/config.md new file mode 100644 index 000000000..3d9d2391c --- /dev/null +++ b/docs/.templates/config.md @@ -0,0 +1,103 @@ +--- +title: Configuration +--- + +import Properties from '@theme/Properties' +import Array from '@theme/Array' +import Alert from '@theme/Alert' + +The `tauri.conf.json` is a file generated by the `tauri init` command (see here) that lives in your Tauri application source directory (src-tauri). + +Once generated, you may modify it at will to customize your Tauri application. + +# Platform-specific configuration + +In addition to the JSON defined on the `tauri.conf.json` file, Tauri reads a platform-specific configuration on `tauri.linux.conf.json`, `tauri.windows.conf.json` and `tauri.macos.conf.json` and merges it with the main `tauri.conf.json` configuration. + +# Configuration structure + +`tauri.conf.json` is composed of the following properties: + +{properties} + + +
+ + +Instead of launching the app directly, we configure the bundled app to run a script that tries to expose the environment variables to the app; without that you'll have trouble using system CLI apps like Node.js. + + +```js title=Example +"tauri": { + "cli": { + "description": "Tauri communication example", + "longDescription": null, + "beforeHelp": null, + "afterHelp": null, + "args": [{ + "short": "c", + "name": "config", + "takesValue": true, + "description": "Config path" + }, { + "short": "t", + "name": "theme", + "takesValue": true, + "description": "App theme", + "possibleValues": ["light", "dark", "system"] + }, { + "short": "v", + "name": "verbose", + "multipleOccurrences": true, + "description": "Verbosity level" + }], + "subcommands": { + "update": { + "description": "Updates the app", + "longDescription": null, + "beforeHelp": null, + "afterHelp": null, + "args": [{ + "short": "b", + "name": "background", + "description": "Update in background" + }], + "subcommands": null + } + } + }, + "bundle": { + "active": true, + "targets": ["deb"], + "identifier": "com.tauri.dev", + "icon": ["icons/32x32.png", "icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico"], + "resources": [], + "externalBin": [], + "copyright": "", + "category": "DeveloperTool", + "shortDescription": "", + "longDescription": "", + "deb": { + "depends": [] + }, + "macOS": { + "frameworks": [], + "minimumSystemVersion": "", + "exceptionDomain": "" + } + }, + "allowlist": { + "all": true + }, + "windows": [{ + "title": "Tauri App", + "width": 800, + "height": 600, + "resizable": true, + "fullscreen": false + }], + "security": { + "csp": "default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self'" + } +} +``` diff --git a/tooling/cli.rs/config_definition.rs b/tooling/cli.rs/config_definition.rs index 54ed08a6c..48648f655 100644 --- a/tooling/cli.rs/config_definition.rs +++ b/tooling/cli.rs/config_definition.rs @@ -841,8 +841,6 @@ fn default_dist_dir() -> AppUrl { AppUrl::Url("../dist".to_string()) } -type JsonObject = HashMap; - /// The tauri.conf.json mapper. #[skip_serializing_none] #[derive(Debug, PartialEq, Clone, Deserialize, Serialize, JsonSchema)] @@ -859,9 +857,13 @@ pub struct Config { pub build: BuildConfig, /// The plugins config. #[serde(default)] - pub plugins: HashMap, + pub plugins: PluginConfig, } +/// The plugin configs holds a HashMap mapping a plugin name to its configuration object. +#[derive(Debug, Clone, Default, PartialEq, Deserialize, Serialize, JsonSchema)] +pub struct PluginConfig(pub HashMap); + fn default_build() -> BuildConfig { BuildConfig { runner: None, diff --git a/tooling/cli.rs/schema.json b/tooling/cli.rs/schema.json index 4210d79be..6d1a4f33c 100644 --- a/tooling/cli.rs/schema.json +++ b/tooling/cli.rs/schema.json @@ -29,11 +29,11 @@ "plugins": { "description": "The plugins config.", "default": {}, - "type": "object", - "additionalProperties": { - "type": "object", - "additionalProperties": true - } + "allOf": [ + { + "$ref": "#/definitions/PluginConfig" + } + ] }, "tauri": { "description": "The Tauri configuration.", @@ -954,6 +954,11 @@ }, "additionalProperties": false }, + "PluginConfig": { + "description": "The plugin configs holds a HashMap mapping a plugin name to its configuration object.", + "type": "object", + "additionalProperties": true + }, "SecurityConfig": { "type": "object", "properties": {