diff --git a/package.json b/package.json index 663825d..ddb8f4f 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "defu": "^6.1.4", "destr": "^2.0.2", "didyoumean2": "^6.0.1", + "globby": "^14.0.1", "magic-string": "^0.30.7", "omark": "^0.1.0", "pathe": "^1.1.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0a88e02..bd0dc76 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,6 +23,9 @@ dependencies: didyoumean2: specifier: ^6.0.1 version: 6.0.1 + globby: + specifier: ^14.0.1 + version: 14.0.1 magic-string: specifier: ^0.30.7 version: 0.30.7 @@ -581,12 +584,10 @@ packages: dependencies: '@nodelib/fs.stat': 2.0.5 run-parallel: 1.2.0 - dev: true /@nodelib/fs.stat@2.0.5: resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} engines: {node: '>= 8'} - dev: true /@nodelib/fs.walk@1.2.8: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} @@ -594,7 +595,6 @@ packages: dependencies: '@nodelib/fs.scandir': 2.1.5 fastq: 1.17.1 - dev: true /@rollup/plugin-alias@5.1.0(rollup@3.29.4): resolution: {integrity: sha512-lpA3RZ9PdIG7qqhEfv79tBffNaoDuukFDrmhLqg9ifv99u/ehn+lOg30x2zmhf8AQqQUZaMk/B9fZraQ6/acDQ==} @@ -795,6 +795,11 @@ packages: resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} dev: true + /@sindresorhus/merge-streams@2.2.0: + resolution: {integrity: sha512-UTce8mUwUW0RikMb/eseJ7ys0BRkZVFB86orHzrfW12ZmFtym5zua8joZ4L7okH2dDFHkcFjqnZ5GocWBXOFtA==} + engines: {node: '>=18'} + dev: false + /@trysound/sax@0.2.0: resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} engines: {node: '>=10.13.0'} @@ -2311,7 +2316,6 @@ packages: glob-parent: 5.1.2 merge2: 1.4.1 micromatch: 4.0.5 - dev: true /fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} @@ -2330,7 +2334,6 @@ packages: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} dependencies: reusify: 1.0.4 - dev: true /file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} @@ -2563,6 +2566,18 @@ packages: slash: 4.0.0 dev: true + /globby@14.0.1: + resolution: {integrity: sha512-jOMLD2Z7MAhyG8aJpNOpmziMOP4rPLcc95oQPKXBazW82z+CEgPFBQvEpRUa1KeIMUJo4Wsm+q6uzO/Q/4BksQ==} + engines: {node: '>=18'} + dependencies: + '@sindresorhus/merge-streams': 2.2.0 + fast-glob: 3.3.2 + ignore: 5.3.1 + path-type: 5.0.0 + slash: 5.1.0 + unicorn-magic: 0.1.0 + dev: false + /gopd@1.0.1: resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} dependencies: @@ -2650,7 +2665,6 @@ packages: /ignore@5.3.1: resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} engines: {node: '>= 4'} - dev: true /import-fresh@3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} @@ -3093,7 +3107,6 @@ packages: /merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} - dev: true /micromatch@4.0.5: resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} @@ -3101,7 +3114,6 @@ packages: dependencies: braces: 3.0.2 picomatch: 2.3.1 - dev: true /mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} @@ -3457,6 +3469,11 @@ packages: engines: {node: '>=8'} dev: true + /path-type@5.0.0: + resolution: {integrity: sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==} + engines: {node: '>=12'} + dev: false + /pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} @@ -3831,7 +3848,6 @@ packages: /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - dev: true /rc9@2.1.1: resolution: {integrity: sha512-lNeOl38Ws0eNxpO3+wD1I9rkHGQyj1NU1jlzv4go2CtEnEQEUfqnIvZG7W+bC/aXdJ27n5x/yUjb6RoT9tko+Q==} @@ -3920,7 +3936,6 @@ packages: /reusify@1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - dev: true /rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} @@ -3985,7 +4000,6 @@ packages: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} dependencies: queue-microtask: 1.2.3 - dev: true /safe-array-concat@1.1.0: resolution: {integrity: sha512-ZdQ0Jeb9Ofti4hbt5lX3T2JcAamT9hfzYU1MNB+z/jaEbB6wfFfPIR/zEORmZqobkCCJhSjodobH6WHNmJ97dg==} @@ -4092,6 +4106,11 @@ packages: engines: {node: '>=12'} dev: true + /slash@5.1.0: + resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} + engines: {node: '>=14.16'} + dev: false + /source-map-js@1.0.2: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'} @@ -4447,6 +4466,11 @@ packages: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} dev: true + /unicorn-magic@0.1.0: + resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} + engines: {node: '>=18'} + dev: false + /universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} diff --git a/src/automd.ts b/src/automd.ts index 4bd096e..6fd17ce 100644 --- a/src/automd.ts +++ b/src/automd.ts @@ -1,27 +1,59 @@ import { existsSync, promises as fsp } from "node:fs"; +import { resolve, relative } from "pathe"; import type { Config, ResolvedConfig } from "./config"; import { TransformResult, transform } from "./transform"; import { loadConfig } from "./config"; export interface AutomdResult extends TransformResult { - config: ResolvedConfig; + _config: ResolvedConfig; + input: string; + output: string; } -export async function automd(_config: Config = {}): Promise { +export async function automd(_config: Config = {}): Promise { const config = await loadConfig(_config.dir, _config); - if (!existsSync(config.input)) { - throw new Error(`File not found: ${config.input}`); + let inputFiles = config.input; + if (inputFiles.some((i) => i.includes("*"))) { + const { globby } = await import("globby"); + inputFiles = await globby(inputFiles, { + cwd: config.dir, + absolute: false, + onlyFiles: true, + ignore: ["node_modules", "dist"], + }); + } else { + inputFiles = inputFiles + .map((i) => resolve(config.dir, i)) + .filter((i) => existsSync(i)) + .map((i) => relative(config.dir, i)); } - const contents = await fsp.readFile(config.input, "utf8"); + return Promise.all( + inputFiles.map((i) => _automd(i, config, inputFiles.length > 1)), + ); +} + +export async function _automd( + relativeInput: string, + config: ResolvedConfig, + multi: boolean, +): Promise { + const input = resolve(config.dir, relativeInput); + const contents = await fsp.readFile(input, "utf8"); const result = await transform(contents, config); - await fsp.writeFile(config.output, result.contents, "utf8"); + const output = multi + ? resolve(config.dir, config.output || ".", relativeInput) + : resolve(config.dir, config.output || relativeInput); + + await fsp.writeFile(output, result.contents, "utf8"); return { - config, + _config: config, + input, + output, ...result, }; } diff --git a/src/cli.ts b/src/cli.ts index 39bfa5f..8651e82 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,7 +1,9 @@ #!/usr/bin/env node +import { relative } from "pathe"; import { defineCommand, runMain } from "citty"; import consola from "consola"; +import { getColor } from "consola/utils"; import { name, description, version } from "../package.json"; import { automd } from "./automd"; @@ -27,20 +29,43 @@ const main = defineCommand({ }, }, async setup({ args }) { - const { updates, config } = await automd({ + const fileUpdates = await automd({ dir: args.dir, input: args.input, output: args.output, }); - if (updates.length === 0) { - consola.warn(`No updates applied to \`${config.input}\``); + if (fileUpdates.length === 0) { + consola.warn(`No files processed!`); process.exit(1); } consola.success( - `Updated \`${config.input}\` in \`${updates.length}\` sections.`, + `Automd updated in \`${relative(process.cwd(), fileUpdates[0]._config.dir)}\``, ); + + const changeTypes = { + updated: { label: "updated", color: getColor("blue") }, + noChanges: { label: "no changes", color: getColor("green") }, + alreadyUpdate: { label: "already up-to-date", color: getColor("gray") }, + }; + + for (const f of fileUpdates) { + const [input, output] = [f.input, f.output].map((i) => + relative(f._config.dir, i), + ); + const fileStr = + input === output ? ` ${input}` : ` ${input} ~> ${output}`; + + const changesStr = + f.updates.map((u) => u.block.generator).join(", ") || "-"; + + const t = + // prettier-ignore + f.updates.length === 0 ? changeTypes.alreadyUpdate : (f.hasChanged ? changeTypes.updated : changeTypes.noChanges); + + consola.log(t.color(` ─ ${fileStr} ${t.label}`)); + } }, }); diff --git a/src/config.ts b/src/config.ts index 56fb7db..038c9fa 100644 --- a/src/config.ts +++ b/src/config.ts @@ -10,16 +10,16 @@ export interface Config { dir?: string; /** - * Name or path of the input file. + * Name or path to the input file or files with glob patterns. * - * Defaults to `README.md` + * Default is `README.md`. */ - input?: string; + input?: string | string[]; /** - * Name or path of the output file. + * Name or path of the output files. * - * Defaults to the same as `input` + * Default output is same as input. */ output?: string; @@ -31,6 +31,8 @@ const RESOLVED_CONFIG_SYMBOL = Symbol("automdConfig"); export type ResolvedConfig = { [P in keyof Config]-?: Config[P] } & { [RESOLVED_CONFIG_SYMBOL]: true; + input: string[]; + output?: string; }; export function resolveConfig( @@ -49,11 +51,10 @@ export function resolveConfig( }; _config.dir = resolve(_config.dir); - _config.input = resolve(_config.dir, _config.input); - _config.output = _config.output - ? resolve(_config.dir, _config.output) - : _config.input; + _config.input = ( + Array.isArray(_config.input) ? _config.input : [_config.input] + ).filter(Boolean); return _config; }