diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 0bda220c1..582c2161a 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -21,6 +21,7 @@ jobs: node-version: lts/* cache: 'pnpm' - run: pnpm i + - run: npm run format:description - run: npm run format - name: Commit files run: | diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index 3298f08fc..f1c95cc35 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -18,7 +18,7 @@ jobs: name: npm publish if: github.repository == 'DIYgod/RSSHub' runs-on: ubuntu-slim - timeout-minutes: 5 + timeout-minutes: 10 env: HUSKY: 0 steps: diff --git a/lib/routes/uber/blog.ts b/lib/routes/uber/blog.ts index 3dc011341..413034ef2 100644 --- a/lib/routes/uber/blog.ts +++ b/lib/routes/uber/blog.ts @@ -34,7 +34,8 @@ export const route: Route = { description: "The English blog on any of Uber's regional sites (e.g., www.uber.com/en-JP/blog) is the same engineering blog provided by this route, so language selection is not supported. This route is not for the public news blog on specific regional sites (e.g., www.uber.com/ja-JP/blog).", zh: { - description: 'uber的任何区域站点的英文blog(例如www.uber.com/en-JP/blog)都是相同的内容,正是本路由提供的engineering blog,因此本路由不提供语言选择;本路由不是uber在特定区域站点的公开新闻blog(例如www.uber.com/ja-JP/blog)', + description: + 'uber 的任何区域站点的英文 blog(例如 [www.uber.com/en-JP/blog](http://www.uber.com/en-JP/blog))都是相同的内容,正是本路由提供的 engineering blog,因此本路由不提供语言选择;本路由不是 uber 在特定区域站点的公开新闻 blog(例如 [www.uber.com/ja-JP/blog](http://www.uber.com/ja-JP/blog))', }, }; diff --git a/package.json b/package.json index 2bd522835..2e01b9f5a 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "eslint": "eslint --cache . --concurrency auto", "format": "oxlint --type-aware --fix . && oxfmt .", "format:check": "oxlint --type-aware . && oxfmt . --check", + "format:description": "tsx scripts/workflow/format-description.ts", "format:staged": "lint-staged", "lint": "oxlint --type-aware .", "prepare": "husky || true", @@ -180,6 +181,7 @@ "eslint-plugin-simple-import-sort": "13.0.0", "eslint-plugin-unicorn": "64.0.0", "eslint-plugin-yml": "3.3.2", + "fast-string-width": "3.0.2", "fs-extra": "11.3.5", "globals": "17.6.0", "got": "15.0.5", @@ -194,6 +196,9 @@ "oxlint": "1.62.0", "oxlint-plugin-eslint": "1.62.0", "oxlint-tsgolint": "0.22.1", + "remark": "15.0.1", + "remark-gfm": "4.0.1", + "remark-pangu": "2.2.0", "remark-parse": "11.0.0", "tsdown": "0.21.10", "typescript": "5.9.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index afb8b8c21..f90a9003d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -398,6 +398,9 @@ importers: eslint-plugin-yml: specifier: 3.3.2 version: 3.3.2(eslint@10.3.0(jiti@2.6.1)) + fast-string-width: + specifier: 3.0.2 + version: 3.0.2 fs-extra: specifier: 11.3.5 version: 11.3.5 @@ -440,6 +443,15 @@ importers: oxlint-tsgolint: specifier: 0.22.1 version: 0.22.1 + remark: + specifier: 15.0.1 + version: 15.0.1 + remark-gfm: + specifier: 4.0.1 + version: 4.0.1 + remark-pangu: + specifier: 2.2.0 + version: 2.2.0 remark-parse: specifier: 11.0.0 version: 11.0.0 @@ -2810,6 +2822,9 @@ packages: '@types/triple-beam@1.3.5': resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==} + '@types/unist@2.0.11': + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} @@ -3268,6 +3283,9 @@ packages: caseless@0.12.0: resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} @@ -4582,6 +4600,9 @@ packages: long@5.3.2: resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + lowercase-keys@3.0.0: resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -4635,9 +4656,39 @@ packages: markdown-table@2.0.0: resolution: {integrity: sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==} + markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + + mdast-util-find-and-replace@3.0.2: + resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} + mdast-util-from-markdown@2.0.3: resolution: {integrity: sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==} + mdast-util-gfm-autolink-literal@2.0.1: + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} + + mdast-util-gfm-footnote@2.1.0: + resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} + + mdast-util-gfm-strikethrough@2.0.0: + resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} + + mdast-util-gfm-table@2.0.0: + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} + + mdast-util-gfm-task-list-item@2.0.0: + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} + + mdast-util-gfm@3.1.0: + resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + mdast-util-to-string@4.0.0: resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} @@ -4661,6 +4712,27 @@ packages: micromark-core-commonmark@2.0.3: resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + micromark-extension-gfm-autolink-literal@2.1.0: + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} + + micromark-extension-gfm-footnote@2.1.0: + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} + + micromark-extension-gfm-strikethrough@2.1.0: + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} + + micromark-extension-gfm-table@2.1.1: + resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==} + + micromark-extension-gfm-tagfilter@2.0.0: + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} + + micromark-extension-gfm-task-list-item@2.1.0: + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} + + micromark-extension-gfm@3.0.0: + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} + micromark-factory-destination@2.0.1: resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} @@ -5023,6 +5095,10 @@ packages: pako@2.1.0: resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==} + pangu@4.0.7: + resolution: {integrity: sha512-weZKJIwwy5gjt4STGVUH9bix3BGk7wZ2ahtIypwe3e/mllsrIZIvtfLx1dPX56GcpZFOCFKmeqI1qVuB9enRzA==} + hasBin: true + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -5263,9 +5339,22 @@ packages: resolution: {integrity: sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==} hasBin: true + remark-gfm@4.0.1: + resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} + + remark-pangu@2.2.0: + resolution: {integrity: sha512-3h2UiRIbnEQR+IxYPhQ5SMbAZPobIV8ddza8U1hJLsfLranbv/cdFiwnZEgMOJYKmi+9XHLMxrAIipmIsLrzPg==} + engines: {node: '>=10'} + remark-parse@11.0.0: resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + remark-stringify@11.0.0: + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + + remark@15.0.1: + resolution: {integrity: sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A==} + repeat-string@1.6.1: resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} engines: {node: '>=0.10'} @@ -5871,9 +5960,27 @@ packages: unified@11.0.5: resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + unist-util-is@4.1.0: + resolution: {integrity: sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==} + + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + unist-util-stringify-position@4.0.0: resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + unist-util-visit-parents@3.1.1: + resolution: {integrity: sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==} + + unist-util-visit-parents@6.0.1: + resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==} + + unist-util-visit@2.0.3: + resolution: {integrity: sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==} + + unist-util-visit@5.0.0: + resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + universal-user-agent@7.0.3: resolution: {integrity: sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==} @@ -6271,6 +6378,9 @@ packages: zod@4.4.3: resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + snapshots: '@actions/core@3.0.1': @@ -8190,6 +8300,8 @@ snapshots: '@types/triple-beam@1.3.5': {} + '@types/unist@2.0.11': {} + '@types/unist@3.0.3': {} '@typescript-eslint/eslint-plugin@8.59.2(@typescript-eslint/parser@8.59.2(eslint@10.3.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.3.0(jiti@2.6.1))(typescript@5.9.3)': @@ -8634,6 +8746,8 @@ snapshots: caseless@0.12.0: {} + ccount@2.0.1: {} + chai@6.2.2: {} chalk@5.6.2: {} @@ -10136,6 +10250,8 @@ snapshots: long@5.3.2: {} + longest-streak@3.1.0: {} + lowercase-keys@3.0.0: {} lowercase-keys@4.0.1: {} @@ -10196,6 +10312,15 @@ snapshots: dependencies: repeat-string: 1.6.1 + markdown-table@3.0.4: {} + + mdast-util-find-and-replace@3.0.2: + dependencies: + '@types/mdast': 4.0.4 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.1 + mdast-util-from-markdown@2.0.3: dependencies: '@types/mdast': 4.0.4 @@ -10213,6 +10338,80 @@ snapshots: transitivePeerDependencies: - supports-color + mdast-util-gfm-autolink-literal@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.2 + micromark-util-character: 2.1.1 + + mdast-util-gfm-footnote@2.1.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-strikethrough@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-table@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-task-list-item@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm@3.1.0: + dependencies: + mdast-util-from-markdown: 2.0.3 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.1.0 + mdast-util-gfm-strikethrough: 2.0.0 + mdast-util-gfm-table: 2.0.0 + mdast-util-gfm-task-list-item: 2.0.0 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.1 + + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.0.0 + zwitch: 2.0.4 + mdast-util-to-string@4.0.0: dependencies: '@types/mdast': 4.0.4 @@ -10255,6 +10454,64 @@ snapshots: micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 + micromark-extension-gfm-autolink-literal@2.1.0: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-footnote@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-strikethrough@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-table@2.1.1: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-tagfilter@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-gfm-task-list-item@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm@3.0.0: + dependencies: + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.1 + micromark-extension-gfm-tagfilter: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + micromark-factory-destination@2.0.1: dependencies: micromark-util-character: 2.1.1 @@ -10700,6 +10957,8 @@ snapshots: pako@2.1.0: {} + pangu@4.0.7: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -10959,6 +11218,23 @@ snapshots: dependencies: jsesc: 3.1.0 + remark-gfm@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-gfm: 3.1.0 + micromark-extension-gfm: 3.0.0 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-pangu@2.2.0: + dependencies: + pangu: 4.0.7 + unist-util-is: 4.1.0 + unist-util-visit: 2.0.3 + remark-parse@11.0.0: dependencies: '@types/mdast': 4.0.4 @@ -10968,6 +11244,21 @@ snapshots: transitivePeerDependencies: - supports-color + remark-stringify@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-to-markdown: 2.1.2 + unified: 11.0.5 + + remark@15.0.1: + dependencies: + '@types/mdast': 4.0.4 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + repeat-string@1.6.1: {} request-promise-core@1.1.4(request@2.88.2): @@ -11610,10 +11901,38 @@ snapshots: trough: 2.2.0 vfile: 6.0.3 + unist-util-is@4.1.0: {} + + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position@4.0.0: dependencies: '@types/unist': 3.0.3 + unist-util-visit-parents@3.1.1: + dependencies: + '@types/unist': 2.0.11 + unist-util-is: 4.1.0 + + unist-util-visit-parents@6.0.1: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@2.0.3: + dependencies: + '@types/unist': 2.0.11 + unist-util-is: 4.1.0 + unist-util-visit-parents: 3.1.1 + + unist-util-visit@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.1 + universal-user-agent@7.0.3: {} universalify@0.2.0: {} @@ -11998,3 +12317,5 @@ snapshots: meriyah: 6.1.4 zod@4.4.3: {} + + zwitch@2.0.4: {} diff --git a/scripts/workflow/format-description.ts b/scripts/workflow/format-description.ts new file mode 100644 index 000000000..0483bea30 --- /dev/null +++ b/scripts/workflow/format-description.ts @@ -0,0 +1,193 @@ +import fs from 'node:fs/promises'; +import path from 'node:path'; + +import stringWidth from 'fast-string-width'; +import { remark } from 'remark'; +import remarkGfm from 'remark-gfm'; +import remarkPangu from 'remark-pangu'; +import typescript from 'typescript'; + +const __dirname = import.meta.dirname; +const routesDir = path.resolve(__dirname, '../../lib/routes'); + +function remarkDirectiveSpace() { + return (tree: any) => { + walkDirectiveAst(tree); + }; +} + +function walkDirectiveAst(node: any): void { + if (node.type === 'text' && typeof node.value === 'string') { + node.value = node.value.replaceAll(/^:::([A-Za-z][\w-]*)/gm, '::: $1'); + } + if (Array.isArray(node.children)) { + for (const child of node.children) { + walkDirectiveAst(child); + } + } +} + +const processor = remark() + .data('settings', { + bullet: '-', + }) + .use(remarkDirectiveSpace) + .use(remarkPangu) + .use(remarkGfm, { + stringLength: stringWidth, + }); + +interface DescriptionEdit { + start: number; + end: number; + raw: string; +} + +function getPropertyName(name: typescript.PropertyName): string { + if (typescript.isIdentifier(name) || typescript.isPrivateIdentifier(name) || typescript.isStringLiteral(name) || typescript.isNoSubstitutionTemplateLiteral(name)) { + return name.text; + } + return ''; +} + +const TARGETS: Record = { route: 'Route', namespace: 'Namespace' }; + +function isTargetTypedDeclaration(decl: typescript.VariableDeclaration): boolean { + if (!typescript.isIdentifier(decl.name)) { + return false; + } + const expectedType = TARGETS[decl.name.text]; + if (!expectedType) { + return false; + } + if (decl.type && typescript.isTypeReferenceNode(decl.type)) { + const typeName = decl.type.typeName; + if (typescript.isIdentifier(typeName) && typeName.text === expectedType) { + return true; + } + } + return false; +} + +function collectDescriptionEdits(sourceFile: typescript.SourceFile): DescriptionEdit[] { + const edits: DescriptionEdit[] = []; + + const visitObject = (obj: typescript.ObjectLiteralExpression) => { + for (const prop of obj.properties) { + if (!typescript.isPropertyAssignment(prop)) { + continue; + } + const name = getPropertyName(prop.name); + if (name === 'description') { + const init = prop.initializer; + if (typescript.isStringLiteral(init) || typescript.isNoSubstitutionTemplateLiteral(init)) { + edits.push({ + start: init.getStart(sourceFile), + end: init.getEnd(), + raw: init.text, + }); + } + } else if ((name === 'ja' || name === 'zh' || name === 'zh-TW') && typescript.isObjectLiteralExpression(prop.initializer)) { + visitObject(prop.initializer); + } + } + }; + + for (const stmt of sourceFile.statements) { + if (!typescript.isVariableStatement(stmt)) { + continue; + } + const isExported = stmt.modifiers?.some((m) => m.kind === typescript.SyntaxKind.ExportKeyword); + if (!isExported) { + continue; + } + for (const decl of stmt.declarationList.declarations) { + if (!isTargetTypedDeclaration(decl)) { + continue; + } + if (decl.initializer && typescript.isObjectLiteralExpression(decl.initializer)) { + visitObject(decl.initializer); + } + } + } + + return edits; +} + +function escapeTemplateLiteral(s: string): string { + return s + .replaceAll('\\', String.raw`\\`) + .replaceAll('`', '\\`') + .replaceAll('${', '\\${'); +} + +async function* walk(dir: string): AsyncGenerator { + const entries = await fs.readdir(dir, { withFileTypes: true }); + for (const entry of entries) { + const full = path.join(dir, entry.name); + if (entry.isDirectory()) { + yield* walk(full); + } else if (entry.isFile() && /\.tsx?$/.test(entry.name) && !entry.name.endsWith('.d.ts')) { + yield full; + } + } +} + +async function processFile(filePath: string): Promise { + const sourceText = await fs.readFile(filePath, 'utf8'); + if (!sourceText.includes('description')) { + return; + } + + const sourceFile = typescript.createSourceFile(filePath, sourceText, typescript.ScriptTarget.Latest, false, filePath.endsWith('.tsx') ? typescript.ScriptKind.TSX : typescript.ScriptKind.TS); + + const edits = collectDescriptionEdits(sourceFile); + if (edits.length === 0) { + return; + } + + edits.sort((a, b) => b.start - a.start); + + let result = sourceText; + let changed = false; + + const formattedResults = await Promise.all( + edits.map(async (edit) => { + if (!edit.raw.trim()) { + return { edit, formatted: null as string | null }; + } + const file = await processor.process(edit.raw); + return { + edit, + formatted: String(file).replace(/\n+$/, ''), + }; + }) + ); + + for (const { edit, formatted } of formattedResults) { + if (!formatted || formatted === edit.raw) { + continue; + } + + const replacement = '`' + escapeTemplateLiteral(formatted) + '`'; // oxlint handles unncessary single line template literals. + result = result.slice(0, edit.start) + replacement + result.slice(edit.end); + changed = true; + } + + if (changed) { + await fs.writeFile(filePath, result, 'utf8'); + } +} + +async function main() { + const started = performance.now(); + // @ts-ignore ts(2550) + const files: string[] = await Array.fromAsync(walk(routesDir)); + + await Promise.all(files.map((f) => processFile(f))); + + // oxlint-disable-next-line no-console + console.log(`Finished in ${Math.round(performance.now() - started)}ms on ${files.length} files.`); +} + +await main();