├── .prettierrc ├── docs ├── .npmrc ├── .gitignore ├── .config │ ├── automd.ts │ └── docs.yaml ├── pnpm-workspace.yaml ├── 1.guide │ ├── 3.api.md │ ├── 1.index.md │ └── 2.config.md ├── package.json ├── 2.generators │ ├── 1.index.md │ ├── with-automd.md │ ├── file.md │ ├── pm-x.md │ ├── fetch.md │ ├── jsdocs.md │ ├── jsimport.md │ ├── pm-install.md │ ├── contributors.md │ └── badges.md └── .docs │ └── public │ └── icon.svg ├── renovate.json ├── .gitignore ├── src ├── index.ts ├── generators │ ├── fetch.ts │ ├── index.ts │ ├── with-automd.ts │ ├── file.ts │ ├── jsimport.ts │ ├── pm.ts │ ├── contributors.ts │ ├── badges.ts │ └── jsdocs.ts ├── generator.ts ├── _utils.ts ├── config.ts ├── _parse.ts ├── cli.ts ├── transform.ts └── automd.ts ├── eslint.config.mjs ├── vitest.config.ts ├── .editorconfig ├── test ├── _setup.ts ├── fixture │ ├── src │ │ ├── example.ts │ │ └── test.ts │ ├── TEST.md │ ├── INPUT.md │ └── OUTPUT.md ├── automd.test.ts ├── parse.test.ts └── transform.test.ts ├── tsconfig.json ├── .github └── workflows │ ├── ci.yml │ └── autofix.yml ├── LICENSE ├── .config └── automd.ts ├── README.md ├── package.json └── CHANGELOG.md /.prettierrc: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /docs/.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .nuxt 3 | .data 4 | .output 5 | dist 6 | -------------------------------------------------------------------------------- /docs/.config/automd.ts: -------------------------------------------------------------------------------- 1 | export { default } from "../../.config/automd.ts"; 2 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["github>unjs/renovate-config"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | dist 4 | .vscode 5 | .DS_Store 6 | .eslintcache 7 | *.log* 8 | *.env* 9 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./automd"; 2 | export * from "./generator"; 3 | export * from "./config"; 4 | export * from "./transform"; 5 | -------------------------------------------------------------------------------- /docs/pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: [] 2 | ignoredBuiltDependencies: 3 | - "@parcel/watcher" 4 | - "@tailwindcss/oxide" 5 | - esbuild 6 | - vue-demi 7 | onlyBuiltDependencies: 8 | - better-sqlite3 9 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import unjs from "eslint-config-unjs"; 2 | 3 | // https://github.com/unjs/eslint-config 4 | export default unjs({ 5 | ignores: [ 6 | "**/.docs" 7 | ], 8 | rules: {}, 9 | }); 10 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vitest/config"; 2 | 3 | export default defineConfig({ 4 | test: { 5 | // reporters: ["hanging-process"], 6 | // globalSetup: "./test/_setup.ts", 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /docs/1.guide/3.api.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: eos-icons:api-outlined 3 | --- 4 | 5 | # Programmatic API 6 | 7 | > [!IMPORTANT] 8 | > Programmatic API is not stable! 9 | 10 | ```js 11 | import { automd, transform } from "automd"; 12 | ``` 13 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "type": "module", 4 | "scripts": { 5 | "build": "undocs build", 6 | "dev": "undocs dev" 7 | }, 8 | "devDependencies": { 9 | "undocs": "^0.4.10" 10 | }, 11 | "packageManager": "pnpm@10.22.0" 12 | } 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | charset = utf-8 8 | 9 | [*.js] 10 | indent_style = space 11 | indent_size = 2 12 | 13 | [{package.json,*.yml,*.cjson}] 14 | indent_style = space 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /test/_setup.ts: -------------------------------------------------------------------------------- 1 | import { automd } from "../src"; 2 | 3 | console.log("Starting automd with watcher on repo..."); 4 | const { unwatch } = await automd({ 5 | watch: true, 6 | }); 7 | 8 | export const setup = async () => {}; 9 | 10 | export const teardown = async () => { 11 | await unwatch?.(); 12 | }; 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "moduleResolution": "bundler", 6 | "esModuleInterop": true, 7 | "strict": true, 8 | "skipLibCheck": true, 9 | "resolveJsonModule": true, 10 | "paths": { 11 | "automd": [ 12 | "./src/index.ts" 13 | ] 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/fixture/src/example.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Adds two numbers together. 3 | * 4 | * @example 5 | * 6 | * ```js 7 | * add(1, 2); // 3 8 | * ``` 9 | */ 10 | export function add(a: number, b: number) { 11 | return a + b; 12 | } 13 | 14 | export const object = { 15 | /** 16 | * An object key 17 | */ 18 | key: { 19 | /** 20 | * A subkey 21 | */ 22 | subkey: "value", 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /docs/.config/docs.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://unpkg.com/undocs/schema/config.json 2 | 3 | name: "AutoMD" 4 | shortDescription: "Markdown, Automated." 5 | description: "your automated markdown maintainer" 6 | github: "unjs/automd" 7 | url: "https://automd.unjs.io" 8 | socials: 9 | twitter: "https://twitter.com/unjsio" 10 | bluesky: "https://bsky.app/profile/unjs.io" 11 | sponsors: 12 | api: https://sponsors.pi0.io/sponsors.json 13 | automd: true 14 | landing: 15 | contributors: true 16 | -------------------------------------------------------------------------------- /docs/2.generators/1.index.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: fluent-mdl2:generate 3 | --- 4 | 5 | # Generators 6 | 7 | Automd uses an extendable system of generators that are responsible to maintain each `<-- automd:name -->` section. 8 | 9 | There are several available generators for automd each supporting different arguments. 10 | 11 | See next sections for available generators and [open issues](https://github.com/unjs/automd/issues?q=is%3Aopen+is%3Aissue+label%3Agenerator) for proposed generators and feel free to suggest any generator ideas to be included! 12 | -------------------------------------------------------------------------------- /src/generators/fetch.ts: -------------------------------------------------------------------------------- 1 | import { defineGenerator } from "../generator"; 2 | 3 | export const fetch = defineGenerator({ 4 | name: "fetch", 5 | async generate({ args }) { 6 | const { $fetch } = await import("ofetch"); 7 | 8 | let url = args.url; 9 | if (!url) { 10 | throw new Error("URL is required!"); 11 | } 12 | if (url.startsWith("gh:")) { 13 | url = `https://raw.githubusercontent.com/${url.slice(3)}`; 14 | } 15 | 16 | const contents = await $fetch(url); 17 | 18 | return { 19 | contents, 20 | }; 21 | }, 22 | }); 23 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | ci: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v5 16 | - run: npm i -fg corepack && corepack enable 17 | - uses: actions/setup-node@v5 18 | with: 19 | node-version: 22 20 | cache: "pnpm" 21 | - run: pnpm install 22 | - run: pnpm lint 23 | - run: pnpm test:types 24 | - run: pnpm build 25 | - run: pnpm dev 26 | - run: pnpm vitest --coverage 27 | - uses: codecov/codecov-action@v5 28 | -------------------------------------------------------------------------------- /src/generators/index.ts: -------------------------------------------------------------------------------- 1 | import { Generator } from "../generator"; 2 | import { jsdocs } from "./jsdocs"; 3 | import { badges } from "./badges"; 4 | import { pmX, pmInstall } from "./pm"; 5 | import { fetch as _fetch } from "./fetch"; 6 | import { jsimport } from "./jsimport"; 7 | import { withAutomd } from "./with-automd"; 8 | import { file } from "./file"; 9 | import { contributors } from "./contributors"; 10 | 11 | export default { 12 | jsdocs, 13 | badges, 14 | "pm-i": pmInstall, 15 | "pm-install": pmInstall, 16 | "pm-x": pmX, 17 | fetch: _fetch, 18 | file, 19 | jsimport, 20 | "with-automd": withAutomd, 21 | contributors, 22 | } as Record; 23 | -------------------------------------------------------------------------------- /test/fixture/TEST.md: -------------------------------------------------------------------------------- 1 | ## The Lazy Coder's Guide to Programming 2 | 3 | Programming can be hard. But fear not! With the power of copy-paste, you can conquer any coding challenge without breaking a sweat. Just remember: if it works once, it'll work a thousand times. Who needs original code anyway? 4 | 5 | When your code doesn't work, don't blame yourself. It's clearly the compiler's fault for not understanding your genius. Remember, the more error messages you get, the closer you are to becoming a programming master. 6 | 7 | Why waste time solving problems when someone else has already done it for you? Stack Overflow is your best friend, your mentor, and your savior. Just make sure to upvote the answers that save your bacon. 8 | -------------------------------------------------------------------------------- /.github/workflows/autofix.yml: -------------------------------------------------------------------------------- 1 | name: autofix.ci # needed to securely identify the workflow 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: ["main"] 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | autofix: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v5 16 | - run: npm i -fg corepack && corepack enable 17 | - uses: actions/setup-node@v5 18 | with: 19 | node-version: 22 20 | cache: "pnpm" 21 | - run: pnpm install 22 | - run: pnpm lint:fix 23 | - run: pnpm vitest run -u 24 | - uses: autofix-ci/action@635ffb0c9798bd160680f18fd73371e355b85f27 25 | with: 26 | commit-message: "chore: apply automated fixes" 27 | -------------------------------------------------------------------------------- /src/generators/with-automd.ts: -------------------------------------------------------------------------------- 1 | import { defineGenerator } from "../generator"; 2 | 3 | export const withAutomd = defineGenerator({ 4 | name: "with-automd", 5 | generate({ args }) { 6 | const lastUpdate = args.lastUpdate 7 | ? ` (last updated: ${typeof args.lastUpdate === "string" ? args.lastUpdate : new Date().toDateString()})` 8 | : ""; 9 | 10 | const emoji = args.emoji === false ? "" : "🤖 "; 11 | 12 | const lines: string[] = []; 13 | 14 | if (args.separator !== false) { 15 | lines.push("---", ""); 16 | } 17 | 18 | lines.push( 19 | `_${emoji}auto updated with [automd](https://automd.unjs.io)${lastUpdate}_`, 20 | ); 21 | 22 | return { 23 | contents: lines.join("\n"), 24 | }; 25 | }, 26 | }); 27 | -------------------------------------------------------------------------------- /docs/2.generators/with-automd.md: -------------------------------------------------------------------------------- 1 | # with-automd 2 | 3 | The `with-automd` generator generates a benner that notifies docs are updated with automd + the last update time. 4 | 5 | ## Example 6 | 7 | 8 | 9 | ### Input 10 | 11 | 12 | 13 | 14 | ### Output 15 | 16 | 17 | 18 | --- 19 | 20 | _🤖 auto updated with [automd](https://automd.unjs.io)_ 21 | 22 | 23 | 24 | 25 | 26 | ## Arguments 27 | 28 | ::field-group 29 | 30 | ::field{name="lastUpdate" type="string"} 31 | Show last updated date. (use string for static value) 32 | :: 33 | 34 | ::field{name="no-separator" type="boolean"} 35 | Disable addition of separator `---` 36 | :: 37 | 38 | :: 39 | -------------------------------------------------------------------------------- /docs/1.guide/1.index.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: ph:book-open-duotone 3 | --- 4 | 5 | # Getting Started 6 | 7 | Automd scans for the annotation comments within the markdown document and updates their contents using built-in generators. 8 | 9 | The syntax is like this: 10 | 11 | 12 | 13 | 14 | > [!NOTE] 15 | > This project is in the early stages and under development. 16 | 17 | ## Using CLI 18 | 19 | The easiest way to use automd is to use the CLI. You can install automd and add it to the `build` or `release` command in `package.json` or directly run `npx automd` in your project. 20 | 21 | ```sh 22 | npx automd@latest 23 | ``` 24 | 25 | By default, the `README.md` file in the current working directory will be used as the target. 26 | 27 | You can use `--dir` and `--input` arguments to customize the default behavior to operate on any other markdown file. 28 | -------------------------------------------------------------------------------- /test/automd.test.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath } from "node:url"; 2 | import { expect, describe, it } from "vitest"; 3 | import { format } from "prettier"; 4 | import { automd } from "../src"; 5 | 6 | describe("automd generators", () => { 7 | let output: string; 8 | 9 | it("run on fixture", async () => { 10 | const { results } = await automd({ 11 | dir: fileURLToPath(new URL("fixture", import.meta.url)), 12 | input: "INPUT.md", 13 | output: "OUTPUT.md", 14 | }); 15 | output = results[0].contents; 16 | await expect(output).toMatchFileSnapshot(`fixture/OUTPUT.md`); 17 | 18 | const issues = results 19 | .flatMap((r) => r.updates.flatMap((u) => u.result.issues)) 20 | .filter(Boolean); 21 | expect(issues).toEqual([]); 22 | }); 23 | 24 | it("is formatted", async () => { 25 | expect(await format(output, { parser: "markdown" })).toEqual(output); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /src/generator.ts: -------------------------------------------------------------------------------- 1 | import type { Block } from "./_parse"; 2 | import type { ResolvedConfig } from "./config"; 3 | import type { TransformResult } from "./transform"; 4 | 5 | export interface GenerateContext { 6 | args: Record; 7 | config: ResolvedConfig; 8 | block: Block; 9 | url?: string; 10 | transform: (contents: string) => Promise; 11 | } 12 | 13 | /** 14 | * The result of generating a component. 15 | */ 16 | export interface GenerateResult { 17 | /** 18 | * The generated component 19 | */ 20 | contents: string; 21 | 22 | /** 23 | * A list of issues that occurred during generation. 24 | */ 25 | issues?: string[]; 26 | 27 | /** 28 | * Whether to unwrap the component. 29 | */ 30 | unwrap?: boolean; 31 | } 32 | 33 | export interface Generator { 34 | name: string; 35 | generate: (ctx: GenerateContext) => GenerateResult | Promise; 36 | } 37 | 38 | /** 39 | * @internal 40 | */ 41 | export function defineGenerator(generator: Generator) { 42 | return generator; 43 | } 44 | -------------------------------------------------------------------------------- /test/fixture/INPUT.md: -------------------------------------------------------------------------------- 1 | # Automd built-in generator fixtures 2 | 3 | ## `badges` 4 | 5 | 6 | 7 | 8 | ## `pm-x` 9 | 10 | 11 | 12 | 13 | ## `pm-install` 14 | 15 | 16 | 17 | 18 | ## `jsdocs` 19 | 20 | 21 | 22 | 23 | ## `jsimport` 24 | 25 | 26 | 27 | 28 | ## `with-automd` 29 | 30 | 31 | 32 | 33 | ## `fetch` 34 | 35 | 36 | 37 | 38 | ## `file` 39 | 40 | 41 | 42 | 43 | ## `contributors` 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Pooya Parsa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.config/automd.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "../src"; 2 | 3 | export default { 4 | input: ["README.md", "docs/**/*.md"], 5 | ignore: ["test/fixture/**"], 6 | generators: { 7 | example: { 8 | name: "example", 9 | async generate({ args, transform }) { 10 | const { generator, ...generatorArgs } = args; 11 | 12 | const argsString = Object.entries(generatorArgs) 13 | .map(([k, v]) => { 14 | if (v === true) { 15 | return k; 16 | } 17 | if (v === false) { 18 | return k.startsWith("no-") ? k.slice(3) : `no-${k}`; 19 | } 20 | return `${k}=${JSON.stringify(v)}`; 21 | }) 22 | .join(" ") 23 | .trim(); 24 | 25 | const input = `\n`; 26 | const output = (await transform(input)).contents; 27 | return { 28 | contents: `### Input\n\n${_mdCode(input)}\n\n### Output\n\n${_mdCode(output)}`, 29 | }; 30 | }, 31 | }, 32 | }, 33 | }; 34 | 35 | function _mdCode(md: string) { 36 | return md 37 | .split("\n") 38 | .map((l) => { 39 | l = l.trim(); 40 | return l ? ` ${l}` : ""; 41 | }) 42 | .join("\n"); 43 | } 44 | -------------------------------------------------------------------------------- /docs/2.generators/file.md: -------------------------------------------------------------------------------- 1 | # file 2 | 3 | The `file` generator reads a file and inlines it's contents. 4 | 5 | ## Example 6 | 7 | 8 | 9 | ### Input 10 | 11 | 12 | 13 | 14 | ### Output 15 | 16 | 17 | 18 | ```ts [example.ts] 19 | /** 20 | * Adds two numbers together. 21 | * 22 | * @example 23 | * 24 | * ```js 25 | * add(1, 2); // 3 26 | * ``` 27 | */ 28 | export function add(a: number, b: number) { 29 | return a + b; 30 | } 31 | 32 | export const object = { 33 | /** 34 | * An object key 35 | */ 36 | key: { 37 | /** 38 | * A subkey 39 | */ 40 | subkey: "value", 41 | }, 42 | }; 43 | ``` 44 | 45 | 46 | 47 | 48 | 49 | ## Arguments 50 | 51 | ::field-group 52 | 53 | ::field{name="src" type="string"} 54 | Relative path to the file. 55 | :: 56 | 57 | ::field{name="code" type="boolean"} 58 | Render file as code. 59 | :: 60 | 61 | ::field{name="lang" type="string"} 62 | Code lang. 63 | :: 64 | 65 | ::field{name="name" type="string|boolean"} 66 | File name in code. Use `no-name` to disable name in code. 67 | :: 68 | 69 | ::field{name="noTrim" type="boolean"} 70 | Don't trim the file contents. 71 | :: 72 | -------------------------------------------------------------------------------- /docs/2.generators/pm-x.md: -------------------------------------------------------------------------------- 1 | # pm-x 2 | 3 | The `pm-x` generator generates commands for running/executing a package through JavaScript package managers. 4 | 5 | ## Example 6 | 7 | 8 | 9 | ### Input 10 | 11 | 12 | 13 | 14 | ### Output 15 | 16 | 17 | 18 | ```sh 19 | # npm 20 | npx package-name@latest "[files] 21 | 22 | # pnpm 23 | pnpm dlx package-name@latest "[files] 24 | 25 | # bun 26 | bunx package-name@latest "[files] 27 | 28 | # deno 29 | deno run -A npm:package-name@latest "[files] 30 | ``` 31 | 32 | 33 | 34 | 35 | 36 | ## Arguments 37 | 38 | ::field-group 39 | 40 | ::field{name="name" type="string"} 41 | The package name (by default tries to read from the `name` field in `package.json`). 42 | :: 43 | ::field{name="separate" type="boolean"} 44 | Separate code blocks for each package manager (defaults to `false`). 45 | :: 46 | ::field{name="args" type="string"} 47 | An additional string appended at the end of each command suggesting arguments to be used with the program. (defaults to `""`). 48 | :: 49 | ::field{name="version" type="boolean"} 50 | Show version in exec command 51 | :: 52 | 53 | :: 54 | -------------------------------------------------------------------------------- /docs/2.generators/fetch.md: -------------------------------------------------------------------------------- 1 | # fetch 2 | 3 | The `fetch` generator fetches a URL (using [unjs/ofetch](https://ofetch.unjs.io)) and inlines response body. 4 | 5 | ## Example 6 | 7 | 8 | 9 | ### Input 10 | 11 | 12 | 13 | 14 | ### Output 15 | 16 | 17 | 18 | ## The Lazy Coder's Guide to Programming 19 | 20 | Programming can be hard. But fear not! With the power of copy-paste, you can conquer any coding challenge without breaking a sweat. Just remember: if it works once, it'll work a thousand times. Who needs original code anyway? 21 | 22 | When your code doesn't work, don't blame yourself. It's clearly the compiler's fault for not understanding your genius. Remember, the more error messages you get, the closer you are to becoming a programming master. 23 | 24 | Why waste time solving problems when someone else has already done it for you? Stack Overflow is your best friend, your mentor, and your savior. Just make sure to upvote the answers that save your bacon. 25 | 26 | 27 | 28 | 29 | 30 | ## Arguments 31 | 32 | ::field-group 33 | 34 | ::field{name="url" type="string"} 35 | The URL to fetch from 36 | :: 37 | 38 | :: 39 | 40 | > [!TIP] 41 | > You can start url with `gh:` to use github contents! 42 | -------------------------------------------------------------------------------- /src/_utils.ts: -------------------------------------------------------------------------------- 1 | import type { PackageJson } from "pkg-types"; 2 | import { readPackageJSON } from "pkg-types"; 3 | import { defu } from "defu"; 4 | import { fileURLToPath } from "mlly"; 5 | import { resolve, join } from "pathe"; 6 | 7 | export function resolvePath( 8 | path: string, 9 | { url, dir }: { url?: string; dir: string }, 10 | ) { 11 | if (path.startsWith("/")) { 12 | return join(dir, path); 13 | } 14 | return url ? fileURLToPath(new URL(path, url)) : resolve(dir, path); 15 | } 16 | 17 | export async function getPkg(dir: string, input: Record = {}) { 18 | const pkg = await readPackageJSON(dir).catch(() => undefined); 19 | return defu( 20 | { 21 | name: input.name, 22 | version: typeof input.version === "string" ? input.version : undefined, 23 | github: input.github || input.gh, 24 | }, 25 | { 26 | name: pkg?.name, 27 | version: pkg?.version, 28 | github: _getGitRepo(pkg?.repository), 29 | }, 30 | { 31 | name: process.env.npm_package_name, 32 | version: process.env.npm_package_version, 33 | }, 34 | ); 35 | } 36 | 37 | function _getGitRepo(repo: PackageJson["repository"]) { 38 | const url = typeof repo === "string" ? repo : repo?.url; 39 | if (!url || typeof url !== "string") { 40 | return; 41 | } 42 | const match = 43 | /(?:https:\/\/github\.com\/|gh:|github:|)([\w-]+)\/([\w-]+)/.exec(url); 44 | if (match && match[1] && match[2]) { 45 | return `${match[1]}/${match[2]}`; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /test/fixture/src/test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Send a message 3 | * 4 | * This is another description of the 5 | * function that spans multiple lines. 6 | * 7 | * Again, this is another description of the function that spans multiple lines. 8 | * 9 | * @param message - The message to send 10 | * @param date - The date to send the message 11 | * @param flash - Whether to flash the message 12 | * 13 | * @example 14 | * 15 | * ```js 16 | * sendMessage("Hello", "7/1/1995", false); // => "OK" 17 | * ``` 18 | */ 19 | export function sendMessage( 20 | message: string, 21 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 22 | date = new Date(), 23 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 24 | flash?: boolean, 25 | ): string { 26 | return "OK"; 27 | } 28 | 29 | /** 30 | * 31 | * Various configuration options 32 | */ 33 | export const config = { 34 | /** 35 | * The name of the configuration 36 | */ 37 | name: "default", 38 | 39 | /** The price */ 40 | price: 12.5, 41 | 42 | /** 43 | * checked state 44 | */ 45 | checked: false, 46 | 47 | /** 48 | * Configure the dimensions 49 | * 50 | * @example 51 | * 52 | * ```js 53 | * { width: 10, height: 10 } 54 | * ``` 55 | */ 56 | dimensions: { 57 | /** Width in px */ 58 | width: 10, 59 | 60 | /** Height in px */ 61 | height: 10, 62 | }, 63 | 64 | /** 65 | * A list of tags 66 | */ 67 | tags: { 68 | $resolve: (val: string) => ["tag1", val], 69 | }, 70 | }; 71 | -------------------------------------------------------------------------------- /test/parse.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from "vitest"; 2 | import { parseRawArgs, findBlocks } from "../src/_parse"; 3 | 4 | describe("parseRawArgs", () => { 5 | const tests = [ 6 | [`foo`, { foo: true }], 7 | [`no-foo`, { foo: false }], 8 | [`foo="bar"`, { foo: "bar" }], 9 | [`foo=bar`, { foo: "bar" }], 10 | [ 11 | `a-key=a-value another-key=another-value`, 12 | { aKey: "a-value", anotherKey: "another-value" }, 13 | ], 14 | ] as const; 15 | for (const [input, expected] of tests) { 16 | it(`${JSON.stringify(input)} => ${JSON.stringify(expected)}`, () => { 17 | expect(parseRawArgs(input)).toEqual(expected); 18 | }); 19 | } 20 | }); 21 | 22 | describe("findBlocks", () => { 23 | const fixture = ` 24 | 25 | (a) 26 | 27 | 28 | 29 | (b) 30 | 31 | 32 | 33 | (c) 34 | 35 | `; 36 | 37 | 38 | const mkBlock = (generator: string, rawArgs: string, contents: string) => ({ 39 | generator, 40 | rawArgs, 41 | contents: expect.stringContaining(contents), 42 | loc: { start: expect.any(Number), end: expect.any(Number) }, 43 | }); 44 | 45 | it("should find all blocks", () => { 46 | const blocks = findBlocks(fixture); 47 | expect(blocks[0]).toMatchObject(mkBlock("pm-x", "args=.", "(a)")); 48 | expect(blocks[1]).toMatchObject( 49 | mkBlock("pm-install", "dev no-auto", "(b)"), 50 | ); 51 | expect(blocks[2]).toMatchObject(mkBlock("jsdocs", "", "(c)")); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /src/generators/file.ts: -------------------------------------------------------------------------------- 1 | import { readFile } from "node:fs/promises"; 2 | import { basename, extname } from "pathe"; 3 | import { md } from "mdbox"; 4 | import { defineGenerator } from "../generator"; 5 | import { resolvePath } from "../_utils"; 6 | 7 | export const file = defineGenerator({ 8 | name: "file", 9 | async generate({ args, config, url }) { 10 | const fullPath = resolvePath(args.src, { url, dir: config.dir }); 11 | let contents = await readFile(fullPath, "utf8"); 12 | if (!args.noTrim) { 13 | contents = contents.trim(); 14 | } 15 | 16 | if (args.lines) { 17 | const groups = /^(?\d+)?:(?\d+)?$/.exec( 18 | args.lines, 19 | )?.groups; 20 | 21 | if (!groups) { 22 | throw new Error("invalid lines format"); 23 | } 24 | 25 | const lines = contents.split("\n"); 26 | 27 | const startLine = Number(groups.startLine) || 1; 28 | const endLine = Number(groups.endLine) || (lines.length as number); 29 | 30 | if (startLine < 1) { 31 | throw new Error("first line's index can not be smaller than 1"); 32 | } 33 | 34 | contents = lines.slice(startLine - 1, endLine).join("\n"); 35 | } 36 | 37 | if (args.code) { 38 | contents = md.codeBlock( 39 | contents, 40 | args.lang || extname(fullPath).slice(1), 41 | { 42 | // prettier-ignore 43 | ext: args.name === false ? undefined : (typeof args.name === 'string' ? args.name : `[${basename(fullPath)}]`), 44 | }, 45 | ); 46 | } 47 | 48 | return { 49 | contents, 50 | }; 51 | }, 52 | }); 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🤖 automd 2 | 3 | 4 | 5 | [![npm version](https://img.shields.io/npm/v/automd?color=yellow)](https://npmjs.com/package/automd) 6 | [![npm downloads](https://img.shields.io/npm/dm/automd?color=yellow)](https://npm.chart.dev/automd) 7 | [![license](https://img.shields.io/github/license/unjs/automd?color=yellow)](https://github.com/unjs/automd/blob/main/LICENSE) 8 | 9 | 10 | 11 | Automated markdown maintainer! 12 | 13 | 📚 [documentation](https://automd.unjs.io) 14 | 15 | 16 | 17 | ## Contribution 18 | 19 |
20 | Local development 21 | 22 | - Clone this repository 23 | - Install the latest LTS version of [Node.js](https://nodejs.org/en/) 24 | - Enable [Corepack](https://github.com/nodejs/corepack) using `corepack enable` 25 | - Install dependencies using `pnpm install` 26 | - Run tests using `pnpm dev` or `pnpm test` 27 | 28 |
29 | 30 | 31 | 32 | ## License 33 | 34 | 35 | 36 | Published under the [MIT](https://github.com/unjs/automd/blob/main/LICENSE) license. 37 | Made by [@pi0](https://github.com/pi0) and [community](https://github.com/unjs/automd/graphs/contributors) 💛 38 |

39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | --- 48 | 49 | _🤖 auto updated with [automd](https://automd.unjs.io)_ 50 | 51 | 52 | -------------------------------------------------------------------------------- /docs/2.generators/jsdocs.md: -------------------------------------------------------------------------------- 1 | # jsdocs 2 | 3 | The `jsdocs` generator can automatically read through your code and extract and sync documentation of function exports leveraging JSDocs and TypeScript hints. 4 | 5 | Internally it uses [untyped](https://untyped.unjs.io/) and [jiti](https://github.com/unjs/jiti) loader for JSDocs parsing and TypeScript support. 6 | 7 | ## Example 8 | 9 | 10 | 11 | ### Input 12 | 13 | 14 | 15 | 16 | ### Output 17 | 18 | 19 | 20 | ### `add(a, b)` 21 | 22 | Adds two numbers together. 23 | 24 | **Example:** 25 | 26 | ```js 27 | add(1, 2); // 3 28 | ``` 29 | 30 | ### `object` 31 | 32 | #### `key` 33 | 34 | An object key 35 | 36 | ##### `subkey` 37 | 38 | - **Type**: `string` 39 | - **Default**: `"value"` 40 | 41 | A subkey 42 | 43 | 44 | 45 | 46 | 47 | ## Arguments 48 | 49 | ::field-group 50 | 51 | ::field{name="src" type="string"} 52 | Path to the source file. The default is `./src/index` and can be omitted. 53 | :: 54 | 55 | ::field{name="headingLevel" type="number"} 56 | Nested level for markdown group headings (default is `2` => `##`). Note: Each function uses `headingLevel+1` for the title in nested levels. 57 | :: 58 | 59 | ::field{name="group" type="string"} 60 | Only render function exports annotated with `@group name`. By default, there is no group filter. Value can be a string or an array of strings. 61 | :: 62 | 63 | :: 64 | -------------------------------------------------------------------------------- /docs/2.generators/jsimport.md: -------------------------------------------------------------------------------- 1 | # jsimport 2 | 3 | The `jsimport` generator generates JavaScript usage example to be imported. 4 | 5 | ## Example 6 | 7 | 8 | 9 | ### Input 10 | 11 | 12 | 13 | 14 | ### Output 15 | 16 | 17 | 18 | **ESM** (Node.js, Bun, Deno) 19 | 20 | ```js 21 | import { foo, bar } from "pkg"; 22 | ``` 23 | 24 | **CommonJS** (Legacy Node.js) 25 | 26 | ```js 27 | const { foo, bar } = require("pkg"); 28 | ``` 29 | 30 | **CDN** (Deno and Browsers) 31 | 32 | ```js 33 | import { foo, bar } from "https://esm.sh/pkg"; 34 | ``` 35 | 36 | 37 | 38 | 39 | 40 | ## Arguments 41 | 42 | ::field-group 43 | 44 | ::field{name="name" type="string"} 45 | The package name (by default tries to read from the `name` field in `package.json`). 46 | :: 47 | 48 | ::field{name="import/imports" type="string"} 49 | Array or comma seperated list of export names. 50 | :: 51 | 52 | ::field{name="no-esm" type="boolean"} 53 | Disable ESM instructions. 54 | :: 55 | 56 | ::field{name="cdn" type="boolean"} 57 | Generate CDN import usage. 58 | :: 59 | 60 | ::field{name="cjs" type="boolean"} 61 | Generate CommonJS require usage. 62 | :: 63 | 64 | ::field{name="src" type="boolean"} 65 | Auto scan export names from src using [unjs/mlly](https://mlly.unjs.io). 66 | :: 67 | 68 | ::field{name="printWidth" type="number"} 69 | The maximum length that requires wrapping lines. Default is `80` 70 | :: 71 | 72 | :: 73 | -------------------------------------------------------------------------------- /docs/2.generators/pm-install.md: -------------------------------------------------------------------------------- 1 | # pm-install 2 | 3 | The `pm-install` or `pm-i` generator generates installation commands for several JavaScript package managers. 4 | 5 | ## Example 6 | 7 | 8 | 9 | ### Input 10 | 11 | 12 | 13 | 14 | ### Output 15 | 16 | 17 | 18 | ```sh 19 | # ✨ Auto-detect 20 | npx nypm install -D package-name 21 | 22 | # npm 23 | npm install -D package-name 24 | 25 | # yarn 26 | yarn add -D package-name 27 | 28 | # pnpm 29 | pnpm add -D package-name 30 | 31 | # bun 32 | bun install -D package-name 33 | 34 | # deno 35 | deno install --dev npm:package-name 36 | ``` 37 | 38 | 39 | 40 | 41 | 42 | ## Arguments 43 | 44 | ::field-group 45 | 46 | ::field{name="name" type="string"} 47 | The package name (by default tries to read from the `name` field in `package.json`). 48 | :: 49 | 50 | ::field{name="dev" type="boolean"} 51 | Install as a dev dependency (defaults to `false`). 52 | :: 53 | 54 | ::field{name="global" type="boolean"} 55 | Install globally (useful for CLIs) (defaults to `false`). 56 | :: 57 | 58 | ::field{name="separate" type="boolean"} 59 | Separate code blocks for each package manager (defaults to `false`). 60 | :: 61 | 62 | ::field{name="auto" type="boolean"} 63 | Auto-detect package manager using [unjs/nypm](https://github.com/unjs/nypm#-nypm) (defaults to `true`). 64 | :: 65 | 66 | ::field{name="version" type="boolean"} 67 | Show version in install command 68 | :: 69 | 70 | :: 71 | -------------------------------------------------------------------------------- /docs/1.guide/2.config.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: ri:settings-3-line 3 | --- 4 | 5 | # Configuration 6 | 7 | You can specify custom configuration using `.automdrc` or `automd.config` (with `.ts`, `.mjs`, `.json`, ... powered by [unjs/c12](https://c12.unjs.io)) 8 | 9 | ## Syntax 10 | 11 | **Example:** `.automdrc` 12 | 13 | ```ini 14 | input=DOCS.md 15 | ``` 16 | 17 | **Example:** `automd.config.js` 18 | 19 | ```ts 20 | /** @type {import("automd").Config} */ 21 | export default { 22 | input: "DOCS.md", 23 | }; 24 | ``` 25 | 26 | ## Available Configs 27 | 28 | ### `dir` 29 | 30 | - Type: `string` 31 | - Default: current working directory 32 | 33 | Working directory where paths are resolved from. 34 | 35 | > [!TIP] 36 | > You can use `--dir` to override with CLI. 37 | 38 | ### `input` 39 | 40 | - Type: `string | string[]` 41 | - Default: `README.md` 42 | 43 | Name or path to the input file or files with glob patterns. 44 | 45 | > [!TIP] 46 | > You can use `--input` to override with CLI. 47 | 48 | ### `output` 49 | 50 | - Type: `string` 51 | - Default: input 52 | 53 | Name or path of the output files. If not provided, the input file will be overwritten. 54 | 55 | ### `ignore` 56 | 57 | - Type: `string[]` 58 | - Default: `["node_modules", "dist", "/.*"]` 59 | 60 | Ignore patterns if input is a glob pattern. 61 | 62 | ### `watch` 63 | 64 | - Type: `boolean` 65 | - Default: `false` 66 | 67 | Watch for changes in input files and regenerate output. 68 | 69 | ### `onWatch` 70 | 71 | - Type: `function` 72 | - Default: `undefined` 73 | 74 | Watch callback function, called when files change in watch mode. 75 | 76 | ### `generators` 77 | 78 | - Type: `object` 79 | - Default: `{}` 80 | 81 | A map of generator names to custom generators. 82 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "automd", 3 | "version": "0.4.2", 4 | "description": "Your automated markdown maintainer!", 5 | "repository": "unjs/automd", 6 | "license": "MIT", 7 | "sideEffects": false, 8 | "type": "module", 9 | "exports": { 10 | "types": "./dist/index.d.mts", 11 | "default": "./dist/index.mjs" 12 | }, 13 | "types": "./dist/index.d.mts", 14 | "bin": { 15 | "automd": "dist/cli.mjs" 16 | }, 17 | "files": [ 18 | "dist" 19 | ], 20 | "scripts": { 21 | "automd": "jiti src/cli.ts", 22 | "docs:dev": "cd docs && bun i && bun dev", 23 | "build": "pnpm automd && unbuild", 24 | "dev": "vitest -u", 25 | "lint": "eslint --cache . && prettier -c src", 26 | "lint:fix": "pnpm automd && eslint --cache . --fix && prettier -c src -w", 27 | "prepack": "pnpm build", 28 | "release": "pnpm test && changelogen --release && npm publish && git push --follow-tags && pnpm build --stub", 29 | "test": "pnpm lint && pnpm test:types && vitest run --coverage", 30 | "test:types": "tsc --noEmit --skipLibCheck" 31 | }, 32 | "dependencies": { 33 | "@parcel/watcher": "^2.5.1", 34 | "c12": "^3.3.2", 35 | "citty": "^0.1.6", 36 | "consola": "^3.4.2", 37 | "defu": "^6.1.4", 38 | "destr": "^2.0.5", 39 | "didyoumean2": "^7.0.4", 40 | "magic-string": "^0.30.21", 41 | "mdbox": "^0.1.1", 42 | "mlly": "^1.8.0", 43 | "ofetch": "^1.5.1", 44 | "pathe": "^2.0.3", 45 | "perfect-debounce": "^2.0.0", 46 | "pkg-types": "^2.3.0", 47 | "scule": "^1.3.0", 48 | "tinyglobby": "^0.2.15", 49 | "untyped": "^2.0.0" 50 | }, 51 | "devDependencies": { 52 | "@types/node": "^24.10.1", 53 | "@vitest/coverage-v8": "^3.2.4", 54 | "automd": "^0.4.2", 55 | "changelogen": "^0.6.2", 56 | "eslint": "^9.39.1", 57 | "eslint-config-unjs": "^0.5.0", 58 | "jiti": "^2.6.1", 59 | "prettier": "^3.6.2", 60 | "typescript": "^5.9.3", 61 | "unbuild": "^3.6.1", 62 | "vitest": "^3.2.4" 63 | }, 64 | "packageManager": "pnpm@10.22.0" 65 | } 66 | -------------------------------------------------------------------------------- /src/generators/jsimport.ts: -------------------------------------------------------------------------------- 1 | import { readFile } from "node:fs/promises"; 2 | import { md } from "mdbox"; 3 | import { findExportNames, resolvePath } from "mlly"; 4 | import { getPkg } from "../_utils"; 5 | import { defineGenerator } from "../generator"; 6 | 7 | const DEFAULT_CDN = "https://esm.sh/"; 8 | 9 | export const jsimport = defineGenerator({ 10 | name: "jsimport", 11 | async generate({ config, args }) { 12 | const { name } = await getPkg(config.dir, args); 13 | 14 | const importPath = name + (args.path || ""); 15 | 16 | const importNames: string[] = ([] as string[]) 17 | .concat(args.import, args.imports) // eslint-disable-line unicorn/prefer-spread 18 | .filter(Boolean) 19 | .flatMap((i) => i.split(/\s*,\s*/)); 20 | 21 | if (args.src) { 22 | const resolved = await resolvePath(args.src, { url: config.dir }); 23 | const contents = await readFile(resolved, "utf8"); 24 | const exportNames = findExportNames(contents); 25 | if (exportNames && exportNames.length > 0) { 26 | importNames.push(...exportNames); 27 | } 28 | } 29 | 30 | const lines: string[] = []; 31 | 32 | const fmtImports = 33 | importNames.length > 1 34 | ? `\n${importNames.map((i) => " " + i + ",").join("\n")}\n` 35 | : (importNames[0] && ` ${importNames[0]} `) || ""; 36 | 37 | const formatMultiLine = (str: string) => { 38 | return str.length > (args.printWidth || 80) 39 | ? str 40 | : str 41 | .replace(/\n/g, "") 42 | .replace(/,\s*}/, "}") 43 | .replace(/(\w)}/, "$1 }") 44 | .replace(/\s+/g, " "); 45 | }; 46 | 47 | if (args.esm !== false) { 48 | const code = formatMultiLine( 49 | `import {${fmtImports}} from "${importPath}";`, 50 | ); 51 | lines.push("**ESM** (Node.js, Bun, Deno)", md.codeBlock(code, "js")); 52 | } 53 | 54 | if (args.cjs) { 55 | const code = formatMultiLine( 56 | `const {${fmtImports}} = require("${importPath}");`, 57 | ); 58 | lines.push("**CommonJS** (Legacy Node.js)", md.codeBlock(code, "js")); 59 | } 60 | 61 | if (args.cdn) { 62 | const cdnBase = typeof args.cdn === "string" ? args.cdn : DEFAULT_CDN; 63 | const code = formatMultiLine( 64 | `import {${fmtImports}} from "${cdnBase}${importPath}";`, 65 | ); 66 | lines.push("**CDN** (Deno and Browsers)", md.codeBlock(code, "js")); 67 | } 68 | 69 | return { 70 | contents: lines.join("\n\n"), 71 | }; 72 | }, 73 | }); 74 | -------------------------------------------------------------------------------- /docs/2.generators/contributors.md: -------------------------------------------------------------------------------- 1 | # contributors 2 | 3 | The `contributors` generator generates an image of contributors using [contrib.rocks](https://contrib.rocks/) service plus additional data about authors and license. 4 | 5 | ## Example 6 | 7 | 8 | 9 | ### Input 10 | 11 | 12 | 13 | 14 | ### Output 15 | 16 | 17 | 18 | Published under the [MIT](https://github.com/unjs/automd/blob/main/LICENSE) license. 19 | Made by [@pi0](https://github.com/pi0) and [community](https://github.com/unjs/automd/graphs/contributors) 💛 20 |

21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ## Arguments 30 | 31 | ::field-group 32 | 33 | ::field{name="provider" type="string"} 34 | Available providers are `markupgo` and `contrib.rocks` (default is `contrib.rocks`) 35 | :: 36 | 37 | ::field{name="github" type="string"} 38 | Github repository name (by default tries to read from `package.json`) e.g. `unjs/automd` 39 | :: 40 | 41 | ::field{name="max" type="number"} 42 | Max contributor count (100 by default). 43 | 44 | Set to 0 for all contributors. Max avatar count is 500. (Only available for `markupgo`) 45 | :: 46 | 47 | ::field{name="circleSize" type="number"} 48 | Size of contributor circle (40 by default) (Only available for `markupgo`) 49 | :: 50 | 51 | ::field{name="circleSpacing" type="number"} 52 | Spacing between contributor circles (6 by default) (Only available for `markupgo`) 53 | :: 54 | 55 | ::field{name="circleRadius" type="number"} 56 | Radius of contributor circle (40 by default) (Only available for `markupgo`) 57 | :: 58 | 59 | ::field{name="center" type="boolean"} 60 | Center the contributor circles (false by default) (Only available for `markupgo`) 61 | :: 62 | 63 | ::field{name="markupGoLogo" type="boolean"} 64 | Show markupGo logo for credits. 65 | :: 66 | 67 | ::field{name="width" type="number"} 68 | Width of the image (890 by default) (Only available for `markupgo`) 69 | :: 70 | 71 | ::field{name="anon" type="boolean"} 72 | Include anonymous users (false by default) 73 | :: 74 | 75 | ::field{name="author" type="string"} 76 | Comma separated list of github usersnames. 77 | :: 78 | 79 | ::field{name="license" type="string"} 80 | Name of license. 81 | :: 82 | 83 | :: 84 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from "pathe"; 2 | import type { Generator } from "./generator"; 3 | import type { AutomdResult } from "./automd"; 4 | 5 | export interface Config { 6 | /** 7 | * The working directory 8 | * 9 | * @default "." (current directory) 10 | */ 11 | dir?: string; 12 | 13 | /** 14 | * Name or path to the input file or files with glob patterns. 15 | * 16 | * @default "README.md" 17 | */ 18 | input?: string | string[]; 19 | 20 | /** 21 | * Name or path of the output files. If not provided, the input file will be overwritten. 22 | * 23 | * @default input 24 | */ 25 | output?: string; 26 | 27 | /** 28 | * Ignore patterns if input is a glob pattern 29 | * 30 | * @default ["node_modules", "dist", "/.*"] 31 | */ 32 | ignore?: string[]; 33 | 34 | /** 35 | * Watch for changes in input files and regenerate output 36 | */ 37 | watch?: boolean; 38 | 39 | /** 40 | * Watch callback 41 | */ 42 | onWatch?: (event: { results: AutomdResult[]; time: number }) => void; 43 | 44 | /** Custom generators */ 45 | generators?: Record; 46 | } 47 | 48 | const RESOLVED_CONFIG_SYMBOL = Symbol("automdConfig"); 49 | 50 | export type ResolvedConfig = { [P in keyof Config]-?: Config[P] } & { 51 | [RESOLVED_CONFIG_SYMBOL]: true; 52 | input: string[]; 53 | output?: string; 54 | }; 55 | 56 | export function resolveConfig( 57 | config?: Config | ResolvedConfig, 58 | ): ResolvedConfig { 59 | if (config && RESOLVED_CONFIG_SYMBOL in config) { 60 | return config as ResolvedConfig; 61 | } 62 | 63 | const _config = { 64 | dir: ".", 65 | input: "README.md", 66 | generators: {}, 67 | [RESOLVED_CONFIG_SYMBOL]: true, 68 | ...config, 69 | }; 70 | 71 | _config.dir = resolve(_config.dir); 72 | 73 | _config.input = ( 74 | Array.isArray(_config.input) ? _config.input : [_config.input] 75 | ).filter(Boolean); 76 | 77 | return _config; 78 | } 79 | 80 | export async function loadConfig( 81 | dir = ".", 82 | overrides: Config, 83 | ): Promise { 84 | const { loadConfig } = await import("c12"); 85 | 86 | dir = resolve(dir); 87 | 88 | const { config } = await loadConfig({ 89 | cwd: dir, 90 | name: "automd", 91 | dotenv: true, 92 | overrides, 93 | defaults: { 94 | ignore: ["**/node_modules", "**/dist", "**/.*"], 95 | dir, 96 | }, 97 | }); 98 | 99 | return resolveConfig(config as Config); 100 | } 101 | -------------------------------------------------------------------------------- /docs/2.generators/badges.md: -------------------------------------------------------------------------------- 1 | # badges 2 | 3 | The `badges` generator generates badges for the latest npm version, npm download statistics, code coverage, and bundle size. 4 | 5 | ## Example 6 | 7 | 8 | 9 | ### Input 10 | 11 | 12 | 13 | 14 | ### Output 15 | 16 | 17 | 18 | [![npm version](https://img.shields.io/npm/v/defu?color=yellow)](https://npmjs.com/package/defu) 19 | [![npm downloads](https://img.shields.io/npm/dm/defu?color=yellow)](https://npm.chart.dev/defu) 20 | [![bundle size](https://img.shields.io/bundlephobia/minzip/defu?color=yellow)](https://bundlephobia.com/package/defu) 21 | [![install size](https://badgen.net/packagephobia/install/defu?color=yellow)](https://packagephobia.com/result?p=defu) 22 | [![codecov](https://img.shields.io/codecov/c/gh/unjs/automd?color=yellow)](https://codecov.io/gh/unjs/automd) 23 | [![license](https://img.shields.io/github/license/unjs/automd?color=yellow)](https://github.com/unjs/automd/blob/main/LICENSE) 24 | 25 | 26 | 27 | 28 | 29 | ## Arguments 30 | 31 | ::field-group 32 | 33 | ::field{name="name" type="string"} 34 | The npm package name. By default tries to infer from `package.json` 35 | :: 36 | 37 | ::field{name="github" type="string"} 38 | Github repository name. By default tries to infer from `package.json` 39 | :: 40 | 41 | ::field{name="license" type="boolean"} 42 | Show license badge (requires `github`) 43 | :: 44 | 45 | ::field{name="licenseBranch" type="string"} 46 | Branch to use for license badge defaults to `main` 47 | :: 48 | 49 | ::field{name="bundlephobia" type="boolean"} 50 | Show [Bundlephobia](https://bundlephobia.com/) badge (requires `name`) 51 | :: 52 | 53 | ::field{name="codecov" type="boolean"} 54 | Enable [Codecov](https://codecov.io) badge (requires `github`) 55 | :: 56 | 57 | ::field{name="no-npmDownloads" type="boolean"} 58 | Hide npm downloads badge 59 | :: 60 | 61 | ::field{name="no-npmVersion" type="boolean"} 62 | Hide npm version badge 63 | :: 64 | 65 | ::field{name="provider" type="string"} 66 | Can be one of `shields` (for [shields.io](https://shields.io/)) or `badgen` / `badgenClassic` (for [badgen.net](https://badgen.net/)). Default is `badgen`. 67 | :: 68 | 69 | :: 70 | 71 | > [!TIP] 72 | > You can use additional args `color`, `labelColor` to customize style. For provider specific params, use `styleParams`. 73 | -------------------------------------------------------------------------------- /src/generators/pm.ts: -------------------------------------------------------------------------------- 1 | import { md } from "mdbox"; 2 | import { defineGenerator } from "../generator"; 3 | import { getPkg } from "../_utils"; 4 | 5 | const INSTALL_COMMANDS = [ 6 | ["npm", "install"], 7 | ["yarn", "add"], 8 | ["pnpm", "add"], 9 | ["bun", "install"], 10 | ["deno", "install", " --dev", "npm:"], 11 | ] as const; 12 | 13 | const NYPM_COMMAND = ["npx nypm", "install"] as const; 14 | 15 | const RUN_COMMANDS = [ 16 | ["npm", "npx "], 17 | ["pnpm", "pnpm dlx "], 18 | ["bun", "bunx "], 19 | ["deno", "deno run -A npm:"], 20 | ] as const; 21 | 22 | export const pmInstall = defineGenerator({ 23 | name: "pm-install", 24 | async generate({ config, args }) { 25 | const { name, version } = await getPkg(config.dir, args); 26 | 27 | if (!name) { 28 | return { 29 | contents: "", 30 | }; 31 | } 32 | 33 | let versionSuffix = ""; 34 | if (args.version) { 35 | versionSuffix = 36 | typeof args.version === "string" ? `@${args.version}` : `@^${version}`; 37 | } 38 | 39 | const commands = 40 | args.auto === false 41 | ? INSTALL_COMMANDS 42 | : [NYPM_COMMAND, ...INSTALL_COMMANDS]; 43 | 44 | const contents = commands.map( 45 | ([cmd, install, dev = " -D", pkgPrefix = ""]) => 46 | // prettier-ignore 47 | `# ${cmd.includes("nypm") ? "✨ Auto-detect" : cmd}\n${cmd} ${install}${args.dev ? dev : (args.global ? "g" : "")} ${pkgPrefix}${name}${versionSuffix}`, 48 | ); 49 | 50 | if ((args.separate ?? false) === false) { 51 | return { 52 | contents: md.codeBlock(contents.join("\n\n"), "sh"), 53 | }; 54 | } 55 | 56 | return { 57 | contents: contents.map((cmd) => md.codeBlock(cmd, "sh")).join("\n\n"), 58 | }; 59 | }, 60 | }); 61 | 62 | export const pmX = defineGenerator({ 63 | name: "pm-x", 64 | async generate({ config, args }) { 65 | const { name, version } = await getPkg(config.dir, args); 66 | 67 | if (!name) { 68 | return { 69 | contents: "", 70 | }; 71 | } 72 | 73 | let versionSuffix = ""; 74 | if (args.version) { 75 | versionSuffix = 76 | typeof args.version === "string" ? `@${args.version}` : `@${version}`; 77 | } 78 | 79 | const contents = RUN_COMMANDS.map( 80 | ([pm, cmd]) => 81 | `# ${pm}\n${cmd}${name}${versionSuffix}${args.args ? ` ${args.args}` : ""}`, 82 | ); 83 | 84 | if ((args.separate ?? false) === false) { 85 | return { 86 | contents: md.codeBlock(contents.join("\n\n"), "sh"), 87 | }; 88 | } 89 | 90 | return { 91 | contents: contents.map((cmd) => md.codeBlock(cmd, "sh")).join("\n\n"), 92 | }; 93 | }, 94 | }); 95 | -------------------------------------------------------------------------------- /test/transform.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from "vitest"; 2 | import { transform } from "../src"; 3 | 4 | describe("transform", () => { 5 | it("basic transform works", async () => { 6 | const input = `\n\n`; 7 | 8 | const result = await transform(input, { 9 | generators: { 10 | test: { 11 | name: "test", 12 | generate({ args }) { 13 | return { contents: JSON.stringify({ args }) }; 14 | }, 15 | }, 16 | }, 17 | }); 18 | 19 | expect(result.hasChanged).toBe(true); 20 | expect(result.contents).toMatchInlineSnapshot(` 21 | " 22 | 23 | {"args":{"foo":"bar"}} 24 | 25 | " 26 | `); 27 | 28 | expect(result.updates).toHaveLength(1); 29 | expect(result.updates[0].block).toMatchInlineSnapshot(` 30 | { 31 | "_loc": { 32 | "end": 46, 33 | "start": 0, 34 | }, 35 | "contents": " 36 | 37 | ", 38 | "generator": "test", 39 | "loc": { 40 | "end": 30, 41 | "start": 28, 42 | }, 43 | "rawArgs": "foo=bar", 44 | } 45 | `); 46 | }); 47 | 48 | describe("unwrap", () => { 49 | it("manual unwrap", async () => { 50 | const input = `foo\n\n\nbaz`; 51 | 52 | const result = await transform(input, { 53 | generators: { 54 | test: { 55 | name: "test", 56 | generate() { 57 | return { 58 | contents: `bar`, 59 | unwrap: true, 60 | }; 61 | }, 62 | }, 63 | }, 64 | }); 65 | 66 | expect(result.contents).toMatchInlineSnapshot(` 67 | "foo 68 | bar 69 | baz" 70 | `); 71 | }); 72 | it("auto unwrap", async () => { 73 | const input = `a\n\n\nd`; 74 | 75 | const result = await transform(input, { 76 | generators: { 77 | test: { 78 | name: "test", 79 | generate() { 80 | return { 81 | contents: `b\n\n\nc`, 82 | }; 83 | }, 84 | }, 85 | }, 86 | }); 87 | 88 | expect(result.contents).toMatchInlineSnapshot(` 89 | "a 90 | b 91 | 92 | 93 | --- 94 | 95 | _🤖 auto updated with [automd](https://automd.unjs.io) (last updated: now)_ 96 | 97 | 98 | c 99 | d" 100 | `); 101 | }); 102 | }); 103 | }); 104 | -------------------------------------------------------------------------------- /src/_parse.ts: -------------------------------------------------------------------------------- 1 | import { destr } from "destr"; 2 | import { camelCase } from "scule"; 3 | 4 | export interface Block { 5 | /** 6 | * The name of the generator to use for updates. 7 | */ 8 | generator: string; 9 | 10 | /** 11 | * The arguments that are passed to the generator. 12 | */ 13 | rawArgs: string; 14 | 15 | /** 16 | * The current content of the block. 17 | */ 18 | contents: string; 19 | 20 | /** 21 | * The location of the content in the original document. 22 | */ 23 | loc: { start: number; end: number }; 24 | 25 | /** 26 | * The location including the automd comments. 27 | */ 28 | _loc: { start: number; end: number }; 29 | } 30 | 31 | /** 32 | * Searches a markdown document for special sections that `automd` can update. 33 | * 34 | * @param md - The markdown document as a string. 35 | * @returns an array of blocks that can be updated automatically. {@link Block} 36 | */ 37 | export function findBlocks(md: string): Block[] { 38 | const blocks: Block[] = []; 39 | 40 | // Regex is stateful, so we need to reset it 41 | const AUTOMD_RE = 42 | /^(?)(?.+?)(?^)/gimsu; 43 | 44 | for (const match of md.matchAll(AUTOMD_RE)) { 45 | if (match.index === undefined || !match.groups) { 46 | continue; 47 | } 48 | 49 | const start = match.index + match.groups.open.length; 50 | const end = start + match.groups.contents.length; 51 | 52 | blocks.push({ 53 | generator: match.groups.generator, 54 | rawArgs: match.groups.args, 55 | contents: match.groups.contents, 56 | loc: { start, end }, 57 | _loc: { start: match.index, end: match.index + match[0].length }, 58 | }); 59 | } 60 | 61 | return blocks; 62 | } 63 | 64 | /** 65 | * Checks if a markdown document contains sections that can be automatically updated. 66 | * 67 | * @param md - The markdown document as a string. 68 | * @returns true if there are `automd` sections, false otherwise. 69 | */ 70 | export function containsAutomd(md: string) { 71 | return /^`, 119 | issues: [error], 120 | }; 121 | } 122 | 123 | const context: GenerateContext = { 124 | args, 125 | config, 126 | block, 127 | transform: (contents: string) => transform(contents, config, url), 128 | url, 129 | }; 130 | 131 | try { 132 | const result = (await generator.generate(context)) as GenerateResult; 133 | 134 | if (!result.unwrap && containsAutomd(result.contents)) { 135 | result.unwrap = true; 136 | } 137 | if (result.unwrap) { 138 | const nestedRes = await context.transform(result.contents); 139 | result.contents = nestedRes.contents; 140 | // TODO: inherit time, issues, etc. 141 | if (nestedRes.hasIssues) { 142 | result.issues = [ 143 | ...(result.issues || []), 144 | ...nestedRes.updates.flatMap((u) => u.result.issues || []), 145 | ].filter(Boolean); 146 | } 147 | } 148 | 149 | return result; 150 | } catch (error: any) { 151 | return { 152 | contents: ``, 153 | issues: [error], 154 | }; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/generators/badges.ts: -------------------------------------------------------------------------------- 1 | import { image, link } from "mdbox"; 2 | import { defineGenerator } from "../generator"; 3 | import { getPkg } from "../_utils"; 4 | 5 | type BadgeType = keyof typeof badgeTypes; 6 | type BadgeProvider = Record; 7 | 8 | const badgeTypes = { 9 | npmVersion: { 10 | name: "npm version", 11 | to: "https://npmjs.com/package/{name}", 12 | }, 13 | npmDownloads: { 14 | name: "npm downloads", 15 | to: "https://npm.chart.dev/{name}", 16 | }, 17 | bundlephobia: { 18 | name: "bundle size", 19 | to: "https://bundlephobia.com/package/{name}", 20 | }, 21 | bundlejs: { 22 | name: "bundle size", 23 | to: "https://bundlejs.com/?q={name}", 24 | }, 25 | packagephobia: { 26 | name: "install size", 27 | to: "https://packagephobia.com/result?p={name}", 28 | }, 29 | codecov: { 30 | name: "codecov", 31 | to: "https://codecov.io/gh/{github}", 32 | }, 33 | license: { 34 | name: "license", 35 | to: "https://github.com/{github}/blob/{licenseBranch}/LICENSE", 36 | }, 37 | }; 38 | 39 | const badgeProviders = >{ 40 | // https://shields.io/badges/static-badge 41 | shields: { 42 | npmVersion: "https://img.shields.io/npm/v/{name}", 43 | npmDownloads: "https://img.shields.io/npm/dm/{name}", 44 | bundlephobia: "https://img.shields.io/bundlephobia/minzip/{name}", 45 | packagephobia: "https://badgen.net/packagephobia/install/{name}", // https://github.com/badges/shields/issues/1701 46 | bundlejs: "https://img.shields.io/bundlejs/size/{name}", 47 | codecov: "https://img.shields.io/codecov/c/gh/{github}", 48 | license: "https://img.shields.io/github/license/{github}", 49 | }, 50 | // https://badgen.net/help 51 | badgen: { 52 | npmVersion: "https://flat.badgen.net/npm/v/{name}", 53 | npmDownloads: "https://flat.badgen.net/npm/dm/{name}", 54 | bundlephobia: "https://flat.badgen.net/bundlephobia/minzip/{name}", 55 | bundlejs: false, // https://github.com/badgen/badgen/issues/82 56 | packagephobia: "https://flat.badgen.net/packagephobia/install/{name}", 57 | codecov: "https://flat.badgen.net/codecov/c/github/{github}", 58 | license: "https://flat.badgen.net/github/license/{github}", 59 | }, 60 | badgenClassic: { 61 | npmVersion: "https://badgen.net/npm/v/{name}", 62 | npmDownloads: "https://badgen.net/npm/dm/{name}", 63 | bundlephobia: "https://badgen.net/bundlephobia/minzip/{name}", 64 | bundlejs: false, // https://github.com/badgen/badgen/issues/82 65 | packagephobia: "https://badgen.net/packagephobia/install/{name}", 66 | codecov: "https://badgen.net/codecov/c/github/{github}", 67 | license: "https://badgen.net/github/license/{github}", 68 | }, 69 | }; 70 | 71 | export const badges = defineGenerator({ 72 | name: "badges", 73 | async generate({ config, args }) { 74 | const pkg = await getPkg(config.dir, args); 75 | const ctx: Record = { 76 | name: pkg.name, 77 | github: pkg.github, 78 | licenseBranch: "main", 79 | ...args, 80 | }; 81 | 82 | const fillStr = (str: string) => 83 | str.replace(/{(\w+)}/g, (_, key) => ctx[key] || ""); 84 | 85 | const provider = badgeProviders[args.provider] || badgeProviders.shields; 86 | const providerParams = Object.entries({ 87 | color: args.color, 88 | labelColor: args.labelColor, 89 | ...args.styleParams, 90 | }) 91 | .filter(([, value]) => value) 92 | .map(([key, value]) => `${key}=${encodeURIComponent(value as string)}`) 93 | .join("&"); 94 | 95 | const badges = { 96 | npmVersion: { 97 | enabled: ctx.name && args.npmVersion !== false, 98 | ...badgeTypes.npmVersion, 99 | }, 100 | npmDownloads: { 101 | enabled: ctx.name && args.npmDownloads !== false, 102 | ...badgeTypes.npmDownloads, 103 | }, 104 | bundlephobia: { 105 | enabled: args.bundlephobia && ctx.name, 106 | ...badgeTypes.bundlephobia, 107 | }, 108 | bundlejs: { 109 | enabled: args.bundlejs && ctx.name, 110 | ...badgeTypes.bundlejs, 111 | }, 112 | packagephobia: { 113 | enabled: args.packagephobia && ctx.name, 114 | ...badgeTypes.packagephobia, 115 | }, 116 | codecov: { 117 | enabled: args.codecov && ctx.github, 118 | ...badgeTypes.codecov, 119 | }, 120 | license: { 121 | enabled: args.license && ctx.github, 122 | ...badgeTypes.license, 123 | }, 124 | } as const; 125 | 126 | const md: string[] = []; 127 | 128 | for (const [badgeType, badge] of Object.entries(badges)) { 129 | if (!badge.enabled || !provider[badgeType as BadgeType]) { 130 | continue; 131 | } 132 | const to = fillStr(badge.to); 133 | const imgURL = 134 | fillStr(provider[badgeType as BadgeType] as string) + 135 | (providerParams ? `?${providerParams}` : ""); 136 | md.push(link(to, image(imgURL, badge.name))); 137 | } 138 | 139 | return { 140 | contents: md.join("\n"), 141 | }; 142 | }, 143 | }); 144 | -------------------------------------------------------------------------------- /docs/.docs/public/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | 9 | 10 | 11 | 13 | 15 | 17 | 18 | 19 | 20 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 53 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /src/automd.ts: -------------------------------------------------------------------------------- 1 | import { existsSync, promises as fsp } from "node:fs"; 2 | import { resolve, relative, dirname } from "pathe"; 3 | import type { SubscribeCallback } from "@parcel/watcher"; 4 | import { pathToFileURL } from "mlly"; 5 | import { debounce } from "perfect-debounce"; 6 | import type { Config, ResolvedConfig } from "./config"; 7 | import { type TransformResult, transform } from "./transform"; 8 | import { loadConfig } from "./config"; 9 | 10 | export interface AutomdResult extends TransformResult { 11 | input: string; 12 | output: string; 13 | } 14 | 15 | /** 16 | * Describes what you get back from the `automd` function. 17 | */ 18 | export interface AutomdReturn { 19 | /** 20 | * A list of the changes made to the file(s) by `automd`. 21 | */ 22 | results: AutomdResult[]; 23 | 24 | /** 25 | * How long it took to make the changes, in milliseconds. 26 | */ 27 | time: number; 28 | 29 | /** 30 | * The resolved configuration that were used for these changes. 31 | */ 32 | config: ResolvedConfig; 33 | 34 | /** 35 | * If you started watching the file(s) for changes, this function can be called to stop watching. 36 | */ 37 | unwatch?: () => void | Promise; 38 | } 39 | 40 | /** 41 | * Scans a markdown file looking for special comments. 42 | * These comments tell the function to add or update certain parts of the file automatically. 43 | * You can change how this works by giving it different settings in the `_config` option. 44 | * 45 | * @param _config - The settings to use for the update process. See {@link Config}. 46 | * @returns - An object containing the results of the update, including any changes made and any problems found. See {@link AutomdReturn}. 47 | * 48 | * @see https://automd.unjs.io/guide 49 | */ 50 | export async function automd(_config: Config = {}): Promise { 51 | const start = performance.now(); 52 | const config = await loadConfig(_config.dir, _config); 53 | 54 | let inputFiles = config.input; 55 | if (inputFiles.some((i) => i.includes("*"))) { 56 | const { glob } = await import("tinyglobby"); 57 | inputFiles = await glob(inputFiles, { 58 | cwd: config.dir, 59 | absolute: false, 60 | onlyFiles: true, 61 | ignore: config.ignore, 62 | }); 63 | } else { 64 | inputFiles = inputFiles 65 | .map((i) => resolve(config.dir, i)) 66 | .filter((i) => existsSync(i)) 67 | .map((i) => relative(config.dir, i)); 68 | } 69 | const multiFiles = inputFiles.length > 1; 70 | 71 | const cache: ResultCache = new Map(); 72 | 73 | const results = await Promise.all( 74 | inputFiles.map((i) => _automd(i, config, multiFiles, cache)), 75 | ); 76 | 77 | let unwatch; 78 | if (config.watch) { 79 | unwatch = await _watch(inputFiles, config, multiFiles, cache); 80 | } 81 | 82 | const time = performance.now() - start; 83 | 84 | return { 85 | time, 86 | results, 87 | config, 88 | unwatch, 89 | }; 90 | } 91 | 92 | // -- internal -- 93 | 94 | type ResultCache = Map; 95 | 96 | async function _automd( 97 | relativeInput: string, 98 | config: ResolvedConfig, 99 | multiFiles: boolean, 100 | cache: ResultCache, 101 | ): Promise { 102 | const start = performance.now(); 103 | const input = resolve(config.dir, relativeInput); 104 | const contents = await fsp.readFile(input, "utf8"); 105 | 106 | const cachedResult = await cache.get(input); 107 | if (cachedResult?.contents === contents) { 108 | cachedResult.time = performance.now() - start; 109 | return cachedResult; 110 | } 111 | 112 | const transformResult = await transform( 113 | contents, 114 | config, 115 | pathToFileURL(input), 116 | ); 117 | 118 | const output = multiFiles 119 | ? resolve(config.dir, config.output || ".", relativeInput) 120 | : resolve(config.dir, config.output || relativeInput); 121 | 122 | await fsp.mkdir(dirname(output), { recursive: true }); 123 | await fsp.writeFile(output, transformResult.contents, "utf8"); 124 | 125 | const result: AutomdResult = { 126 | input, 127 | output, 128 | ...transformResult, 129 | }; 130 | cache.set(input, result); 131 | result.time = performance.now() - start; 132 | return result; 133 | } 134 | 135 | async function _watch( 136 | inputFiles: string[], 137 | config: ResolvedConfig, 138 | multiFiles: boolean, 139 | cache: ResultCache, 140 | ) { 141 | const watcher = await import("@parcel/watcher"); 142 | 143 | const watchCb: SubscribeCallback = debounce(async (_err, events) => { 144 | const filesToUpdate = events 145 | .map((e) => relative(config.dir, e.path)) 146 | .filter((p) => inputFiles.includes(p)); 147 | const start = performance.now(); 148 | const results = await Promise.all( 149 | filesToUpdate.map((f) => _automd(f, config, multiFiles, cache)), 150 | ); 151 | const time = performance.now() - start; 152 | if (config.onWatch) { 153 | config.onWatch({ results, time }); 154 | } 155 | }); 156 | 157 | const subscription = await watcher.subscribe(config.dir, watchCb, { 158 | ignore: config.ignore, 159 | }); 160 | 161 | process.on("SIGINT", () => { 162 | subscription.unsubscribe(); 163 | }); 164 | 165 | return subscription.unsubscribe; 166 | } 167 | -------------------------------------------------------------------------------- /test/fixture/OUTPUT.md: -------------------------------------------------------------------------------- 1 | # Automd built-in generator fixtures 2 | 3 | ## `badges` 4 | 5 | 6 | 7 | [![npm version](https://img.shields.io/npm/v/automd)](https://npmjs.com/package/automd) 8 | [![npm downloads](https://img.shields.io/npm/dm/automd)](https://npm.chart.dev/automd) 9 | [![bundle size](https://img.shields.io/bundlephobia/minzip/automd)](https://bundlephobia.com/package/automd) 10 | [![install size](https://badgen.net/packagephobia/install/automd)](https://packagephobia.com/result?p=automd) 11 | 12 | 13 | 14 | ## `pm-x` 15 | 16 | 17 | 18 | ```sh 19 | # npm 20 | npx automd . 21 | 22 | # pnpm 23 | pnpm dlx automd . 24 | 25 | # bun 26 | bunx automd . 27 | 28 | # deno 29 | deno run -A npm:automd . 30 | ``` 31 | 32 | 33 | 34 | ## `pm-install` 35 | 36 | 37 | 38 | ```sh 39 | # ✨ Auto-detect 40 | npx nypm install -D automd 41 | ``` 42 | 43 | ```sh 44 | # npm 45 | npm install -D automd 46 | ``` 47 | 48 | ```sh 49 | # yarn 50 | yarn add -D automd 51 | ``` 52 | 53 | ```sh 54 | # pnpm 55 | pnpm add -D automd 56 | ``` 57 | 58 | ```sh 59 | # bun 60 | bun install -D automd 61 | ``` 62 | 63 | ```sh 64 | # deno 65 | deno install --dev npm:automd 66 | ``` 67 | 68 | 69 | 70 | ## `jsdocs` 71 | 72 | 73 | 74 | ### `config` 75 | 76 | #### `checked` 77 | 78 | - **Type**: `boolean` 79 | - **Default**: `false` 80 | 81 | checked state 82 | 83 | #### `dimensions` 84 | 85 | Configure the dimensions 86 | 87 | **Example:** 88 | 89 | ```js 90 | { width: 10, height: 10 } 91 | ``` 92 | 93 | ##### `height` 94 | 95 | - **Type**: `number` 96 | - **Default**: `10` 97 | 98 | Height in px 99 | 100 | ##### `width` 101 | 102 | - **Type**: `number` 103 | - **Default**: `10` 104 | 105 | Width in px 106 | 107 | #### `name` 108 | 109 | - **Type**: `string` 110 | - **Default**: `"default"` 111 | 112 | The name of the configuration 113 | 114 | #### `price` 115 | 116 | - **Type**: `number` 117 | - **Default**: `12.5` 118 | 119 | The price 120 | 121 | #### `tags` 122 | 123 | - **Type**: `array` 124 | - **Default**: `["tag1",null]` 125 | 126 | A list of tags 127 | 128 | ### `sendMessage(message, date, flash?)` 129 | 130 | Send a message 131 | 132 | This is another description of the function that spans multiple lines. 133 | 134 | Again, this is another description of the function that spans multiple lines. 135 | 136 | **Example:** 137 | 138 | ```js 139 | sendMessage("Hello", "7/1/1995", false); // => "OK" 140 | ``` 141 | 142 | 143 | 144 | ## `jsimport` 145 | 146 | 147 | 148 | **ESM** (Node.js, Bun, Deno) 149 | 150 | ```js 151 | import { foo, bar } from "pkg"; 152 | ``` 153 | 154 | **CommonJS** (Legacy Node.js) 155 | 156 | ```js 157 | const { foo, bar } = require("pkg"); 158 | ``` 159 | 160 | **CDN** (Deno and Browsers) 161 | 162 | ```js 163 | import { foo, bar } from "https://esm.sh/pkg"; 164 | ``` 165 | 166 | 167 | 168 | ## `with-automd` 169 | 170 | 171 | 172 | --- 173 | 174 | _🤖 auto updated with [automd](https://automd.unjs.io)_ 175 | 176 | 177 | 178 | ## `fetch` 179 | 180 | 181 | 182 | ## The Lazy Coder's Guide to Programming 183 | 184 | Programming can be hard. But fear not! With the power of copy-paste, you can conquer any coding challenge without breaking a sweat. Just remember: if it works once, it'll work a thousand times. Who needs original code anyway? 185 | 186 | When your code doesn't work, don't blame yourself. It's clearly the compiler's fault for not understanding your genius. Remember, the more error messages you get, the closer you are to becoming a programming master. 187 | 188 | Why waste time solving problems when someone else has already done it for you? Stack Overflow is your best friend, your mentor, and your savior. Just make sure to upvote the answers that save your bacon. 189 | 190 | 191 | 192 | ## `file` 193 | 194 | 195 | 196 | ## The Lazy Coder's Guide to Programming 197 | 198 | Programming can be hard. But fear not! With the power of copy-paste, you can conquer any coding challenge without breaking a sweat. Just remember: if it works once, it'll work a thousand times. Who needs original code anyway? 199 | 200 | When your code doesn't work, don't blame yourself. It's clearly the compiler's fault for not understanding your genius. Remember, the more error messages you get, the closer you are to becoming a programming master. 201 | 202 | 203 | 204 | ## `contributors` 205 | 206 | 207 | 208 | Published under the [MIT](https://github.com/unjs/automd/blob/main/LICENSE) license. 209 | Made by [@pi0](https://github.com/pi0) and [community](https://github.com/unjs/automd/graphs/contributors) 💛 210 |

211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | Published under the [MIT](https://github.com/unjs/automd/blob/main/LICENSE) license. 220 | Made by [@pi0](https://github.com/pi0) and [community](https://github.com/unjs/automd/graphs/contributors) 💛 221 |

222 | 223 | 224 | 225 | 226 | 227 | -------------------------------------------------------------------------------- /src/generators/jsdocs.ts: -------------------------------------------------------------------------------- 1 | import type { Schema } from "untyped"; 2 | import { titleCase } from "scule"; 3 | import { defineGenerator } from "../generator"; 4 | import { resolvePath } from "../_utils"; 5 | 6 | type RenderOptions = { 7 | group?: string | string[]; 8 | defaultGroup?: string; 9 | }; 10 | 11 | export const jsdocs = defineGenerator({ 12 | name: "jsdocs", 13 | async generate({ config, args, url }) { 14 | const { loadSchema } = await import("untyped/loader"); 15 | const fullPath = resolvePath(args.src, { url, dir: config.dir }); 16 | 17 | const schema = await loadSchema(fullPath, { 18 | jiti: { 19 | // TODO: untyped should be able to reuse same jiti instance to avoid race conditions 20 | fsCache: false, 21 | moduleCache: false, 22 | }, 23 | }); 24 | 25 | return { 26 | contents: _render( 27 | schema, 28 | args as RenderOptions, 29 | Number.parseInt(args.headingLevel) || 2, 30 | ) 31 | .join("\n") 32 | .replace(/\n{3,}/g, "\n\n"), 33 | }; 34 | }, 35 | }); 36 | 37 | // -- main renderer -- 38 | 39 | function _render(schema: Schema, opts: RenderOptions, headingLevel: number) { 40 | const sections = Object.create(null) as Record; 41 | for (const [key, keySchema] of Object.entries(schema.properties || {})) { 42 | const section = _renderSection(key, keySchema, opts, headingLevel + 1); 43 | if (!section) { 44 | continue; 45 | } 46 | sections[section.group] = sections[section.group] || []; 47 | sections[section.group].push([section.heading, section.lines]); 48 | } 49 | 50 | const lines: string[] = []; 51 | 52 | const sortedGroups = Object.keys(sections).sort((a, b) => { 53 | if (a === "") { 54 | return 1; 55 | } 56 | if (b === "") { 57 | return -1; 58 | } 59 | return a.localeCompare(b); 60 | }); 61 | for (const group of sortedGroups) { 62 | if (group) { 63 | lines.push(`\n${"#".repeat(headingLevel)} ${titleCase(group)}\n`); 64 | } 65 | const sortedSections = sections[group].sort((a, b) => 66 | a[0].localeCompare(b[0]), 67 | ); 68 | for (const section of sortedSections) { 69 | const heading = `\n${"#".repeat(headingLevel + 1)} ${section[0]}\n`; 70 | lines.push(heading, ...section[1]); 71 | } 72 | } 73 | 74 | return lines; 75 | } 76 | 77 | // --- section renderer --- 78 | 79 | function _renderSection( 80 | key: string, 81 | schema: Schema, 82 | opts: RenderOptions, 83 | headingLevel: number, 84 | ) { 85 | // Parse tag annotations 86 | const tags = _parseTags(schema.tags); 87 | 88 | // Ignore deprecated and intenral functions 89 | if (tags.some((t) => t.tag === "@deprecated" || t.tag === "@internal")) { 90 | return; 91 | } 92 | 93 | // Find group 94 | const group = 95 | tags.find((t) => t.tag === "@group")?.contents || opts.defaultGroup || ""; 96 | 97 | // Filter by group if specified 98 | if ( 99 | opts.group && 100 | (typeof opts.group === "string" 101 | ? group !== opts.group 102 | : !opts.group.includes(group)) 103 | ) { 104 | return; 105 | } 106 | 107 | let heading = `\`${key}\``; 108 | const lines: string[] = []; 109 | 110 | if (schema.type === "function") { 111 | // Function signature in heading 112 | heading = `\`${_generateFunctionSig(key, schema)}\``; 113 | } else if (schema.type !== "object") { 114 | // JS value 115 | lines.push( 116 | `- **Type**: \`${schema.markdownType || schema.tsType || schema.type}\``, 117 | ); 118 | if ("default" in schema) { 119 | lines.push(`- **Default**: \`${JSON.stringify(schema.default)}\``); 120 | } 121 | lines.push(""); 122 | } 123 | 124 | // Add body 125 | lines.push(..._renderBody(schema)); 126 | 127 | // Render example tags 128 | for (const tag of tags) { 129 | if (tag.tag === "@example") { 130 | const codeBlock = tag.contents.startsWith("`") 131 | ? tag.contents 132 | : `\`\`\`ts\n${tag.contents}\n\`\`\``; 133 | lines.push("", "**Example:**", "", codeBlock); 134 | } 135 | } 136 | 137 | // Add object properties 138 | if (schema.type === "object") { 139 | lines.push(..._render(schema, opts, headingLevel)); 140 | } 141 | 142 | return { 143 | heading, 144 | lines, 145 | group, 146 | }; 147 | } 148 | 149 | // -- body --- 150 | 151 | function _renderBody(schema: Schema) { 152 | const lines: string[] = []; 153 | if (schema.title) { 154 | lines.push(schema.title.trim()); 155 | } 156 | if (schema.title && schema.description) { 157 | // Insert an empty line between the title and the description to separate them. 158 | lines.push(""); 159 | } 160 | if (schema.description) { 161 | // Insert an empty line between each line of the description that contains a newline. 162 | lines.push( 163 | ...schema.description 164 | .split("\n") 165 | .map((line) => line.trim()) 166 | .join("\n\n") 167 | .split("\n"), 168 | ); 169 | } 170 | 171 | return lines; 172 | } 173 | 174 | // --- tag parsing --- 175 | 176 | function _parseTags(lines: string[] = []) { 177 | const parsedTags: { tag: string; contents: string }[] = []; 178 | 179 | let tag = ""; 180 | let contentLines: string[] = []; 181 | 182 | for (const line of lines.join("\n").split("\n")) { 183 | if (line.startsWith("@")) { 184 | if (tag) { 185 | parsedTags.push({ 186 | tag, 187 | contents: contentLines.join("\n"), 188 | }); 189 | } 190 | const [_tag, ...rest] = line.split(" "); 191 | tag = _tag; 192 | contentLines = rest; 193 | } else { 194 | contentLines.push(line); 195 | } 196 | } 197 | 198 | if (tag) { 199 | parsedTags.push({ tag, contents: contentLines.join("\n") }); 200 | } 201 | 202 | return parsedTags; 203 | } 204 | 205 | // --- function signature --- 206 | 207 | function _generateFunctionSig(name: string, meta: Schema) { 208 | return `${name}(${(meta.args || []) 209 | .map((arg) => { 210 | let str = arg.name; 211 | if (arg.optional) { 212 | str += "?"; 213 | } 214 | const tsType = _simpleArgType(arg.tsType); 215 | if (tsType) { 216 | str += `: ${tsType}`; 217 | } 218 | return str; 219 | }) 220 | .join(", ")})`; 221 | } 222 | 223 | function _simpleArgType(tsType = "") { 224 | return tsType 225 | .split(/\s*\|\s*/) 226 | .filter((t) => t && t !== "object" && t.startsWith("{")) 227 | .map((ot) => 228 | ot 229 | .split(/\s*[,;]\s*/g) 230 | .map((p) => p.replaceAll(/\s*:\s*(string|boolean|number)/g, "")) 231 | .join(", "), 232 | ) 233 | .join(" | "); 234 | } 235 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | ## v0.4.2 5 | 6 | [compare changes](https://github.com/unjs/automd/compare/v0.4.1...v0.4.2) 7 | 8 | ## v0.4.1 9 | 10 | [compare changes](https://github.com/unjs/automd/compare/v0.4.0...v0.4.1) 11 | 12 | ### 🩹 Fixes 13 | 14 | - **pm-install:** Use `pnpm add` instead of `pnpm install` ([#122](https://github.com/unjs/automd/pull/122)) 15 | - **pm, pm-install:** Add `npm:` prefix for deno ([#115](https://github.com/unjs/automd/pull/115)) 16 | - **jsimport:** Bun doesn't support url imports ([#112](https://github.com/unjs/automd/pull/112)) 17 | - Create missing output dir ([#106](https://github.com/unjs/automd/pull/106)) 18 | 19 | ### 📖 Documentation 20 | 21 | - Update config section ([#107](https://github.com/unjs/automd/pull/107)) 22 | 23 | ### 🏡 Chore 24 | 25 | - Update deps ([a63bb3e](https://github.com/unjs/automd/commit/a63bb3e)) 26 | 27 | ### ❤️ Contributors 28 | 29 | - Daniel Rentz ([@danielrentz](https://github.com/danielrentz)) 30 | - Yvbopeng ([@yvbopeng](https://github.com/yvbopeng)) 31 | - Huseeiin ([@huseeiin](https://github.com/huseeiin)) 32 | - Keito ([@mst-mkt](https://github.com/mst-mkt)) 33 | - Selemon Brahanu ([@selemondev](https://github.com/selemondev)) 34 | - Pooya Parsa ([@pi0](https://github.com/pi0)) 35 | 36 | ## v0.4.0 37 | 38 | [compare changes](https://github.com/unjs/automd/compare/v0.3.12...v0.4.0) 39 | 40 | ### 🚀 Enhancements 41 | 42 | - **file:** Trim contents by default ([#81](https://github.com/unjs/automd/pull/81)) 43 | - **file:** Support `lines` arg to limit content ([#46](https://github.com/unjs/automd/pull/46)) 44 | - **pm-install:** Support `global` arg ([#53](https://github.com/unjs/automd/pull/53)) 45 | - Add support for `markupgo` provider in contributors generator ([#67](https://github.com/unjs/automd/pull/67)) 46 | 47 | ### 🩹 Fixes 48 | 49 | - **badges:** Add packagephobia badge from badgen ([441c1df](https://github.com/unjs/automd/commit/441c1df)) 50 | 51 | ### 💅 Refactors 52 | 53 | - Migrate globby to tinyglobby ([607981d](https://github.com/unjs/automd/commit/607981d)) 54 | - Update to untyped v2 ([8cf342d](https://github.com/unjs/automd/commit/8cf342d)) 55 | 56 | ### 📦 Build 57 | 58 | - ⚠️ Esm-only dist ([162abd8](https://github.com/unjs/automd/commit/162abd8)) 59 | 60 | ### 🏡 Chore 61 | 62 | - Update deps ([43eaf8c](https://github.com/unjs/automd/commit/43eaf8c)) 63 | - Update major deps ([9d498f7](https://github.com/unjs/automd/commit/9d498f7)) 64 | - Update ci ([f71953c](https://github.com/unjs/automd/commit/f71953c)) 65 | - Lint ([a20f2ea](https://github.com/unjs/automd/commit/a20f2ea)) 66 | - Update snapshot ([3d30099](https://github.com/unjs/automd/commit/3d30099)) 67 | - Fix typo ([70a8bd0](https://github.com/unjs/automd/commit/70a8bd0)) 68 | - Apply automated fixes ([1dc4f8e](https://github.com/unjs/automd/commit/1dc4f8e)) 69 | - Update test ([0a8ed83](https://github.com/unjs/automd/commit/0a8ed83)) 70 | - Update tsconfig ([f6e5dd8](https://github.com/unjs/automd/commit/f6e5dd8)) 71 | - Update docs ([01397b0](https://github.com/unjs/automd/commit/01397b0)) 72 | - Apply automated fixes ([1dd1048](https://github.com/unjs/automd/commit/1dd1048)) 73 | - Fix lint:fix script ([f6947de](https://github.com/unjs/automd/commit/f6947de)) 74 | 75 | ### ✅ Tests 76 | 77 | - Fix typo ([#85](https://github.com/unjs/automd/pull/85)) 78 | 79 | #### ⚠️ Breaking Changes 80 | 81 | - ⚠️ Esm-only dist ([162abd8](https://github.com/unjs/automd/commit/162abd8)) 82 | 83 | ### ❤️ Contributors 84 | 85 | - Pooya Parsa ([@pi0](https://github.com/pi0)) 86 | - Abdullah 87 | - Hugo Richard ([@HugoRCD](https://github.com/HugoRCD)) 88 | - Horu ([@HigherOrderLogic](https://github.com/HigherOrderLogic)) 89 | - Daiki ([@k1tikurisu](https://github.com/k1tikurisu)) 90 | - Patryk Tomczyk ([@patzick](https://github.com/patzick)) 91 | 92 | ## v0.3.12 93 | 94 | [compare changes](https://github.com/unjs/automd/compare/v0.3.11...v0.3.12) 95 | 96 | ### 🩹 Fixes 97 | 98 | - **jsdocs:** Disable jiti fs cache and module cache to avoid race conditions ([4c7138b](https://github.com/unjs/automd/commit/4c7138b)) 99 | 100 | ### ❤️ Contributors 101 | 102 | - Pooya Parsa ([@pi0](http://github.com/pi0)) 103 | 104 | ## v0.3.11 105 | 106 | [compare changes](https://github.com/unjs/automd/compare/v0.3.10...v0.3.11) 107 | 108 | ### 🩹 Fixes 109 | 110 | - **transform:** Filter empty issues and keep stack trace ([83845d6](https://github.com/unjs/automd/commit/83845d6)) 111 | 112 | ### 🏡 Chore 113 | 114 | - **release:** V0.3.10 ([b88f28c](https://github.com/unjs/automd/commit/b88f28c)) 115 | - Add automd to `lint:fix` script ([1ed252e](https://github.com/unjs/automd/commit/1ed252e)) 116 | - Update dependencies ([0c038a2](https://github.com/unjs/automd/commit/0c038a2)) 117 | 118 | ### ❤️ Contributors 119 | 120 | - Pooya Parsa ([@pi0](http://github.com/pi0)) 121 | 122 | ## v0.3.10 123 | 124 | [compare changes](https://github.com/unjs/automd/compare/v0.3.9...v0.3.10) 125 | 126 | ### 💅 Refactors 127 | 128 | - **badges:** Link to `npm.chart.dev` for npm downloads ([#75](https://github.com/unjs/automd/pull/75)) 129 | 130 | ### 🏡 Chore 131 | 132 | - Update deps ([3ad5dd2](https://github.com/unjs/automd/commit/3ad5dd2)) 133 | 134 | ### ❤️ Contributors 135 | 136 | - Pooya Parsa ([@pi0](http://github.com/pi0)) 137 | - Sébastien Chopin 138 | 139 | ## v0.3.9 140 | 141 | [compare changes](https://github.com/unjs/automd/compare/v0.3.8...v0.3.9) 142 | 143 | ### 🚀 Enhancements 144 | 145 | - Add support for deno as package manager ([#74](https://github.com/unjs/automd/pull/74)) 146 | 147 | ### 🏡 Chore 148 | 149 | - Update dependencies ([0828a0b](https://github.com/unjs/automd/commit/0828a0b)) 150 | 151 | ### ❤️ Contributors 152 | 153 | - Pooya Parsa ([@pi0](http://github.com/pi0)) 154 | - Bartek Iwańczuk 155 | 156 | ## v0.3.8 157 | 158 | [compare changes](https://github.com/unjs/automd/compare/v0.3.7...v0.3.8) 159 | 160 | ### 🚀 Enhancements 161 | 162 | - Upgrade c12 with jiti v2 with esm support ([a42d4d2](https://github.com/unjs/automd/commit/a42d4d2)) 163 | 164 | ### 🩹 Fixes 165 | 166 | - `version` should be obtained automatically when set to `true` ([#59](https://github.com/unjs/automd/pull/59)) 167 | 168 | ### 📖 Documentation 169 | 170 | - Add jsdocs for main exports ([#55](https://github.com/unjs/automd/pull/55)) 171 | 172 | ### 🏡 Chore 173 | 174 | - Apply automated fixes ([5ce5ba3](https://github.com/unjs/automd/commit/5ce5ba3)) 175 | - Update deps ([cde3b3a](https://github.com/unjs/automd/commit/cde3b3a)) 176 | - Update eslint to v9 ([9e68077](https://github.com/unjs/automd/commit/9e68077)) 177 | 178 | ### ❤️ Contributors 179 | 180 | - Byron ([@byronogis](http://github.com/byronogis)) 181 | - Pooya Parsa ([@pi0](http://github.com/pi0)) 182 | - Max ([@onmax](http://github.com/onmax)) 183 | 184 | ## v0.3.7 185 | 186 | [compare changes](https://github.com/unjs/automd/compare/v0.3.6...v0.3.7) 187 | 188 | ### 🚀 Enhancements 189 | 190 | - **badges:** Support `bundlejs` ([0ab578e](https://github.com/unjs/automd/commit/0ab578e)) 191 | 192 | ### 💅 Refactors 193 | 194 | - **badges:** Switch to `shields` by default ([308381c](https://github.com/unjs/automd/commit/308381c)) 195 | 196 | ### 🏡 Chore 197 | 198 | - Update docs ([cc8b1c8](https://github.com/unjs/automd/commit/cc8b1c8)) 199 | 200 | ### ❤️ Contributors 201 | 202 | - Pooya Parsa ([@pi0](http://github.com/pi0)) 203 | 204 | ## v0.3.6 205 | 206 | [compare changes](https://github.com/unjs/automd/compare/v0.3.5...v0.3.6) 207 | 208 | ### 🚀 Enhancements 209 | 210 | - **file:** Allow rendering in code block ([fbb4003](https://github.com/unjs/automd/commit/fbb4003)) 211 | 212 | ### 🩹 Fixes 213 | 214 | - **pm-x:** Make version optional ([ffada82](https://github.com/unjs/automd/commit/ffada82)) 215 | - **parser:** Support unicode ([21ed0e1](https://github.com/unjs/automd/commit/21ed0e1)) 216 | - **parser:** Support unicode ([7ed127a](https://github.com/unjs/automd/commit/7ed127a)) 217 | - **watcher:** Debounce event handling ([0ad9c81](https://github.com/unjs/automd/commit/0ad9c81)) 218 | - **pm-i:** Don't modify global array! ([977c2c3](https://github.com/unjs/automd/commit/977c2c3)) 219 | - Respect multiline description ([#39](https://github.com/unjs/automd/pull/39)) 220 | 221 | ### 💅 Refactors 222 | 223 | - Switch to mdbox ([5698d0d](https://github.com/unjs/automd/commit/5698d0d)) 224 | 225 | ### 📖 Documentation 226 | 227 | - Use `field-group` & `field` component in args section ([#34](https://github.com/unjs/automd/pull/34)) 228 | - Update `file` to `input` for cli usage ([#38](https://github.com/unjs/automd/pull/38)) 229 | - Update undocs ([f3c90e3](https://github.com/unjs/automd/commit/f3c90e3)) 230 | - Update `src` values to use relative path ([bbe3c12](https://github.com/unjs/automd/commit/bbe3c12)) 231 | 232 | ### 🏡 Chore 233 | 234 | - Upate docs ([5fe253e](https://github.com/unjs/automd/commit/5fe253e)) 235 | - **release:** V0.3.5 ([cd31db1](https://github.com/unjs/automd/commit/cd31db1)) 236 | - Format docs ([cbf800f](https://github.com/unjs/automd/commit/cbf800f)) 237 | - Apply automated fixes ([3ec3668](https://github.com/unjs/automd/commit/3ec3668)) 238 | - Remove unused code ([#40](https://github.com/unjs/automd/pull/40)) 239 | 240 | ### ❤️ Contributors 241 | 242 | - Pooya Parsa ([@pi0](http://github.com/pi0)) 243 | - Estéban ([@Barbapapazes](http://github.com/Barbapapazes)) 244 | - Christian Preston ([@cpreston321](http://github.com/cpreston321)) 245 | 246 | ## v0.3.5 247 | 248 | [compare changes](https://github.com/unjs/automd/compare/v0.3.4...v0.3.5) 249 | 250 | ### 🩹 Fixes 251 | 252 | - **pm-x:** Make version optional ([ffada82](https://github.com/unjs/automd/commit/ffada82)) 253 | 254 | ### 🏡 Chore 255 | 256 | - **release:** V0.3.4 ([988535d](https://github.com/unjs/automd/commit/988535d)) 257 | - Apply automated fixes ([6c6ee1e](https://github.com/unjs/automd/commit/6c6ee1e)) 258 | - Upate docs ([5fe253e](https://github.com/unjs/automd/commit/5fe253e)) 259 | 260 | ### ❤️ Contributors 261 | 262 | - Pooya Parsa ([@pi0](http://github.com/pi0)) 263 | 264 | ## v0.3.4 265 | 266 | [compare changes](https://github.com/unjs/automd/compare/v0.3.3...v0.3.4) 267 | 268 | ### 🩹 Fixes 269 | 270 | - **pm-install:** Disable version by default ([f59c5a6](https://github.com/unjs/automd/commit/f59c5a6)) 271 | 272 | ### 📖 Documentation 273 | 274 | - Improve auto format ([1fc788a](https://github.com/unjs/automd/commit/1fc788a)) 275 | 276 | ### 🏡 Chore 277 | 278 | - Apply automated fixes ([669567b](https://github.com/unjs/automd/commit/669567b)) 279 | 280 | ### ✅ Tests 281 | 282 | - Disable version in snapshots ([b7ce756](https://github.com/unjs/automd/commit/b7ce756)) 283 | 284 | ### ❤️ Contributors 285 | 286 | - Pooya Parsa ([@pi0](http://github.com/pi0)) 287 | 288 | ## v0.3.3 289 | 290 | [compare changes](https://github.com/unjs/automd/compare/v0.3.2...v0.3.3) 291 | 292 | ### 🚀 Enhancements 293 | 294 | - Auto normalize key casings ([bc0e4d2](https://github.com/unjs/automd/commit/bc0e4d2)) 295 | - **automd:** Expose `unwatch` ([6ee0d6b](https://github.com/unjs/automd/commit/6ee0d6b)) 296 | - Improve jsdocs ([d2ee600](https://github.com/unjs/automd/commit/d2ee600)) 297 | - Resolve relative to file url ([4790e7c](https://github.com/unjs/automd/commit/4790e7c)) 298 | 299 | ### 🩹 Fixes 300 | 301 | - **config:** Add `dir` to defaults ([4017016](https://github.com/unjs/automd/commit/4017016)) 302 | - **with-automd:** Hide last update by default ([96e2ade](https://github.com/unjs/automd/commit/96e2ade)) 303 | - **cli:** Split input patterns ([efe6285](https://github.com/unjs/automd/commit/efe6285)) 304 | - **config:** Extend default ignores ([ea0667a](https://github.com/unjs/automd/commit/ea0667a)) 305 | 306 | ### 📖 Documentation 307 | 308 | - Update ([3552482](https://github.com/unjs/automd/commit/3552482)) 309 | - Explicitly add automd dep ([f876ff4](https://github.com/unjs/automd/commit/f876ff4)) 310 | - Add scule dep ([44fc486](https://github.com/unjs/automd/commit/44fc486)) 311 | - Remove additional dependency requirement ([5b0b197](https://github.com/unjs/automd/commit/5b0b197)) 312 | - Fix github repo ([a7beba1](https://github.com/unjs/automd/commit/a7beba1)) 313 | 314 | ### 🏡 Chore 315 | 316 | - Apply automated fixes ([29ca7d3](https://github.com/unjs/automd/commit/29ca7d3)) 317 | - Set automd config for repo ([361cd50](https://github.com/unjs/automd/commit/361cd50)) 318 | - Remove dependency on stub ([41cab79](https://github.com/unjs/automd/commit/41cab79)) 319 | - Avoid depending automd config on src ([224aad6](https://github.com/unjs/automd/commit/224aad6)) 320 | - Disable global setup for now ([41a621d](https://github.com/unjs/automd/commit/41a621d)) 321 | 322 | ### ✅ Tests 323 | 324 | - Integrate with automd on repo ([c66a576](https://github.com/unjs/automd/commit/c66a576)) 325 | - Fail on issues ([40c6fea](https://github.com/unjs/automd/commit/40c6fea)) 326 | - Add `hanging-process` reporter ([4ab612b](https://github.com/unjs/automd/commit/4ab612b)) 327 | 328 | ### ❤️ Contributors 329 | 330 | - Pooya Parsa ([@pi0](http://github.com/pi0)) 331 | 332 | ## v0.3.2 333 | 334 | [compare changes](https://github.com/unjs/automd/compare/v0.3.1...v0.3.2) 335 | 336 | ### 🚀 Enhancements 337 | 338 | - Contributors generator ([f627d63](https://github.com/unjs/automd/commit/f627d63)) 339 | - **with-automd:** `separator` option ([7e5d3a6](https://github.com/unjs/automd/commit/7e5d3a6)) 340 | 341 | ### 🩹 Fixes 342 | 343 | - Fix auto updated message ([8eaaba9](https://github.com/unjs/automd/commit/8eaaba9)) 344 | 345 | ### 📖 Documentation 346 | 347 | - Use quote for args for more clarity ([6b261b9](https://github.com/unjs/automd/commit/6b261b9)) 348 | 349 | ### 🏡 Chore 350 | 351 | - **release:** V0.3.1 ([9f60d8d](https://github.com/unjs/automd/commit/9f60d8d)) 352 | - Apply automated fixes ([91bb79a](https://github.com/unjs/automd/commit/91bb79a)) 353 | - Update readme ([36ce791](https://github.com/unjs/automd/commit/36ce791)) 354 | - Update readme ([f74e9ff](https://github.com/unjs/automd/commit/f74e9ff)) 355 | 356 | ### ❤️ Contributors 357 | 358 | - Pooya Parsa ([@pi0](http://github.com/pi0)) 359 | 360 | ## v0.3.1 361 | 362 | [compare changes](https://github.com/unjs/automd/compare/v0.3.0...v0.3.1) 363 | 364 | ### 🚀 Enhancements 365 | 366 | - **badges:** Support packagephobia ([79cb700](https://github.com/unjs/automd/commit/79cb700)) 367 | - `js-import` generator ([89a4bf0](https://github.com/unjs/automd/commit/89a4bf0)) 368 | - Add `with-automd` generator ([3df4a62](https://github.com/unjs/automd/commit/3df4a62)) 369 | - **fetch:** Support `gh:` prefix ([b12087e](https://github.com/unjs/automd/commit/b12087e)) 370 | - Support generator unwrap ([3d3422f](https://github.com/unjs/automd/commit/3d3422f)) 371 | - `file` generator ([3ca4924](https://github.com/unjs/automd/commit/3ca4924)) 372 | 373 | ### 🩹 Fixes 374 | 375 | - **jsdocs:** Trim last line ([7259a71](https://github.com/unjs/automd/commit/7259a71)) 376 | - **badges:** Respect `npmVersion` and `npmDownloads` ([01e49f4](https://github.com/unjs/automd/commit/01e49f4)) 377 | - **jsimport:** Wrap with `printWith` of `80` ([a65b0c3](https://github.com/unjs/automd/commit/a65b0c3)) 378 | - **transform:** Always trim before replace ([c2eb2a9](https://github.com/unjs/automd/commit/c2eb2a9)) 379 | 380 | ### 💅 Refactors 381 | 382 | - Rename `js-import` to `jsimport` ([bc9a221](https://github.com/unjs/automd/commit/bc9a221)) 383 | 384 | ### 📖 Documentation 385 | 386 | - Use shorthands in examples ([2a6b4de](https://github.com/unjs/automd/commit/2a6b4de)) 387 | 388 | ### 🏡 Chore 389 | 390 | - **release:** V0.3.0 ([610dbb3](https://github.com/unjs/automd/commit/610dbb3)) 391 | - Apply automated fixes ([0919539](https://github.com/unjs/automd/commit/0919539)) 392 | - Update docs ([c79b0e6](https://github.com/unjs/automd/commit/c79b0e6)) 393 | - Update snapshot ([c65ce22](https://github.com/unjs/automd/commit/c65ce22)) 394 | - Update readme ([cbb521d](https://github.com/unjs/automd/commit/cbb521d)) 395 | - Apply automated fixes ([b93f840](https://github.com/unjs/automd/commit/b93f840)) 396 | - Add STORY.md ([1aa13eb](https://github.com/unjs/automd/commit/1aa13eb)) 397 | - Update dev script ([1b39861](https://github.com/unjs/automd/commit/1b39861)) 398 | - Use `gh:` proto for fetch examples ([804ee57](https://github.com/unjs/automd/commit/804ee57)) 399 | 400 | ### ✅ Tests 401 | 402 | - Add format test ([0d0dfa1](https://github.com/unjs/automd/commit/0d0dfa1)) 403 | 404 | ### ❤️ Contributors 405 | 406 | - Pooya Parsa ([@pi0](http://github.com/pi0)) 407 | 408 | ## v0.3.0 409 | 410 | [compare changes](https://github.com/unjs/automd/compare/v0.2.0...v0.3.0) 411 | 412 | ### 🚀 Enhancements 413 | 414 | - `pm-i` alias ([#22](https://github.com/unjs/automd/pull/22)) 415 | - Support `separate` for pm generators ([#24](https://github.com/unjs/automd/pull/24)) 416 | - Did you mean? ([#25](https://github.com/unjs/automd/pull/25)) 417 | - `license` badge ([#27](https://github.com/unjs/automd/pull/27)) 418 | - ⚠️ Allow to specify `input` and `output` ([92baec6](https://github.com/unjs/automd/commit/92baec6)) 419 | - Multi file input/output ([#21](https://github.com/unjs/automd/pull/21)) 420 | - Allow extending `ignore` patterns ([e4ac409](https://github.com/unjs/automd/commit/e4ac409)) 421 | - Support watcher ([13e391b](https://github.com/unjs/automd/commit/13e391b)) 422 | - Pass transform function to generators for sub-generation ([1700a9a](https://github.com/unjs/automd/commit/1700a9a)) 423 | - `fetch` generator ([155dfc0](https://github.com/unjs/automd/commit/155dfc0)) 424 | 425 | ### 🔥 Performance 426 | 427 | - Resolve config once ([62a757a](https://github.com/unjs/automd/commit/62a757a)) 428 | 429 | ### 🩹 Fixes 430 | 431 | - Graceful issue handling ([c02dc1b](https://github.com/unjs/automd/commit/c02dc1b)) 432 | - Return original content if no changes ([49421d3](https://github.com/unjs/automd/commit/49421d3)) 433 | - **parser:** Check `` to be start of a line ([03a71a5](https://github.com/unjs/automd/commit/03a71a5)) 434 | 435 | ### 💅 Refactors 436 | 437 | - Improve badges ([db2fb78](https://github.com/unjs/automd/commit/db2fb78)) 438 | - ⚠️ Split transform utils ([014838b](https://github.com/unjs/automd/commit/014838b)) 439 | - Avoid `node:` static imports ([2ffa57b](https://github.com/unjs/automd/commit/2ffa57b)) 440 | - Simplify `node:` usage for now ([40e49d9](https://github.com/unjs/automd/commit/40e49d9)) 441 | - Improve cli impl ([9c6e747](https://github.com/unjs/automd/commit/9c6e747)) 442 | 443 | ### 📖 Documentation 444 | 445 | - Various fixes ([#16](https://github.com/unjs/automd/pull/16)) 446 | - Use unjs badge colors ([#17](https://github.com/unjs/automd/pull/17)) 447 | - Fix syntax example ([#18](https://github.com/unjs/automd/pull/18)) 448 | - Update with auto generated examples! ([375367a](https://github.com/unjs/automd/commit/375367a)) 449 | 450 | ### 🏡 Chore 451 | 452 | - **release:** V0.2.0 ([f2cc711](https://github.com/unjs/automd/commit/f2cc711)) 453 | - Fix readme ([#15](https://github.com/unjs/automd/pull/15)) 454 | - Initiate docs ([8801856](https://github.com/unjs/automd/commit/8801856)) 455 | - Improve docs formatting ([816bebb](https://github.com/unjs/automd/commit/816bebb)) 456 | - Add `docs:dev` script ([bf4bdb1](https://github.com/unjs/automd/commit/bf4bdb1)) 457 | - Apply automated fixes ([2b58e08](https://github.com/unjs/automd/commit/2b58e08)) 458 | - Remove unused code ([c041248](https://github.com/unjs/automd/commit/c041248)) 459 | - Update test ([30d3346](https://github.com/unjs/automd/commit/30d3346)) 460 | - Apply automated fixes ([40e11ba](https://github.com/unjs/automd/commit/40e11ba)) 461 | - Update lockfile ([a49c629](https://github.com/unjs/automd/commit/a49c629)) 462 | - Add `docs:auto:md` command ([dc8085a](https://github.com/unjs/automd/commit/dc8085a)) 463 | - Make timings more accurate ([ac1cb8d](https://github.com/unjs/automd/commit/ac1cb8d)) 464 | 465 | ### ✅ Tests 466 | 467 | - Update tests ([7c31f11](https://github.com/unjs/automd/commit/7c31f11)) 468 | - Add snapshot test for generators ([affd5ee](https://github.com/unjs/automd/commit/affd5ee)) 469 | 470 | #### ⚠️ Breaking Changes 471 | 472 | - ⚠️ Allow to specify `input` and `output` ([92baec6](https://github.com/unjs/automd/commit/92baec6)) 473 | - ⚠️ Split transform utils ([014838b](https://github.com/unjs/automd/commit/014838b)) 474 | 475 | ### ❤️ Contributors 476 | 477 | - Pooya Parsa ([@pi0](http://github.com/pi0)) 478 | - Christian Preston ([@cpreston321](http://github.com/cpreston321)) 479 | - Uncenter 480 | 481 | ## v0.2.0 482 | 483 | [compare changes](https://github.com/unjs/automd/compare/v0.1.5...v0.2.0) 484 | 485 | ### 🚀 Enhancements 486 | 487 | - `pm-x` generator ([#11](https://github.com/unjs/automd/pull/11)) 488 | - Support config ([b05cccb](https://github.com/unjs/automd/commit/b05cccb)) 489 | - Custom generators ([c959738](https://github.com/unjs/automd/commit/c959738)) 490 | - ⚠️ Better syntax ([#13](https://github.com/unjs/automd/pull/13)) 491 | - `badges` generator ([#12](https://github.com/unjs/automd/pull/12)) 492 | 493 | ### 🩹 Fixes 494 | 495 | - Allow extended chars in automd comment ([33e440f](https://github.com/unjs/automd/commit/33e440f)) 496 | 497 | ### 💅 Refactors 498 | 499 | - Split parse utils ([2381934](https://github.com/unjs/automd/commit/2381934)) 500 | 501 | ### 🏡 Chore 502 | 503 | - Add nypm to the list ([8c41fb4](https://github.com/unjs/automd/commit/8c41fb4)) 504 | - Update dev script ([e950a09](https://github.com/unjs/automd/commit/e950a09)) 505 | - Add vitest dependency ([5b315d4](https://github.com/unjs/automd/commit/5b315d4)) 506 | - Update license ([8c8df45](https://github.com/unjs/automd/commit/8c8df45)) 507 | - Remove log ([e056ca4](https://github.com/unjs/automd/commit/e056ca4)) 508 | 509 | ### ✅ Tests 510 | 511 | - Add unit tests for parse utils ([0bf0a3c](https://github.com/unjs/automd/commit/0bf0a3c)) 512 | - Update test for keys with `-` ([c04df9d](https://github.com/unjs/automd/commit/c04df9d)) 513 | 514 | ### 🤖 CI 515 | 516 | - Enable vitest ([9ce08f4](https://github.com/unjs/automd/commit/9ce08f4)) 517 | 518 | #### ⚠️ Breaking Changes 519 | 520 | - ⚠️ Better syntax ([#13](https://github.com/unjs/automd/pull/13)) 521 | 522 | ### ❤️ Contributors 523 | 524 | - Christian Preston ([@cpreston321](http://github.com/cpreston321)) 525 | - Pooya Parsa ([@pi0](http://github.com/pi0)) 526 | - Uncenter 527 | 528 | ## v0.1.5 529 | 530 | [compare changes](https://github.com/unjs/automd/compare/v0.1.4...v0.1.5) 531 | 532 | ### 🚀 Enhancements 533 | 534 | - `pm-install` generator ([#9](https://github.com/unjs/automd/pull/9)) 535 | 536 | ### 🩹 Fixes 537 | 538 | - Remove log ([b4cdf5f](https://github.com/unjs/automd/commit/b4cdf5f)) 539 | 540 | ### 🏡 Chore 541 | 542 | - **release:** V0.1.4 ([b36fe54](https://github.com/unjs/automd/commit/b36fe54)) 543 | - Update readme ([f8ee292](https://github.com/unjs/automd/commit/f8ee292)) 544 | - Update readme ([957c995](https://github.com/unjs/automd/commit/957c995)) 545 | 546 | ### ❤️ Contributors 547 | 548 | - Christian Preston ([@cpreston321](http://github.com/cpreston321)) 549 | - Pooya Parsa ([@pi0](http://github.com/pi0)) 550 | 551 | ## v0.1.4 552 | 553 | [compare changes](https://github.com/unjs/automd/compare/v0.1.3...v0.1.4) 554 | 555 | ### 🩹 Fixes 556 | 557 | - Remove log ([b4cdf5f](https://github.com/unjs/automd/commit/b4cdf5f)) 558 | 559 | ### ❤️ Contributors 560 | 561 | - Pooya Parsa ([@pi0](http://github.com/pi0)) 562 | 563 | ## v0.1.3 564 | 565 | [compare changes](https://github.com/unjs/automd/compare/v0.1.2...v0.1.3) 566 | 567 | ### 🚀 Enhancements 568 | 569 | - **jsdocs:** Support default group ([9bdda0b](https://github.com/unjs/automd/commit/9bdda0b)) 570 | 571 | ### 🏡 Chore 572 | 573 | - Stub after release ([5d7af8f](https://github.com/unjs/automd/commit/5d7af8f)) 574 | 575 | ### ❤️ Contributors 576 | 577 | - Pooya Parsa ([@pi0](http://github.com/pi0)) 578 | 579 | ## v0.1.2 580 | 581 | [compare changes](https://github.com/unjs/automd/compare/v0.1.1...v0.1.2) 582 | 583 | ### 🚀 Enhancements 584 | 585 | - **jsdocs:** Add optional and simplified arg types for objects ([7dc55a4](https://github.com/unjs/automd/commit/7dc55a4)) 586 | - Allow desrializing args ([d49593e](https://github.com/unjs/automd/commit/d49593e)) 587 | - **jsdocs:** Allow group filter ([0e31974](https://github.com/unjs/automd/commit/0e31974)) 588 | 589 | ### 🩹 Fixes 590 | 591 | - Use title case for section titles ([d18e529](https://github.com/unjs/automd/commit/d18e529)) 592 | 593 | ### 💅 Refactors 594 | 595 | - Default `headingLevel` to 2 ([4faec97](https://github.com/unjs/automd/commit/4faec97)) 596 | 597 | ### 🏡 Chore 598 | 599 | - Add used by section ([3954be9](https://github.com/unjs/automd/commit/3954be9)) 600 | 601 | ### ❤️ Contributors 602 | 603 | - Pooya Parsa ([@pi0](http://github.com/pi0)) 604 | 605 | ## v0.1.1 606 | 607 | 608 | ### 🚀 Enhancements 609 | 610 | - **jsdocs:** Sections and sorting ([97e9b47](https://github.com/unjs/automd/commit/97e9b47)) 611 | - **jsdocs:** Upper-first headings ([78e9165](https://github.com/unjs/automd/commit/78e9165)) 612 | 613 | ### 🩹 Fixes 614 | 615 | - Skip unkown generators by default ([d28c5b2](https://github.com/unjs/automd/commit/d28c5b2)) 616 | - **jsdocs:** Sort uncategorized utils to the end ([8f30476](https://github.com/unjs/automd/commit/8f30476)) 617 | 618 | ### 💅 Refactors 619 | 620 | - Simplify cli ([2b09de1](https://github.com/unjs/automd/commit/2b09de1)) 621 | 622 | ### 📖 Documentation 623 | 624 | - Fix typos ([#7](https://github.com/unjs/automd/pull/7)) 625 | 626 | ### 🏡 Chore 627 | 628 | - Update readme ([7506739](https://github.com/unjs/automd/commit/7506739)) 629 | - Update readme ([ef8d3ac](https://github.com/unjs/automd/commit/ef8d3ac)) 630 | 631 | ### ❤️ Contributors 632 | 633 | - Pooya Parsa ([@pi0](http://github.com/pi0)) 634 | - Uncenter 635 | 636 | --------------------------------------------------------------------------------