{{ repository.name }}: {{ repository.description }}
14 | {{ repository.name }}: {{ repository.description }}
26 | Version: {{ release.v }}
21 | 22 |URL: {{ release.url }}
23 | 24 |Zipball: {{ release.zipall }}
25 | 26 |Tarball: {{ release.tarball }}
27 | 28 |Prerelease: {{ release.prerelease }}
29 | 30 |Reactions: {{ release.reactions }}
31 | 32 |Author: {{ release.author }}
33 |Version: {{ release.v }}
29 | 30 |URL: {{ release.url }}
31 | 32 |Zipball: {{ release.zipall }}
33 | 34 |Tarball: {{ release.tarball }}
35 | 36 |Prerelease: {{ release.prerelease }}
37 | 38 |Reactions: {{ release.reactions }}
39 | 40 |Author: {{ release.author }}
41 |{{ commit.hash.slice(0, 7) }}
31 |
32 | -
33 |
34 |
35 |
36 | (base: string) => (query: Q): Promise=> { 8 | const url = joinURL('/api/_github', base, `${encodeParams(query) || 'index'}.json`) 9 | 10 | // add to nitro prerender 11 | if (process.server) { 12 | const event = useRequestEvent() 13 | event.node.res.setHeader( 14 | 'x-nitro-prerender', 15 | [event.node.res.getHeader('x-nitro-prerender'), url].filter(Boolean).join(',') 16 | ) 17 | } 18 | 19 | return $fetch(url, { responseType: 'json' }) 20 | } 21 | 22 | return { 23 | fetchRepository: _fetch ('repository'), 24 | fetchReleases: _fetch ('releases'), 25 | fetchRelease: _fetch ('releases'), 26 | fetchLastRelease: (query: GithubRepositoryOptions): Promise => 27 | _fetch ('releases')({ ...query, last: true } as any), 28 | fetchContributors: _fetch ('contributors'), 29 | fetchFileContributors: _fetch ('contributors/file'), 30 | fetchReadme: _fetch ('readme'), 31 | fetchCommits: _fetch ('commits') 32 | } 33 | } 34 | 35 | function encodeParams (params: any) { 36 | return Object.entries(params) 37 | .map(([key, value]) => `${key}_${String(value)}`) 38 | .join(':') 39 | } 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nuxtlabs/github-module", 3 | "version": "1.6.3", 4 | "license": "MIT", 5 | "private": false, 6 | "type": "module", 7 | "exports": { 8 | ".": { 9 | "import": "./dist/module.mjs", 10 | "require": "./dist/module.cjs" 11 | } 12 | }, 13 | "main": "./dist/module.cjs", 14 | "types": "./dist/types.d.ts", 15 | "files": [ 16 | "dist" 17 | ], 18 | "scripts": { 19 | "prepare": "nuxi prepare playground", 20 | "build": "nuxt-module-build", 21 | "dev": "nuxi dev playground", 22 | "dev:build": "nuxi build playground", 23 | "dev:docs": "nuxi dev docs", 24 | "build:docs": "nuxi generate docs", 25 | "lint": "eslint --ext .js,.ts,.vue .", 26 | "test": "vitest run", 27 | "prepack": "nuxt-module-build", 28 | "release": "release-it" 29 | }, 30 | "dependencies": { 31 | "@nuxt/kit": "^3.3.2", 32 | "@octokit/graphql": "^5.0.5", 33 | "@octokit/rest": "^19.0.7", 34 | "defu": "^6.1.2", 35 | "h3": "^1.6.2", 36 | "remark-gfm": "^3.0.1", 37 | "remark-github": "^11.2.4", 38 | "ufo": "^1.1.1" 39 | }, 40 | "devDependencies": { 41 | "@nuxt-themes/docus": "latest", 42 | "@nuxt/content": "latest", 43 | "@nuxt/module-builder": "latest", 44 | "@nuxt/test-utils": "^3.3.2", 45 | "@nuxtjs/eslint-config-typescript": "latest", 46 | "eslint": "latest", 47 | "nuxt": "^3.3.2", 48 | "release-it": "^15.9.3", 49 | "standard-version": "^9.5.0", 50 | "vitest": "^0.29.7" 51 | }, 52 | "packageManager": "pnpm@7.29.1", 53 | "pnpm": { 54 | "peerDependencyRules": { 55 | "allowedVersions": { 56 | "vue": "^3.2.45" 57 | }, 58 | "ignoreMissing": [ 59 | "webpack", 60 | "vue" 61 | ] 62 | } 63 | }, 64 | "release-it": { 65 | "hooks": { 66 | "before:init": [ 67 | "npm run build" 68 | ] 69 | }, 70 | "npm": { 71 | "access": "public" 72 | }, 73 | "git": { 74 | "commitMessage": "chore: release v${version}" 75 | }, 76 | "github": { 77 | "release": true, 78 | "releaseName": "v${version}" 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.yml: -------------------------------------------------------------------------------- 1 | name: "\U0001F41E Bug Report" 2 | description: Create a report to help us improve Nuxt 3 | labels: ["pending triage"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Please carefully read the contribution docs before creating a bug report 9 | 👉 https://v3.nuxtjs.org/community/reporting-bugs 10 | Please use the template below to create a minimal reproduction 11 | 👉 https://stackblitz.com/github/nuxt/starter/tree/content 12 | - type: textarea 13 | id: bug-env 14 | attributes: 15 | label: Environment 16 | description: You can use `npx nuxi info` to fill this section 17 | placeholder: Environment 18 | validations: 19 | required: true 20 | - type: textarea 21 | id: reproduction 22 | attributes: 23 | label: Reproduction 24 | description: Please provide a link to a repo that can reproduce the problem you ran into. A [**minimal reproduction**](https://v3.nuxtjs.org/community/reporting-bugs#create-a-minimal-reproduction) is required unless you are absolutely sure that the issue is obvious and the provided information is enough to understand the problem. If a report is vague (e.g. just a generic error message) and has no reproduction, it will receive a "need reproduction" label. If no reproduction is provided we might close it. 25 | placeholder: Reproduction 26 | validations: 27 | required: true 28 | - type: textarea 29 | id: bug-description 30 | attributes: 31 | label: Describe the bug 32 | description: A clear and concise description of what the bug is. If you intend to submit a PR for this issue, tell us in the description. Thanks! 33 | placeholder: Bug description 34 | validations: 35 | required: true 36 | - type: textarea 37 | id: additonal 38 | attributes: 39 | label: Additional context 40 | description: If applicable, add any other context about the problem here` 41 | - type: textarea 42 | id: logs 43 | attributes: 44 | label: Logs 45 | description: | 46 | Optional if provided reproduction. Please try not to insert an image but copy paste the log text. 47 | render: shell 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Github Module 2 | 3 | > GitHub integration for [Nuxt](https://v3.nuxtjs.org) & [Content](https://content.nuxtjs.org) 4 | 5 | ## Setup 6 | 7 | Install `@nuxtlabs/github-module` in your project: 8 | 9 | ```bash 10 | # Using Yarn 11 | yarn add --dev @nuxtlabs/github-module 12 | # Using NPM 13 | npm install --save-dev @nuxtlabs/github-module 14 | # Using PNPM 15 | pnpm add --save-dev @nuxtlabs/github-module 16 | ``` 17 | 18 | Then, add `@nuxtlabs/github-module` to the `modules` section of your `nuxt.config.ts`: 19 | 20 | ```ts 21 | import { defineNuxtConfig } from 'nuxt' 22 | 23 | export default defineNuxtConfig({ 24 | modules: [ 25 | '@nuxt/content', // Required 26 | '@nuxtlabs/github-module' 27 | ], 28 | github: { 29 | repo: 'nuxt/framework' // Or use GITHUB_REPO in .env 30 | } 31 | }) 32 | ``` 33 | 34 | Lastly, create a [personal access token](https://github.com/settings/tokens) on GitHub and add it into your `.env`: 35 | 36 | ```env 37 | GITHUB_TOKEN=' ' 38 | ``` 39 | 40 | ## Usage 41 | 42 | ```vue 43 | 46 | 47 | 48 | 49 |54 | 55 | ``` 56 | 57 | ## Options 58 | 59 | ```ts 60 | github: { 61 | repo: string, 62 | releases: false | { 63 | api: string 64 | repo: string 65 | token: string 66 | /** 67 | * Parse release notes markdown and return AST tree 68 | * 69 | * Note: This option is only available when you have `@nuxt/content` installed in your project. 70 | * 71 | * @default true 72 | */ 73 | parse: boolean 74 | } 75 | } 76 | ``` 77 | 78 | ## Development 79 | 80 | 1. Clone this repository 81 | 2. Install dependencies using `yarn install` or `npm install` 82 | 3. Generate type stubs using `yarn prepare` or `npm run prepare` 83 | 4. In `playground/.env`, add your [personal access token](https://github.com/settings/tokens) 84 | ```env 85 | GITHUB_TOKEN='50 |53 |{{ release.name }} 51 |52 | ' 86 | ``` 87 | 5. Launch playground using `yarn dev` or `npm run dev` 88 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### [1.4.7](https://github.com/nuxtlabs/github-module/compare/v1.4.6...v1.4.7) (2022-08-22) 6 | 7 | 8 | ### Features 9 | 10 | * **GithubLink:** handle contents from remote github source ([#32](https://github.com/nuxtlabs/github-module/issues/32)) ([11f88e7](https://github.com/nuxtlabs/github-module/commit/11f88e7548c96bc2040a6614f3795abe5255c4df)) 11 | 12 | 13 | ### Bug Fixes 14 | 15 | * add `remark-github` plugin to bundle ([#34](https://github.com/nuxtlabs/github-module/issues/34)) ([9d0236b](https://github.com/nuxtlabs/github-module/commit/9d0236b4de261dc87778afb9ddb13ddd303a4c4d)) 16 | 17 | ### [1.2.4](https://github.com/nuxtlabs/github-module/compare/v1.2.3...v1.2.4) (2022-05-05) 18 | 19 | ### [1.2.3](https://github.com/nuxtlabs/github-module/compare/v1.2.2...v1.2.3) (2022-05-05) 20 | 21 | 22 | ### Bug Fixes 23 | 24 | * add `remark-github` to bundle ([31fd022](https://github.com/nuxtlabs/github-module/commit/31fd02295b206de42f443245e01d38709fb2624a)) 25 | 26 | ### [1.2.2](https://github.com/nuxtlabs/github-module/compare/v1.2.1...v1.2.2) (2022-05-02) 27 | 28 | 29 | ### Bug Fixes 30 | 31 | * correct package name ([cb7dc49](https://github.com/nuxtlabs/github-module/commit/cb7dc49956182a12814f14a3df4937c645835342)) 32 | * imports ([d871f26](https://github.com/nuxtlabs/github-module/commit/d871f26ab3a7bb24aab37498afde19efbe166a7d)) 33 | 34 | ### [1.2.1](https://github.com/nuxtlabs/github-module/compare/v1.2.0...v1.2.1) (2022-05-02) 35 | 36 | 37 | ### Bug Fixes 38 | 39 | * add GITHUB_REPO and use autoimport ([03992ad](https://github.com/nuxtlabs/github-module/commit/03992ad98325456eae0af191fb4f96e2b91e63f5)) 40 | 41 | ## 1.2.0 (2022-05-02) 42 | 43 | 44 | ### Features 45 | 46 | * cache for one minute ([c45a060](https://github.com/nuxtlabs/github-module/commit/c45a060eb336d34131cf67ad635f4deb1e64e944)) 47 | * Nuxt 3 and Content 2 ([#22](https://github.com/nuxtlabs/github-module/issues/22)) ([a1d51a6](https://github.com/nuxtlabs/github-module/commit/a1d51a6b9a86b7d257763d795e3992ca09f3854a)) 48 | 49 | 50 | ### Bug Fixes 51 | 52 | * core compatibility ([cb5e792](https://github.com/nuxtlabs/github-module/commit/cb5e792ed202681a7c433b4ca5485bb92ee9787e)) 53 | * module exports ([71d0af8](https://github.com/nuxtlabs/github-module/commit/71d0af856bdd5025fa5cb3e240d7c0773e7baa78)) 54 | * refactor based on new core ([107a251](https://github.com/nuxtlabs/github-module/commit/107a251bbdf8af7ba4616460bec4f080f88e5d86)) 55 | * rename release to releases ([fe924a8](https://github.com/nuxtlabs/github-module/commit/fe924a864691d6aa389fa578cd6ba31544048fa7)) 56 | * use latest core ([cdf354a](https://github.com/nuxtlabs/github-module/commit/cdf354a363aff87843f60da43ac8e377c4a25a82)) 57 | -------------------------------------------------------------------------------- /docs/content/2.configuration.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Configuration" 3 | description: "How to configure the GitHub package." 4 | toc: false 5 | --- 6 | 7 | ::code-group 8 | 9 | ```ts [Minimal config] 10 | export default defineNuxtConfig({ 11 | github: { 12 | owner: 'nuxtlabs', 13 | repo: 'docus', 14 | branch: 'dev' 15 | }, 16 | }) 17 | ``` 18 | 19 | ```ts [Complete config] 20 | export default defineNuxtConfig({ 21 | github: { 22 | // Repository options 23 | owner: 'nuxtlabs', 24 | repo: 'docus', 25 | branch: 'dev', 26 | token: process.env.GITHUB_TOKEN, 27 | api: 'https://api.github.com', 28 | 29 | // Remark plugin (@nuxt/content integration) 30 | remarkPlugin: true, 31 | 32 | contributors: { 33 | // Scoped repository options (optional) 34 | owner: 'nuxtlabs', 35 | repo: 'docus', 36 | branch: 'dev', 37 | token: process.env.GITHUB_TOKEN, 38 | api: 'https://api.github.com', 39 | // Contributors options 40 | max: 100, 41 | }, 42 | 43 | releases: { 44 | // Scoped repository options (optional) 45 | owner: 'nuxtlabs', 46 | repo: 'docus', 47 | branch: 'dev', 48 | token: process.env.GITHUB_TOKEN, 49 | api: 'https://api.github.com', 50 | // Releases options 51 | parse: true, 52 | }, 53 | }, 54 | }) 55 | ``` 56 | 57 | :: 58 | 59 | ::alert 60 | Even if the `GITHUB_TOKEN` environment variable is not set, the GitHub package will still work. 61 | :br :br 62 | We still recommend to specify a `GITHUB_TOKEN`, especially if you are using a `private` repository. 63 | :: 64 | 65 | | **Key** | **Type** | **Default** | **Description** | 66 | | ---------------------------- | --------- | ---------------------- | --------------------------------------------------------- | 67 | | `github.owner` | `string` | | GitHub repository owner | 68 | | `github.repo` | `string` | | GitHub repository name | 69 | | `github.branch` | `string` | main | GitHub repository branch | 70 | | `github.token` | `string` | | GitHub repository token | 71 | | `github.api` | `string` | https://api.github.com | GitHub API URL | 72 | | `github.remarkPlugin` | `boolean` | `false` | Whether or not to use the `@nuxt/content` plugin | 73 | | `github.parseContents ` | `boolean` | `true` | Whether or not to parse content (for instance readme or releases) | 74 | | `github.disableCache` | `boolean` | `false` | Disable cache for data fetched from server routes | 75 | | `github.contributors` | `boolean` | `true` | Allow fetch of contributors data (create server routes) | 76 | | `github.maxContributors` | `number` | `100` | GitHub contributors max number of contributors to display | 77 | | `github.releases` | `boolean` | `true` | Allow fetch of releases data (create server routes) | 78 | -------------------------------------------------------------------------------- /.github/scripts/bump-edge.ts: -------------------------------------------------------------------------------- 1 | import { promises as fsp } from 'fs' 2 | import { execSync } from 'child_process' 3 | import { resolve } from 'pathe' 4 | import { globby } from 'globby' 5 | 6 | // Temporary forked from nuxt/framework 7 | 8 | async function loadPackage (dir: string) { 9 | const pkgPath = resolve(dir, 'package.json') 10 | const data = JSON.parse(await fsp.readFile(pkgPath, 'utf-8').catch(() => '{}')) 11 | const save = () => fsp.writeFile(pkgPath, JSON.stringify(data, null, 2) + '\n') 12 | 13 | const updateDeps = (reviver: Function) => { 14 | for (const type of ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies']) { 15 | if (!data[type]) { continue } 16 | for (const e of Object.entries(data[type])) { 17 | const dep = { name: e[0], range: e[1], type } 18 | delete data[type][dep.name] 19 | const updated = reviver(dep) || dep 20 | data[updated.type] = data[updated.type] || {} 21 | data[updated.type][updated.name] = updated.range 22 | } 23 | } 24 | } 25 | 26 | return { 27 | dir, 28 | data, 29 | save, 30 | updateDeps 31 | } 32 | } 33 | 34 | type ThenArg = T extends PromiseLike ? U : T 35 | type Package = ThenArg > 36 | 37 | async function loadWorkspace (dir: string) { 38 | const workspacePkg = await loadPackage(dir) 39 | const pkgDirs = await globby(workspacePkg.data.workspaces || [], { onlyDirectories: true }) 40 | 41 | const packages: Package[] = [workspacePkg] 42 | 43 | for (const pkgDir of pkgDirs) { 44 | const pkg = await loadPackage(pkgDir) 45 | if (!pkg.data.name) { continue } 46 | packages.push(pkg) 47 | } 48 | 49 | const find = (name: string) => { 50 | const pkg = packages.find(pkg => pkg.data.name === name) 51 | if (!pkg) { 52 | throw new Error('Workspace package not found: ' + name) 53 | } 54 | return pkg 55 | } 56 | 57 | const rename = (from: string, to: string) => { 58 | find(from).data.name = to 59 | for (const pkg of packages) { 60 | pkg.updateDeps((dep) => { 61 | if (dep.name === from && !dep.range.startsWith('npm:')) { 62 | dep.range = 'npm:' + to + '@' + dep.range 63 | } 64 | }) 65 | } 66 | } 67 | 68 | const setVersion = (name: string, newVersion: string) => { 69 | find(name).data.version = newVersion 70 | for (const pkg of packages) { 71 | pkg.updateDeps((dep) => { 72 | if (dep.name === name) { 73 | dep.range = newVersion 74 | } 75 | }) 76 | } 77 | } 78 | 79 | const save = () => Promise.all(packages.map(pkg => pkg.save())) 80 | 81 | return { 82 | dir, 83 | workspacePkg, 84 | packages, 85 | save, 86 | find, 87 | rename, 88 | setVersion 89 | } 90 | } 91 | 92 | async function main () { 93 | const workspace = await loadWorkspace(process.cwd()) 94 | 95 | const commit = execSync('git rev-parse --short HEAD').toString('utf-8').trim() 96 | const date = Math.round(Date.now() / (1000 * 60)) 97 | 98 | for (const pkg of workspace.packages.filter(p => !p.data.private)) { 99 | workspace.setVersion(pkg.data.name, `${pkg.data.version}-${date}.${commit}`) 100 | workspace.rename(pkg.data.name, pkg.data.name + '-edge') 101 | } 102 | 103 | await workspace.save() 104 | } 105 | 106 | main().catch((err) => { 107 | // eslint-disable-next-line no-console 108 | console.error(err) 109 | process.exit(1) 110 | }) 111 | -------------------------------------------------------------------------------- /src/runtime/types/repository.d.ts: -------------------------------------------------------------------------------- 1 | export interface GithubRepositoryOptions { 2 | owner?: string 3 | branch?: string 4 | repo?: string 5 | api?: string 6 | token?: string 7 | } 8 | 9 | export interface GithubRepositoryReadme { 10 | name: string 11 | path: string 12 | sha: string 13 | size: number 14 | url: string 15 | html_url: string 16 | git_url: string 17 | download_url: string 18 | type: 'file' 19 | content: string, 20 | encoding: 'base64' 21 | } 22 | 23 | export interface GithubRepositoryOwner { 24 | login: string 25 | id: number 26 | node_id: string 27 | avatar_url: string 28 | gravatar_id: string 29 | url: string 30 | html_url: string 31 | followers_url: string 32 | following_url: string 33 | gists_url: string 34 | starred_url: string 35 | subscriptions_url: string 36 | organizations_url: string 37 | repos_url: string 38 | events_url: string 39 | received_events_url: string 40 | type: string 41 | site_admin: boolean 42 | } 43 | 44 | export interface GithubRepositoryLicense { 45 | key: string 46 | name: string 47 | spdx_id: string 48 | url: string 49 | node_id: string 50 | } 51 | 52 | export interface GithubRepositoryOrganization { 53 | login: string 54 | id: number 55 | node_id: string 56 | avatar_url: string 57 | gravatar_id: string 58 | url: string 59 | html_url: string 60 | followers_url: string 61 | following_url: string 62 | gists_url: string 63 | starred_url: string 64 | subscriptions_url: string 65 | organizations_url: string 66 | repos_url: string 67 | events_url: string 68 | received_events_url: string 69 | type: string 70 | site_admin: boolean 71 | } 72 | 73 | export interface GithubRepository { 74 | id: number 75 | node_id: string 76 | name: string 77 | full_name: string 78 | private: boolean 79 | owner: GithubRepositoryOwner 80 | html_url: string 81 | description: string 82 | fork: boolean 83 | url: string 84 | forks_url: string 85 | keys_url: string 86 | collaborators_url: string 87 | teams_url: string 88 | hooks_url: string 89 | issue_events_url: string 90 | events_url: string 91 | assignees_url: string 92 | branches_url: string 93 | tags_url: string 94 | blobs_url: string 95 | git_tags_url: string 96 | git_refs_url: string 97 | trees_url: string 98 | statuses_url: string 99 | languages_url: string 100 | stargazers_url: string 101 | contributors_url: string 102 | subscribers_url: string 103 | subscription_url: string 104 | commits_url: string 105 | git_commits_url: string 106 | comments_url: string 107 | issue_comment_url: string 108 | contents_url: string 109 | compare_url: string 110 | merges_url: string 111 | archive_url: string 112 | downloads_url: string 113 | issues_url: string 114 | pulls_url: string 115 | milestones_url: string 116 | notifications_url: string 117 | labels_url: string 118 | releases_url: string 119 | deployments_url: string 120 | created_at: Date 121 | updated_at: Date 122 | pushed_at: Date 123 | git_url: string 124 | ssh_url: string 125 | clone_url: string 126 | svn_url: string 127 | homepage: string 128 | size: number 129 | stargazers_count: number 130 | watchers_count: number 131 | language: string 132 | has_issues: boolean 133 | has_projects: boolean 134 | has_downloads: boolean 135 | has_wiki: boolean 136 | has_pages: boolean 137 | forks_count: number 138 | mirror_url?: any 139 | archived: boolean 140 | disabled: boolean 141 | open_issues_count: number 142 | license: GithubRepositoryLicense 143 | allow_forking: boolean 144 | is_template: boolean 145 | web_commit_signoff_required: boolean 146 | topics: string[] 147 | visibility: string 148 | forks: number 149 | open_issues: number 150 | watchers: number 151 | default_branch: string 152 | temp_clone_token?: any 153 | organization: GithubRepositoryOrganization 154 | network_count: number 155 | subscribers_count: number 156 | } 157 | -------------------------------------------------------------------------------- /src/runtime/components/GithubLink.ts: -------------------------------------------------------------------------------- 1 | import { joinURL } from 'ufo' 2 | import type { PropType } from 'vue' 3 | import { computed, defineComponent, useSlots } from 'vue' 4 | // @ts-ignore 5 | import { useRuntimeConfig } from '#imports' 6 | 7 | export default defineComponent({ 8 | props: { 9 | /** 10 | * Repository owner. 11 | */ 12 | owner: { 13 | type: String, 14 | default: () => useRuntimeConfig()?.github?.owner, 15 | required: false 16 | }, 17 | /** 18 | * Repository name. 19 | */ 20 | repo: { 21 | type: String, 22 | default: () => useRuntimeConfig()?.github?.repo, 23 | required: false 24 | }, 25 | /** 26 | * The branch to use for the edit link. 27 | */ 28 | branch: { 29 | type: String, 30 | default: () => useRuntimeConfig()?.github?.branch, 31 | required: false 32 | }, 33 | /** 34 | * A base directory to append to the source path. 35 | * 36 | * Won't be used if `page` is set. 37 | */ 38 | dir: { 39 | type: String, 40 | default: () => useRuntimeConfig()?.github?.dir, 41 | required: false 42 | }, 43 | /** 44 | * Source file path. 45 | * 46 | * Won't be used if `page` is set. 47 | */ 48 | source: { 49 | type: String, 50 | required: false, 51 | default: undefined 52 | }, 53 | /** 54 | * Use page from @nuxt/content. 55 | */ 56 | page: { 57 | type: Object as PropType , 58 | required: false, 59 | default: undefined 60 | }, 61 | /** 62 | * Content directory (to be used with `page`) 63 | */ 64 | contentDir: { 65 | type: String, 66 | required: false, 67 | default: 'content' 68 | }, 69 | /** 70 | * Send to an edit page or not. 71 | */ 72 | edit: { 73 | type: Boolean, 74 | required: false, 75 | default: true 76 | } 77 | }, 78 | setup (props) { 79 | if (!props.owner || !props.repo || !props.branch) { 80 | throw new Error('If you want to use `GithubLink` component, you must specify: `owner`, `repo` and `branch`.') 81 | } 82 | 83 | const source = computed(() => { 84 | let { repo, owner, branch, contentDir } = props 85 | let prefix = '' 86 | 87 | // Resolve source from content sources 88 | if (useRuntimeConfig()?.public?.content) { 89 | let source 90 | const { sources } = useRuntimeConfig().public.content 91 | 92 | for (const key in sources || []) { 93 | if (props.page._id.startsWith(key)) { 94 | source = sources[key] 95 | break 96 | } 97 | } 98 | 99 | if (source?.driver === 'github') { 100 | repo = source.repo || props.repo || '' 101 | owner = source.owner || props.owner || '' 102 | branch = source.branch || props.branch || 'main' 103 | contentDir = source.dir || props.contentDir || '' 104 | prefix = source.prefix || '' 105 | } 106 | } 107 | 108 | return { repo, owner, branch, contentDir, prefix } 109 | }) 110 | 111 | const base = computed(() => joinURL('https://github.com', `${source.value.owner}/${source.value.repo}`)) 112 | 113 | const path = computed(() => { 114 | const dirParts: string[] = [] 115 | 116 | // @nuxt/content support 117 | // Create the URL from a document data. 118 | if (props?.page?._path) { 119 | // Use content dir 120 | if (source.value.contentDir) { dirParts.push(source.value.contentDir) } 121 | 122 | // Get page file from page data 123 | dirParts.push(props.page._file.substring(source.value.prefix.length)) 124 | 125 | return dirParts 126 | } 127 | 128 | // Use props dir 129 | if (props.dir) { 130 | dirParts.push(props.dir) 131 | } 132 | 133 | // Use props source 134 | if (props.source) { 135 | dirParts.push(props.source) 136 | } 137 | 138 | return dirParts 139 | }) 140 | 141 | /** 142 | * Create edit link. 143 | */ 144 | const url = computed(() => { 145 | const parts = [base.value] 146 | 147 | if (props.edit) { parts.push('edit') } else { parts.push('tree') } 148 | 149 | parts.push(source.value.branch, ...path.value) 150 | 151 | return parts.filter(Boolean).join('/') 152 | }) 153 | 154 | return { 155 | url 156 | } 157 | }, 158 | render (ctx) { 159 | const { url } = ctx 160 | 161 | const slots = useSlots() 162 | 163 | return slots?.default?.({ url }) 164 | } 165 | }) 166 | -------------------------------------------------------------------------------- /docs/content/3.components.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Components" 3 | description: "Discover every component from the GitHub package." 4 | --- 5 | 6 | This page uses [`@nuxt/content`](https://github.com/nuxt/content) repository as filling data for the releases. 7 | 8 | ## ` ` 9 | 10 | This component is useful if you want to display a link to the GitHub repository. 11 | 12 | Can be used for both browsing and editing links. 13 | 14 | ::code-group 15 | 16 | ::code-block{label="Preview"} 17 | ::source-link 18 | --- 19 | source: "packages/github/src/runtime/components/GithubLink.ts" 20 | --- 21 | :: 22 | :: 23 | 24 | ```vue [Code] 25 | 26 | 27 | 29 | 30 | ``` 31 | 32 | :: 33 | 34 | ::props{of="GithubLink"} 35 | :: 36 | 37 | ::source-link 38 | --- 39 | source: "packages/github/src/runtime/components/GithubLink.ts" 40 | --- 41 | :: 42 | 43 | --- 44 | 45 | ## `28 | ` 46 | 47 | This component is useful if you want to a repository contributors. 48 | 49 | ::code-group 50 | 51 | ::code-block{label="Preview"} 52 | ::contributors-example 53 | :: 54 | :: 55 | 56 | ```vue [Code] 57 | 58 | 59 | 63 | 64 | ``` 65 | 66 | :: 67 | 68 | ::props{of="GithubContributors"} 69 | :: 70 | 71 | ::source-link 72 | --- 73 | source: "packages/github/src/runtime/components/GithubContributors.ts" 74 | --- 75 | :: 76 | 77 | --- 78 | 79 | ## `60 |62 |61 | ` 80 | 81 | This component is useful if you want to a file contributors. 82 | 83 | ::code-group 84 | 85 | ::code-block{label="Preview"} 86 | ::file-contributors-example 87 | :: 88 | :: 89 | 90 | ```vue [Code] 91 | 94 | 95 | 96 | 97 | 98 | 99 | 101 | 102 | ``` 103 | 104 | :: 105 | 106 | ::props{of="GithubFileContributors"} 107 | :: 108 | 109 | ::source-link 110 | --- 111 | source: "packages/github/src/runtime/components/GithubFileContributors.ts" 112 | --- 113 | :: 114 | 115 | --- 116 | 117 | ## `100 | ` 118 | 119 | This component is useful if you want to display last release of the repository. 120 | 121 | ::code-group 122 | 123 | ::code-block{label="Preview"} 124 | ::div{class="max-h-[300px] pb-8"} 125 | ::last-release-example 126 | :: 127 | :: 128 | :: 129 | 130 | ```vue [Code] 131 | 132 | 133 | 136 | 137 | ``` 138 | 139 | :: 140 | 141 | ::props{of="GithubLastRelease"} 142 | :: 143 | 144 | ::source-link 145 | --- 146 | source: "packages/github/src/runtime/components/GithubLastRelease.ts" 147 | --- 148 | :: 149 | 150 | --- 151 | 152 | ## `{{ release.name }} 134 |135 | ` 153 | 154 | This component is useful if you want to display release fetched by tag. 155 | 156 | ::code-group 157 | 158 | ::code-block{label="Preview"} 159 | ::div{class="max-h-[300px] pb-8"} 160 | ::release-example 161 | :: 162 | :: 163 | :: 164 | 165 | ```vue [Code] 166 | 167 | 168 | 171 | 172 | ``` 173 | 174 | :: 175 | 176 | ::props{of="GithubRelease"} 177 | :: 178 | 179 | ::source-link 180 | --- 181 | source: "packages/github/src/runtime/components/GithubRelease.ts" 182 | --- 183 | :: 184 | 185 | --- 186 | 187 | ## `{{ release.name }} 169 |170 | ` 188 | 189 | This component is useful if you want to display all the releases of the repository. 190 | 191 | ::code-group 192 | 193 | ::code-block{label="Preview"} 194 | ::div{class="max-h-[300px] pb-8"} 195 | ::releases-example 196 | :: 197 | :: 198 | :: 199 | 200 | ```vue [Code] 201 | 202 | 203 | 208 | 209 | ``` 210 | 211 | :: 212 | 213 | ::props{of="GithubReleases"} 214 | :: 215 | 216 | ::source-link 217 | --- 218 | source: "packages/github/src/runtime/components/GithubReleases.ts" 219 | --- 220 | :: 221 | 222 | --- 223 | 224 | ## `204 |207 |{{ release.name }} 205 |206 | ` 225 | 226 | This component is useful if you want to display all the informations of the repository. 227 | 228 | ::code-group 229 | 230 | ::code-block{label="Preview"} 231 | ::div{class="max-h-[300px] pb-8"} 232 | ::repository-example 233 | :: 234 | :: 235 | :: 236 | 237 | ```vue [Code] 238 | 239 | 240 | 242 | 243 | ``` 244 | 245 | :: 246 | 247 | ::props{of="GithubRepository"} 248 | :: 249 | 250 | ::source-link 251 | --- 252 | source: "packages/github/src/runtime/components/GithubRepository.ts" 253 | --- 254 | :: 255 | 256 | ## `241 | ` 257 | 258 | This component helps to display the content of `README.md` in any repository. 259 | 260 | ::code-group 261 | 262 | ::code-block{label="Preview"} 263 | ::div{class="max-h-[300px] pb-8"} 264 | ::readme-example 265 | :: 266 | :: 267 | :: 268 | 269 | ```vue [Code] 270 | 271 | 272 | 274 | 275 | ``` 276 | 277 | :: 278 | 279 | ::props{of="GithubReadme"} 280 | :: 281 | 282 | ::source-link 283 | --- 284 | source: "packages/github/src/runtime/components/GithubReadme.ts" 285 | --- 286 | :: 287 | -------------------------------------------------------------------------------- /src/module.ts: -------------------------------------------------------------------------------- 1 | import { defu } from 'defu' 2 | import { addImports, addComponent, createResolver, defineNuxtModule, resolveModule } from '@nuxt/kit' 3 | import type { GithubRepositoryOptions } from './runtime/types' 4 | 5 | export interface ModuleOptions extends GithubRepositoryOptions { 6 | disableCache?: boolean 7 | remarkPlugin?: boolean 8 | releases?: boolean 9 | contributors?: boolean 10 | maxContributors?: number 11 | /** 12 | * Parse contents (releases content, readme) Markdown and return AST tree. 13 | * 14 | * Note: This option is only available when you have `@nuxt/content` installed in your project. 15 | * 16 | * @default true 17 | */ 18 | parseContents?: boolean 19 | } 20 | 21 | declare module '@nuxt/schema' { 22 | interface PublicRuntimeConfig { 23 | // @ts-ignore 24 | github?: ModuleOptions; 25 | } 26 | 27 | interface RuntimeConfig { 28 | // @ts-ignore 29 | github?: ModuleOptions; 30 | } 31 | } 32 | 33 | export default defineNuxtModule273 | ({ 34 | meta: { 35 | name: '@nuxtlabs/github-module', 36 | configKey: 'github' 37 | }, 38 | defaults: { 39 | owner: '', 40 | repo: '', 41 | token: undefined, 42 | branch: 'main', 43 | api: 'https://api.github.com', 44 | remarkPlugin: true, 45 | disableCache: false, 46 | releases: true, 47 | contributors: true, 48 | maxContributors: 100, 49 | parseContents: true 50 | }, 51 | setup (options, nuxt) { 52 | const { resolve } = createResolver(import.meta.url) 53 | const runtimeDir = resolve('./runtime') 54 | 55 | if (!options.owner) { 56 | // Check if we can split repo name into owner/repo 57 | if (options.repo && options.repo.includes('/')) { 58 | const [owner, repo] = options.repo.split('/') 59 | options.owner = owner 60 | options.repo = repo 61 | } 62 | } 63 | 64 | // @ts-ignore 65 | if (!nuxt.options.runtimeConfig.public) { nuxt.options.runtimeConfig.public = {} } 66 | 67 | const config = { 68 | api: options.api || process.env.GITHUB_OWNER, 69 | owner: options.owner || process.env.GITHUB_OWNER, 70 | branch: options.branch || process.env.GITHUB_BRANCH, 71 | repo: options.repo || process.env.GITHUB_REPO, 72 | disableCache: options.disableCache, 73 | parseContents: options.parseContents, 74 | maxContributors: options.maxContributors 75 | } 76 | 77 | // Public runtime config 78 | // @ts-ignore 79 | nuxt.options.runtimeConfig.public.github = defu(nuxt.options.runtimeConfig.public.github, config) 80 | // @ts-ignore 81 | nuxt.options.runtimeConfig.github = defu(nuxt.options.runtimeConfig.github, { 82 | token: options.token || process.env.GITHUB_TOKEN 83 | }, config) 84 | 85 | // Autolink issue/PR/commit links using `remark-github` plugin 86 | if (options.remarkPlugin) { 87 | // @ts-ignore 88 | nuxt.hook('content:context', (context) => { 89 | context.markdown.remarkPlugins ??= {} 90 | 91 | if (!Array.isArray(context.markdown.remarkPlugins)) { 92 | context.markdown.remarkPlugins['remark-github'] = { repository: `${options.owner}/${options.repo}` } 93 | } 94 | }) 95 | } 96 | 97 | const nitroConfig = nuxt.options.nitro 98 | 99 | // Init Nitro handlers 100 | nitroConfig.handlers = nitroConfig.handlers || [] 101 | nitroConfig.prerender = nitroConfig.prerender || {} 102 | nitroConfig.prerender.routes = nitroConfig.prerender.routes || [] 103 | 104 | // Setup repository API 105 | nitroConfig.handlers.push({ 106 | route: '/api/_github/repository/:query', 107 | handler: resolveModule('./server/api/repository', { paths: runtimeDir }) 108 | }) 109 | 110 | // Setup readme file API 111 | nitroConfig.handlers.push({ 112 | route: '/api/_github/readme/:query', 113 | handler: resolveModule('./server/api/readme', { paths: runtimeDir }) 114 | }) 115 | 116 | // Repository component 117 | addComponent({ 118 | name: 'GithubRepository', 119 | filePath: resolveModule('./components/GithubRepository', { paths: runtimeDir }), 120 | global: true 121 | }) 122 | 123 | // GithubLink component 124 | addComponent({ 125 | name: 'GithubLink', 126 | filePath: resolveModule('./components/GithubLink', { paths: runtimeDir }), 127 | global: true 128 | }) 129 | 130 | // GithubReadme component 131 | addComponent({ 132 | name: 'GithubReadme', 133 | filePath: resolveModule('./components/GithubReadme', { paths: runtimeDir }), 134 | global: true 135 | }) 136 | 137 | // Setup releases API 138 | if (options.releases) { 139 | // Releases list 140 | nitroConfig.handlers.push({ 141 | route: '/api/_github/releases/:query', 142 | handler: resolveModule('./server/api/releases/index', { paths: runtimeDir }) 143 | }) 144 | nitroConfig.prerender.routes.push('/api/_github/releases/index.json') 145 | 146 | // Releases components 147 | addComponent({ 148 | name: 'GithubReleases', 149 | filePath: resolveModule('./components/GithubReleases', { paths: runtimeDir }), 150 | global: true 151 | }) 152 | addComponent({ 153 | name: 'GithubLastRelease', 154 | filePath: resolveModule('./components/GithubLastRelease', { paths: runtimeDir }), 155 | global: true 156 | }) 157 | addComponent({ 158 | name: 'GithubRelease', 159 | filePath: resolveModule('./components/GithubRelease', { paths: runtimeDir }), 160 | global: true 161 | }) 162 | } 163 | 164 | // Setup contributors API 165 | if (options.contributors) { 166 | nitroConfig.handlers.push({ 167 | route: '/api/_github/contributors/:query', 168 | handler: resolveModule('./server/api/contributors', { paths: runtimeDir }) 169 | }) 170 | nitroConfig.prerender.routes.push('/api/_github/contributors/index.json') 171 | 172 | // TODO: Add prerender for file arguments (using :source argument) 173 | nitroConfig.handlers.push({ 174 | route: '/api/_github/contributors/file/:query', 175 | handler: resolveModule('./server/api/contributors/file', { paths: runtimeDir }) 176 | }) 177 | 178 | // Contributors components 179 | addComponent({ 180 | name: 'GithubContributors', 181 | filePath: resolveModule('./components/GithubContributors', { paths: runtimeDir }), 182 | global: true 183 | }) 184 | addComponent({ 185 | name: 'GithubFileContributors', 186 | filePath: resolveModule('./components/GithubFileContributors', { paths: runtimeDir }), 187 | global: true 188 | }) 189 | } 190 | 191 | // Setup commits API 192 | nitroConfig.handlers.push({ 193 | route: '/api/_github/commits/:query', 194 | handler: resolveModule('./server/api/commits', { paths: runtimeDir }) 195 | }) 196 | 197 | // GithubCommits component 198 | addComponent({ 199 | name: 'GithubCommits', 200 | filePath: resolveModule('./components/GithubCommits', { paths: runtimeDir }), 201 | global: true 202 | }) 203 | 204 | addImports({ 205 | name: 'useGithub', 206 | from: resolveModule('./composables/useGithub', { paths: runtimeDir }) 207 | }) 208 | 209 | nitroConfig.externals = defu(typeof nitroConfig.externals === 'object' ? nitroConfig.externals : {}, { 210 | inline: [ 211 | // Inline module runtime in Nitro bundle 212 | resolve('./runtime') 213 | ] 214 | }) 215 | } 216 | }) 217 | -------------------------------------------------------------------------------- /src/runtime/server/utils/queries.ts: -------------------------------------------------------------------------------- 1 | import { joinURL, withQuery, QueryObject } from 'ufo' 2 | import { graphql } from '@octokit/graphql' 3 | import remarkGithub from 'remark-github' 4 | import { defu } from 'defu' 5 | import type { ModuleOptions } from '../../../module' 6 | import type { GithubRawRelease, GithubRepositoryOptions, GithubRawContributor, GithubContributorsQuery, GithubReleasesQuery, GithubRepositoryReadme, GithubRepository, GithubCommitsQuery, GithubAuthor } from '../../types' 7 | 8 | export function decodeParams (params = '') { 9 | const result: Record = {} 10 | params = params.replace(/\.json$/, '') 11 | for (const param of params.split(':')) { 12 | const [key, ...value] = param.split('_') 13 | result[key] = value.join('_') 14 | } 15 | return result 16 | } 17 | 18 | function isBot (user: GithubAuthor) { 19 | return user.login.includes('[bot]') || user.login.includes('-bot') || user.login.includes('.bot') 20 | } 21 | 22 | function normalizeRelease (release: any): GithubRawRelease { 23 | return { 24 | name: normalizeReleaseName(release?.name || release?.tag_name), 25 | tag_name: release?.tag_name, 26 | date: release?.published_at, 27 | body: release?.body, 28 | v: +normalizeReleaseName(release?.tag_name)?.substring(1, 2) || 0, 29 | url: release?.html_url, 30 | tarball: release?.tarball_url, 31 | zipball: release?.zipball_url, 32 | prerelease: release?.prerelease, 33 | reactions: release?.reactions, 34 | author: { 35 | name: release?.author?.login, 36 | url: release?.author?.html_url, 37 | avatar: release?.author?.avatar_url 38 | } 39 | } 40 | } 41 | 42 | function normalizeReleaseName (name: string) { 43 | if (!name) { return '' } 44 | 45 | // remove "Release " prefix from release name 46 | name = name.replace('Release ', '') 47 | 48 | // make sure release name starts with an alphabetical character 49 | if (!name.match(/^[a-zA-Z]/)) { 50 | name = `v${name}` 51 | } 52 | 53 | return name 54 | } 55 | 56 | export function githubGraphqlQuery (query: string, options: Partial ): Promise { 57 | const gq = graphql.defaults({ 58 | headers: { 59 | authorization: `token ${options.token}` 60 | } 61 | }) 62 | 63 | return gq (query) 64 | } 65 | 66 | export const parseRelease = async (release: GithubRawRelease, githubConfig: GithubRepositoryOptions) => { 67 | // @ts-ignore 68 | const markdown = await import('@nuxt/content/transformers/markdown').then(m => m.default || m) 69 | let parsedRelease 70 | try { 71 | parsedRelease = { 72 | ...release, 73 | // Parse release notes when `@nuxt/content` is installed. 74 | ...( 75 | release?.body && release?.name 76 | ? await markdown.parse(`github:${release.name}.md`, release.body, { 77 | remarkPlugins: { 78 | 'remark-github': { 79 | instance: remarkGithub, 80 | repository: `${githubConfig.owner}/${githubConfig.repo}` 81 | } 82 | } 83 | }) 84 | : {} 85 | ) 86 | } 87 | } catch (_err: any) { 88 | // eslint-disable-next-line no-console 89 | console.warn(`Cannot parse release ${release?.name} [${_err.response?.status || 500}]`) 90 | return 91 | } 92 | 93 | return parsedRelease 94 | } 95 | 96 | export function overrideConfig (config: ModuleOptions, query: GithubRepositoryOptions): GithubRepositoryOptions { 97 | return (({ owner, repo, branch, api, token }) => ({ owner, repo, branch, api, token }))(defu(query, config)) 98 | } 99 | 100 | export async function fetchRepository ({ api, owner, repo, token }: GithubRepositoryOptions) { 101 | const url = `${api}/repos/${owner}/${repo}` 102 | 103 | const repository = await $fetch (url, { 104 | headers: token ? { Authorization: `token ${token}` } : {} 105 | }).catch((_) => { 106 | /* 107 | // eslint-disable-next-line no-console 108 | console.warn(`Cannot fetch GitHub Repository on ${url} [${err.response?.status || 500}]`) 109 | 110 | // eslint-disable-next-line no-console 111 | console.info('If your repository is private, make sure to provide GITHUB_TOKEN environment in `.env`') 112 | */ 113 | return {} 114 | }) 115 | 116 | return repository 117 | } 118 | 119 | export async function fetchRepositoryContributors ({ max }: Partial , { api, owner, repo, token }: GithubRepositoryOptions) { 120 | let url = `${api}/repos/${owner}/${repo}/contributors` 121 | 122 | url = withQuery(url, { max } as QueryObject) 123 | 124 | const contributors = await $fetch >(url, { 125 | headers: token ? { Authorization: `token ${token}` } : {} 126 | }).catch((_) => { 127 | /* 128 | // eslint-disable-next-line no-console 129 | console.warn(`Cannot fetch GitHub contributors on ${url} [${err.response?.status || 500}]`) 130 | 131 | // eslint-disable-next-line no-console 132 | console.info('If your repository is private, make sure to provide GITHUB_TOKEN environment in `.env`') 133 | 134 | if (err?.response?.status !== 403) { 135 | // eslint-disable-next-line no-console 136 | console.info('To disable fetching contributors, set `github.contributors` to `false` in `nuxt.config.ts`') 137 | } 138 | */ 139 | return [] 140 | }) 141 | 142 | // eslint-disable-next-line camelcase 143 | return contributors.map(({ avatar_url, login }) => ({ avatar_url, login })) 144 | } 145 | 146 | export async function fetchCommits ({ date, source }: Partial & { date: Date }>, { owner, repo, branch, token }: GithubRepositoryOptions) { 147 | const daysAgo = () => { 148 | if (date) { return date.toISOString() } 149 | 150 | const now = new Date() 151 | now.setDate(now.getDate() - 30) // get from 30 days ago 152 | return now.toISOString() 153 | } 154 | 155 | const path = source ? `path: "${source}",` : '' 156 | const data = await githubGraphqlQuery( 157 | ` 158 | query { 159 | repository(owner: "${owner}", name: "${repo}") { 160 | object(expression: "${branch}") { 161 | ... on Commit { 162 | history(since: "${daysAgo()}", ${path}) { 163 | nodes { 164 | oid 165 | messageHeadlineHTML 166 | authors(first: ${5}) { 167 | nodes { 168 | user { 169 | name 170 | avatarUrl 171 | login 172 | } 173 | } 174 | } 175 | } 176 | } 177 | } 178 | } 179 | } 180 | } 181 | `, { token } 182 | ) 183 | 184 | if (!data?.repository?.object?.history?.nodes) { return [] } 185 | 186 | const commits = data.repository.object.history.nodes.map((node: any) => ({ 187 | hash: node.oid, 188 | message: node.messageHeadlineHTML, 189 | authors: node.authors.nodes 190 | .map((author: any) => author.user) 191 | .filter((user: GithubAuthor) => user?.name && !isBot(user)) 192 | })) 193 | 194 | return commits 195 | } 196 | 197 | export async function fetchFileContributors ({ source, max }: Partial , { owner, repo, branch, token }: GithubRepositoryOptions & { maxContributors?: number }) { 198 | const data = await githubGraphqlQuery( 199 | ` 200 | query { 201 | repository(owner: "${owner}", name: "${repo}") { 202 | object(expression: "${branch}") { 203 | ... on Commit { 204 | history(first: ${max}, path: "${source}") { 205 | nodes { 206 | authors(first: ${max}) { 207 | nodes { 208 | user { 209 | name 210 | avatarUrl 211 | login 212 | } 213 | } 214 | } 215 | } 216 | } 217 | } 218 | } 219 | } 220 | }`, 221 | { token } 222 | ).catch((_) => { 223 | /* 224 | // eslint-disable-next-line no-console 225 | console.warn(`Cannot fetch GitHub file contributors on ${source} [${err.response?.status || 500}]`) 226 | 227 | // eslint-disable-next-line no-console 228 | console.info('If your repository is private, make sure to provide GITHUB_TOKEN environment in `.env`') 229 | 230 | if (err?.response?.status !== 403) { 231 | // eslint-disable-next-line no-console 232 | console.info('To disable fetching contributors, set `github.contributors` to `false` in `nuxt.config.ts`') 233 | } 234 | */ 235 | }) 236 | 237 | if (!data?.repository?.object?.history?.nodes) { return [] } 238 | 239 | let users: GithubAuthor[] = data.repository.object.history.nodes 240 | .map((node: any) => node.authors.nodes) 241 | .flat() 242 | .map((node: any) => node.user) 243 | .filter((user: GithubAuthor) => user && user.name) 244 | .filter((user: GithubAuthor) => !isBot(user)) 245 | 246 | // Unique 247 | users = users.reduce((unique: GithubAuthor[], user: GithubAuthor) => (unique.find(u => u.login === user.login) ? unique : unique.concat(user)), []) 248 | 249 | return users.map(({ avatarUrl, name, login }) => ({ avatar_url: avatarUrl, name, login })) 250 | } 251 | 252 | export async function fetchReleases (query: Partial , { api, repo, token, owner }: GithubRepositoryOptions) { 253 | const page = query?.page || 1 254 | const perPage = query?.per_page || 100 255 | const last = query?.last || false 256 | const tag = query?.tag || false 257 | 258 | let url = `${api}/repos/${owner}/${repo}/releases` 259 | if (tag) { 260 | url = joinURL(url, 'tags', tag) 261 | } else if (last) { 262 | url = joinURL(url, 'latest') 263 | } else { 264 | url = withQuery(url, { per_page: perPage, page } as any) 265 | } 266 | 267 | const rawReleases = await $fetch >(url, { 268 | headers: token ? { Authorization: `token ${token}` } : {} 269 | }).catch((_) => { 270 | /* 271 | // eslint-disable-next-line no-console 272 | console.warn(`Cannot fetch GitHub releases on ${url} [${err.response?.status || 500}]`) 273 | 274 | // eslint-disable-next-line no-console 275 | console.info('If your repository is private, make sure to provide GITHUB_TOKEN environment in `.env`') 276 | 277 | if (err.response.status !== 403) { 278 | // eslint-disable-next-line no-console 279 | console.info('To disable fetching releases, set `github.releases` to `false` in `nuxt.config.ts`') 280 | } 281 | */ 282 | }) 283 | 284 | if (!rawReleases) { return last ? {} : [] } 285 | 286 | return (last || tag) ? normalizeRelease(rawReleases) : rawReleases.filter((r: any) => !r.draft).map(normalizeRelease) 287 | } 288 | 289 | export async function fetchReadme ({ api, owner, repo, token }: GithubRepositoryOptions) { 290 | const url = `${api}/repos/${owner}/${repo}/readme` 291 | 292 | const readme = await $fetch (url, { 293 | headers: token ? { Authorization: `token ${token}` } : {} 294 | }).catch((_) => { 295 | /* 296 | // eslint-disable-next-line no-console 297 | console.warn(`Cannot fetch GitHub readme on ${url} [${err.response?.status || 500}]`) 298 | 299 | // eslint-disable-next-line no-console 300 | console.info('If your repository is private, make sure to provide GITHUB_TOKEN environment in `.env`') 301 | */ 302 | 303 | return {} 304 | }) 305 | 306 | return readme 307 | } 308 | --------------------------------------------------------------------------------