|
1 | | -import fs from "node:fs/promises"; |
2 | | -import path from "node:path"; |
| 1 | +import * as NodeContext from "@effect/platform-node/NodeContext"; |
| 2 | +import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; |
| 3 | +import * as FileSystem from "@effect/platform/FileSystem"; |
| 4 | +import * as Path from "@effect/platform/Path"; |
| 5 | +import ansis from "ansis"; |
| 6 | +import * as Effect from "effect/Effect"; |
3 | 7 |
|
4 | 8 | import { glob } from "./utils/glob"; |
5 | 9 |
|
6 | 10 | /** |
7 | 11 | * Build script for processing and copying documentation to the website |
8 | 12 | * |
9 | | - * This script: |
| 13 | + * This script (Effect version): |
10 | 14 | * 1. Collects rule documentation from ESLint Plugins |
11 | 15 | * 2. Copies them to the website with proper naming |
12 | 16 | * 3. Processes the changelog |
13 | | - * 4. Sets up dependencies for the website build |
| 17 | + * 4. (TODO) Generates meta.json / rules data |
14 | 18 | */ |
15 | 19 |
|
16 | | -// Find all rule documentation markdown files from the various plugins |
17 | | -const docs = glob(["packages/plugins/eslint-plugin-react-*/src/rules/*.md"]); |
18 | | - |
19 | | -// TODO: Generate the meta.json file as well |
20 | | -// Process each documentation file: |
21 | | -// - Extract plugin name and rule name |
22 | | -// - Format destination path and rule title |
23 | | -// - Build arrays of file paths and rule metadata |
24 | | -const [ |
25 | | - files, |
26 | | - // rules, // Currently commented out but would contain rule metadata |
27 | | -] = Array.from(docs).reduce<readonly [[string, string][], [string, string][]]>( |
28 | | - ([files, rules], doc) => { |
| 20 | +const DOCS_GLOB = ["packages/plugins/eslint-plugin-react-*/src/rules/*.mdx"]; |
| 21 | + |
| 22 | +interface RuleMeta { |
| 23 | + name: string; |
| 24 | + title: string; |
| 25 | + destination: string; |
| 26 | + source: string; |
| 27 | +} |
| 28 | + |
| 29 | +const collectDocs = Effect.gen(function*() { |
| 30 | + const path = yield* Path.Path; |
| 31 | + const docs = yield* Effect.sync(() => glob(DOCS_GLOB)); |
| 32 | + return docs.map<RuleMeta>((doc) => { |
29 | 33 | const catename = /^packages\/plugins\/eslint-plugin-react-([^/]+)/u.exec(doc)?.[1] ?? ""; |
30 | 34 | const basename = path.parse(path.basename(doc)).name; |
31 | 35 |
|
32 | | - // Special handling for "react-x" plugin (the core plugin) |
33 | 36 | const isPluginX = catename === "x"; |
34 | 37 |
|
35 | | - // Format the rule name differently based on which plugin it belongs to |
36 | | - const name = isPluginX |
37 | | - ? basename // For react-x plugin: just use the rule name |
38 | | - : `${catename}-${basename}`; // For other plugins: prefix with category |
| 38 | + const name = isPluginX ? basename : `${catename}-${basename}`; |
| 39 | + const title = isPluginX ? basename : `${catename}/${basename}`; |
| 40 | + |
| 41 | + const destination = path.join("apps", "website", "content", "docs", "rules", `${name}.mdx`); |
| 42 | + |
| 43 | + return { |
| 44 | + name, |
| 45 | + title, |
| 46 | + destination, |
| 47 | + source: doc, |
| 48 | + }; |
| 49 | + }); |
| 50 | +}); |
| 51 | + |
| 52 | +function copyRuleDoc(meta: RuleMeta) { |
| 53 | + return Effect.gen(function*() { |
| 54 | + const fs = yield* FileSystem.FileSystem; |
| 55 | + const path = yield* Path.Path; |
| 56 | + const dir = path.dirname(meta.destination); |
| 57 | + // Ensure destination directory exists |
| 58 | + yield* fs.makeDirectory(dir, { recursive: true }); |
| 59 | + const content = yield* fs.readFileString(meta.source, "utf8"); |
| 60 | + yield* fs.writeFileString(meta.destination, content); |
| 61 | + yield* Effect.log(ansis.green(`Copied ${meta.source} -> ${meta.destination}`)); |
| 62 | + return meta; |
| 63 | + }); |
| 64 | +} |
| 65 | + |
| 66 | +const processChangelog = Effect.gen(function*() { |
| 67 | + const fs = yield* FileSystem.FileSystem; |
| 68 | + const path = yield* Path.Path; |
| 69 | + const changelogPath = "CHANGELOG.md"; |
| 70 | + const targetPath = path.join("apps", "website", "content", "docs", "changelog.md"); |
| 71 | + |
| 72 | + const source = yield* fs.readFileString(changelogPath, "utf8"); |
| 73 | + const wrapped = [ |
| 74 | + "---", |
| 75 | + "title: Changelog", |
| 76 | + "---", |
| 77 | + "", |
| 78 | + source, |
| 79 | + ].join("\n"); |
| 80 | + |
| 81 | + const dir = path.dirname(targetPath); |
| 82 | + yield* fs.makeDirectory(dir, { recursive: true }); |
| 83 | + yield* fs.writeFileString(targetPath, wrapped); |
| 84 | + yield* Effect.log(ansis.cyan(`Processed changelog -> ${targetPath}`)); |
| 85 | +}); |
39 | 86 |
|
40 | | - // Format the rule title for display purposes |
41 | | - const title = isPluginX |
42 | | - ? basename // For react-x plugin: just use the rule name |
43 | | - : `${catename}/${basename}`; // For other plugins: use category/rule format |
| 87 | +const program = Effect.gen(function*() { |
| 88 | + yield* Effect.log(ansis.bold("Processing rule documentation...")); |
44 | 89 |
|
45 | | - // Define destination path in the website content directory |
46 | | - const dest = path.join("apps", "website", "content", "docs", "rules", `${name}.mdx`); |
| 90 | + const metas = yield* collectDocs; |
47 | 91 |
|
48 | | - // Add to our accumulator arrays |
49 | | - return [[...files, [doc, dest]], [...rules, [name, title]]] as const; |
50 | | - }, |
51 | | - [[], []], |
52 | | -); |
| 92 | + yield* Effect.log( |
| 93 | + metas.length === 0 |
| 94 | + ? ansis.yellow("No documentation files found.") |
| 95 | + : `Found ${ansis.bold(metas.length.toString())} rule documentation file(s).`, |
| 96 | + ); |
53 | 97 |
|
54 | | -// Copy all documentation files to their respective destinations in parallel |
55 | | -await Promise.all(files.map(([src, dest]) => fs.copyFile(src, dest))); |
| 98 | + // Copy in parallel with limited concurrency (adjust if needed) |
| 99 | + yield* Effect.forEach(metas, copyRuleDoc, { concurrency: 8 }); |
56 | 100 |
|
57 | | -// Write rule metadata to a JSON file for the website |
58 | | -// fs.writeFileSync(path.join("apps", "website", "content", "docs", "rules", "data.json"), JSON.stringify(rules, null, 2)); |
| 101 | + // (Optional) Generate rules metadata JSON (still TODO) |
| 102 | + // const rulesData = metas.map(({ name, title }) => ({ name, title })); |
| 103 | + // const rulesDataPath = path.join("apps", "website", "content", "docs", "rules", "data.json"); |
| 104 | + // yield* FileSystem.FileSystem.flatMap(fs => |
| 105 | + // fs.makeDirectory(path.dirname(rulesDataPath), { recursive: true }).pipe( |
| 106 | + // Effect.zipRight( |
| 107 | + // fs.writeFileString(rulesDataPath, JSON.stringify(rulesData, null, 2) + "\n") |
| 108 | + // ), |
| 109 | + // Effect.tap(() => Effect.log(ansis.magenta(`Generated ${rulesDataPath}`))) |
| 110 | + // ) |
| 111 | + // ); |
59 | 112 |
|
60 | | -// Process the changelog file by adding frontmatter for the documentation system |
61 | | -const changelog = await fs.readFile("CHANGELOG.md", "utf-8"); |
| 113 | + yield* processChangelog; |
62 | 114 |
|
63 | | -const changelogWithFrontmatter = [ |
64 | | - "---", |
65 | | - "title: Changelog", |
66 | | - "---", |
67 | | - "", |
68 | | - changelog, |
69 | | -].join("\n"); |
| 115 | + yield* Effect.log(ansis.bold.green("Documentation processing completed.")); |
| 116 | +}); |
70 | 117 |
|
71 | | -// Write the processed changelog to the website content directory |
72 | | -await fs.writeFile(path.join("apps", "website", "content", "docs", "changelog.md"), changelogWithFrontmatter); |
| 118 | +program.pipe(Effect.provide(NodeContext.layer), NodeRuntime.runMain); |
0 commit comments