diff --git a/.gitattributes b/.gitattributes index e03e86ab..a92059ee 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,3 @@ * text=auto eol=lf pnpm-lock.yaml linguist-generated +docs/config.md linguist-generated diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 0a8a061f..77cca1d0 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -1,8 +1,13 @@ import { defineConfig } from 'vitepress'; +import { generateConfigDocs } from './plugins/generate-config-docs'; // https://vitepress.dev/reference/site-config export default defineConfig({ title: 'WXT', + vite: { + clearScreen: false, + plugins: [generateConfigDocs()], + }, description: 'Next gen framework for developing web extensions', lastUpdated: true, diff --git a/docs/.vitepress/plugins/generate-config-docs.ts b/docs/.vitepress/plugins/generate-config-docs.ts new file mode 100644 index 00000000..58d061ec --- /dev/null +++ b/docs/.vitepress/plugins/generate-config-docs.ts @@ -0,0 +1,148 @@ +import { resolve } from 'node:path'; +import { Project, ts, Type, Node, JSDocableNode } from 'ts-morph'; +import Ora from 'ora'; +import { readFileSync, writeFileSync } from 'node:fs'; + +const externalTypesPath = resolve('src/core/types/external.ts'); +const configTemplatePath = resolve('docs/config.tpl.md'); +const configPath = resolve('docs/config.md'); + +const PREFACE = ``; + +/** + * Custom property paths that should not be recursively inspected. Usually 3rd party types. + */ +const LEAF_PATHS = ['imports', 'vite', 'server']; + +/** + * Override any types that resolve to `import(...)` instead of their type names when calling + * `type.getText()` + */ +const CUSTOM_TYPES = { + manifest: + 'Manifest | Promise | () => Manifest | () => Promise', +}; + +export function generateConfigDocs() { + const generateDocs = () => { + const spinner = Ora('Generating /config.md').start(); + try { + const project = new Project({ + tsConfigFilePath: resolve('tsconfig.json'), + }); + + // Load file containing "UserConfig" + const externalTypesFile = project.addSourceFileAtPath(externalTypesPath); + project.resolveSourceFileDependencies(); + + const typeChecker = project.getProgram().getTypeChecker(); + + const inlineConfigInterface = + externalTypesFile.getInterfaceOrThrow('InlineConfig'); + + const getDocsFor = ( + path: string[], + node: Node, + depth = 0, + ): string[] => { + if (depth > 3) throw Error('Recursion to deep for ' + path.join('.')); + + const pathStr = path.join('.'); + + let type: Type; + if (node.isKind(ts.SyntaxKind.InterfaceDeclaration)) { + type = node.getType(); + } else if (node.isKind(ts.SyntaxKind.PropertySignature)) { + type = node.getTypeNodeOrThrow()?.getType(); + } else if (node.isKind(ts.SyntaxKind.MethodSignature)) { + type = node.getType(); + } else { + throw Error('Unsupported type node: ' + node.getKindName()); + } + + if (type.isObject() && !type.isArray()) { + return ( + type + .getProperties() + // .sort((l, r) => l.getName().localeCompare(r.getName())) + .flatMap((property) => { + const childPath = [...path, property.getName()]; + if (LEAF_PATHS.includes(childPath.join('.'))) return []; + + return getDocsFor( + childPath, + property.getDeclarations()[0], + depth + 1, + ); + }) + ); + } + + if ('getJsDocs' in node) { + const lines: string[] = []; + const docs = (node as unknown as JSDocableNode).getJsDocs(); + let typeText: string; + if (CUSTOM_TYPES[pathStr]) { + typeText = CUSTOM_TYPES[pathStr]; + } else if (type.isUnion() && !type.isBoolean()) { + typeText = type + .getUnionTypes() + .map((type) => type.getText()) + .join(' | '); + } else { + typeText = type.getText(); + } + const defaultValue = docs + .flatMap((doc) => doc.getTags()) + .find((tag) => tag.getTagName() === 'default') + ?.getCommentText(); + lines.push( + '', + `## ${pathStr}`, + '', + `- **Type**: \`${typeText}\``, + `- **Default**: \`${defaultValue}\``, + ...docs.flatMap((doc) => doc.getDescription()), + ); + return lines; + } + + return []; + }; + + const lines = getDocsFor([], inlineConfigInterface); + const text = + PREFACE + + '\n\n' + + readFileSync(configTemplatePath, 'utf-8').replace( + '{{ DOCS }}', + lines.join('\n'), + ); + + writeFileSync(configPath, text); + spinner.succeed('Generated /config.md'); + } catch (err) { + spinner.fail('Failed to generate /config.md'); + console.error(err.message); + } + }; + + return { + name: 'docs:generate-config-docs', + buildStart() { + generateDocs(); + }, + configureServer(server: any) { + server.watcher.add(externalTypesPath); + }, + handleHotUpdate(ctx: { file: string }) { + if ([externalTypesPath, configTemplatePath].includes(ctx.file)) { + generateDocs(); + } + }, + }; +} diff --git a/docs/config.md b/docs/config.md index 50a748be..b9ff6c1c 100644 --- a/docs/config.md +++ b/docs/config.md @@ -1,5 +1,200 @@ -# Config + -:::warning 🚧 Under construction -This documentation does not exist yet. -::: +# Config Reference + +Discover all the options you can use in your `wxt.config.ts` file. + +## root + +- **Type**: `string` +- **Default**: `process.cwd()` + +Your project's root directory containing the `package.json` used to fill out the +`manifest.json`. + +## srcDir + +- **Type**: `string` +- **Default**: `config.root` + +Directory containing all source code. Set to `"src"` to move all source code to a `src/` +directory. + +## publicDir + +- **Type**: `string` +- **Default**: `"${config.root}/public"` + +Directory containing files that will be copied to the output directory as-is. + +## entrypointsDir + +- **Type**: `string` +- **Default**: `"${config.srcDir}/entrypoints"` + +## configFile + +- **Type**: `string | false` +- **Default**: `"wxt.config.ts"` + +Path to `"wxt.config.ts"` file or false to disable config file discovery. + +## storeIds.chrome + +- **Type**: `string` +- **Default**: `undefined` + +## storeIds.firefox + +- **Type**: `string` +- **Default**: `undefined` + +## storeIds.edge + +- **Type**: `string` +- **Default**: `undefined` + +## mode + +- **Type**: `string` +- **Default**: `undefined` + +Explicitly set a mode to run in. This will override the default mode for each command, and can +be overridden by the command line `--mode` option. + +## browser + +- **Type**: `"chrome" | "firefox" | "safari" | "edge" | "opera"` +- **Default**: `"chrome"` + +Explicitly set a browser to build for. This will override the default browser for each command, +and can be overridden by the command line `--browser` option. + +## manifestVersion + +- **Type**: `2 | 3` +- **Default**: `undefined` + +Explicitly set a manifest version to target. This will override the default manifest version +for each command, and can be overridden by the command line `--mv2` or `--mv3` option. + +## manifest + +- **Type**: `Manifest | Promise | () => Manifest | () => Promise` +- **Default**: `undefined` + +Customize the `manifest.json` output. Can be an object, promise, or function that returns an +object or promise. + +## runner.openConsole + +- **Type**: `boolean` +- **Default**: `undefined` + +## runner.openDevtools + +- **Type**: `boolean` +- **Default**: `undefined` + +## runner.binaries.chrome + +- **Type**: `string` +- **Default**: `undefined` + +## runner.binaries.edge + +- **Type**: `string` +- **Default**: `undefined` + +## runner.binaries.opera + +- **Type**: `string` +- **Default**: `undefined` + +## runner.binaries.firefox + +- **Type**: `string` +- **Default**: `undefined` + +## runner.firefoxProfile + +- **Type**: `string` +- **Default**: `undefined` + +## runner.chromiumProfile + +- **Type**: `string` +- **Default**: `undefined` + +## runner.firefoxArgs + +- **Type**: `string[]` +- **Default**: `undefined` + +## runner.chromiumArgs + +- **Type**: `string[]` +- **Default**: `undefined` + +## runner.startUrls + +- **Type**: `string[]` +- **Default**: `undefined` + +## zip.artifactTemplate + +- **Type**: `string` +- **Default**: `"{name}-{version}-{browser}.zip"` + +Configure the filename output when zipping files. + +Available template variables: + +- `{name}` - The project's name converted to kebab-case +- `{version}` - The version_name or version from the manifest +- `{browser}` - The target browser from the `--browser` CLI flag +- `{manifestVersion}` - Either "2" or "3" + +## zip.sourcesTemplate + +- **Type**: `string` +- **Default**: `"{name}-{version}-sources.zip"` + +Configure the filename output when zipping files. + +Available template variables: + +- `{name}` - The project's name converted to kebab-case +- `{version}` - The version_name or version from the manifest +- `{browser}` - The target browser from the `--browser` CLI flag +- `{manifestVersion}` - Either "2" or "3" + +## zip.name + +- **Type**: `string` +- **Default**: `undefined` + +Override the artifactTemplate's `{name}` template variable. Defaults to the `package.json`'s +name, or if that doesn't exist, the current working directories name. + +## zip.sourcesRoot + +- **Type**: `string` +- **Default**: `config.root` + +Root directory to ZIP when generating the sources ZIP. + +## zip.ignoredSources + +- **Type**: `string[]` +- **Default**: `undefined` + +[Minimatch](https://www.npmjs.com/package/minimatch) patterns of files to exclude when +creating a ZIP of all your source code for Firfox. Patterns are relative to your +`config.zip.sourcesRoot`. + +Hidden files, node_modules, and tests are ignored by default. diff --git a/docs/config.tpl.md b/docs/config.tpl.md new file mode 100644 index 00000000..4b8bf56d --- /dev/null +++ b/docs/config.tpl.md @@ -0,0 +1,5 @@ +# Config Reference + +Discover all the options you can use in your `wxt.config.ts` file. + +{{ DOCS }} diff --git a/package.json b/package.json index 3be7929f..851be1c9 100644 --- a/package.json +++ b/package.json @@ -102,6 +102,7 @@ "prettier": "^2.8.8", "pretty-quick": "^3.1.3", "simple-git-hooks": "^2.9.0", + "ts-morph": "^19.0.0", "tsup": "^7.1.0", "tsx": "^3.12.7", "typescript": "^5.1.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e06e2334..399ebdc6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -120,6 +120,9 @@ importers: simple-git-hooks: specifier: ^2.9.0 version: 2.9.0 + ts-morph: + specifier: ^19.0.0 + version: 19.0.0 tsup: specifier: ^7.1.0 version: 7.1.0(typescript@5.1.6) @@ -1004,6 +1007,15 @@ packages: defer-to-connect: 2.0.1 dev: false + /@ts-morph/common@0.20.0: + resolution: {integrity: sha512-7uKjByfbPpwuzkstL3L5MQyuXPSKdoNG93Fmi2JoDcTf3pEP731JdRFAduRVkOs8oqxPsXKA+ScrWkdQ8t/I+Q==} + dependencies: + fast-glob: 3.3.1 + minimatch: 7.4.6 + mkdirp: 2.1.6 + path-browserify: 1.0.1 + dev: true + /@types/chai-subset@1.3.3: resolution: {integrity: sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==} dependencies: @@ -1684,7 +1696,6 @@ packages: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} dependencies: balanced-match: 1.0.2 - dev: false /braces@3.0.2: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} @@ -1927,6 +1938,10 @@ packages: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} engines: {node: '>=0.8'} + /code-block-writer@12.0.0: + resolution: {integrity: sha512-q4dMFMlXtKR3XNBHyMHt/3pwYNA69EDk00lloMOaaUMKPUXBw6lpXtbu3MMVG6/uOihGnRDOlkyqsONEUj60+w==} + dev: true + /color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} dependencies: @@ -3924,7 +3939,6 @@ packages: engines: {node: '>=10'} dependencies: brace-expansion: 2.0.1 - dev: false /minimatch@9.0.3: resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} @@ -3985,6 +3999,12 @@ packages: hasBin: true dev: false + /mkdirp@2.1.6: + resolution: {integrity: sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==} + engines: {node: '>=10'} + hasBin: true + dev: true + /mlly@1.4.0: resolution: {integrity: sha512-ua8PAThnTwpprIaU47EPeZ/bPUVp2QYBbWMphUQpVdBI3Lgqzm5KZQ45Agm3YJedHXaIHl6pBGabaLSUPPSptg==} dependencies: @@ -4370,6 +4390,10 @@ packages: entities: 4.5.0 dev: false + /path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + dev: true + /path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -5446,6 +5470,13 @@ packages: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} dev: true + /ts-morph@19.0.0: + resolution: {integrity: sha512-D6qcpiJdn46tUqV45vr5UGM2dnIEuTGNxVhg0sk5NX11orcouwj6i1bMqZIz2mZTZB1Hcgy7C3oEVhAT+f6mbQ==} + dependencies: + '@ts-morph/common': 0.20.0 + code-block-writer: 12.0.0 + dev: true + /tsconfck@2.1.1(typescript@5.1.6): resolution: {integrity: sha512-ZPCkJBKASZBmBUNqGHmRhdhM8pJYDdOXp4nRgj/O0JwUwsMq50lCDRQP/M5GBNAA0elPrq4gAeu4dkaVCuKWww==} engines: {node: ^14.13.1 || ^16 || >=18} diff --git a/src/core/types/external.ts b/src/core/types/external.ts index 6d4b1225..353f7338 100644 --- a/src/core/types/external.ts +++ b/src/core/types/external.ts @@ -5,37 +5,33 @@ import { EntrypointGroup } from '.'; export interface InlineConfig { /** - * Project root directory. + * Your project's root directory containing the `package.json` used to fill out the + * `manifest.json`. * - * @default - * process.cwd() + * @default process.cwd() */ root?: string; /** * Directory containing all source code. Set to `"src"` to move all source code to a `src/` * directory. * - * @default - * "" + * @default config.root */ srcDir?: string; /** * Directory containing files that will be copied to the output directory as-is. * - * @default - * "/publicDir" + * @default "${config.root}/public" */ publicDir?: string; /** - * @default - * "/entrypoints" + * @default "${config.srcDir}/entrypoints" */ entrypointsDir?: string; /** * Path to `"wxt.config.ts"` file or false to disable config file discovery. * - * @default - * "wxt.config.ts" + * @default "wxt.config.ts" */ configFile?: string | false; /** @@ -56,7 +52,7 @@ export interface InlineConfig { */ imports?: Partial; /** - * Explicitly set a browser to target. This will override the default browser for each command, + * Explicitly set a browser to build for. This will override the default browser for each command, * and can be overridden by the command line `--browser` option. * * @default @@ -98,12 +94,12 @@ export interface InlineConfig { * * Available template variables: * - * - `{{name}}` - The project's name converted to kebab-case - * - `{{version}} - The version_name or version from the manifest - * - `{{browser}} - The target browser from the `--browser` CLI flag - * - `{{manifestVersion}}` - Either "2" or "3" + * - `{name}` - The project's name converted to kebab-case + * - `{version}` - The version_name or version from the manifest + * - `{browser}` - The target browser from the `--browser` CLI flag + * - `{manifestVersion}` - Either "2" or "3" * - * @default "{{name}}-{{version}}-{{browser}}.zip" + * @default "{name}-{version}-{browser}.zip" */ artifactTemplate?: string; /** @@ -111,22 +107,23 @@ export interface InlineConfig { * * Available template variables: * - * - `{{name}}` - The project's name converted to kebab-case - * - `{{version}} - The version_name or version from the manifest - * - `{{browser}} - The target browser from the `--browser` CLI flag - * - `{{manifestVersion}}` - Either "2" or "3" + * - `{name}` - The project's name converted to kebab-case + * - `{version}` - The version_name or version from the manifest + * - `{browser}` - The target browser from the `--browser` CLI flag + * - `{manifestVersion}` - Either "2" or "3" * - * @default "{{name}}-{{version}}-sources.zip" + * @default "{name}-{version}-sources.zip" */ sourcesTemplate?: string; /** - * Override the artifactTemplate's `{{name}}` template variable. Defaults to the package.json's + * Override the artifactTemplate's `{name}` template variable. Defaults to the `package.json`'s * name, or if that doesn't exist, the current working directories name. */ name?: string; /** - * Root directory to ZIP. The ZIP can be uploaded to the Firefox Addon Store as your source - * code. Defaults to the `config.root` directory. + * Root directory to ZIP when generating the sources ZIP. + * + * @default config.root */ sourcesRoot?: string; /**