├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github ├── CODEOWNERS └── workflows │ ├── CI.yml │ ├── build.yml │ ├── codeql-analysis.yml │ ├── release.yml │ └── super-linter.yml ├── .gitignore ├── .npmrc ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── imgs └── screenshot.png ├── lerna.json ├── package-lock.json ├── package.json ├── packages ├── cli │ ├── .gitignore │ ├── .npmrc │ ├── CHANGELOG.md │ ├── README.md │ ├── bin │ │ ├── run │ │ └── run.cmd │ ├── lib │ │ ├── base.d.ts │ │ ├── base.d.ts.map │ │ ├── base.js │ │ ├── commands │ │ │ ├── repo.d.ts │ │ │ ├── repo.d.ts.map │ │ │ ├── repo.js │ │ │ ├── repo │ │ │ │ ├── commits.d.ts │ │ │ │ ├── commits.d.ts.map │ │ │ │ ├── commits.js │ │ │ │ ├── milestones.d.ts │ │ │ │ ├── milestones.d.ts.map │ │ │ │ ├── milestones.js │ │ │ │ ├── projects.d.ts │ │ │ │ ├── projects.d.ts.map │ │ │ │ ├── projects.js │ │ │ │ ├── pulls.d.ts │ │ │ │ ├── pulls.d.ts.map │ │ │ │ ├── pulls.js │ │ │ │ ├── releases.d.ts │ │ │ │ ├── releases.d.ts.map │ │ │ │ └── releases.js │ │ │ ├── search.d.ts │ │ │ ├── search.d.ts.map │ │ │ ├── search.js │ │ │ └── search │ │ │ │ ├── issues.d.ts │ │ │ │ ├── issues.d.ts.map │ │ │ │ └── issues.js │ │ ├── index.d.ts │ │ ├── index.d.ts.map │ │ └── index.js │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── base.ts │ │ ├── commands │ │ │ ├── repo.ts │ │ │ ├── repo │ │ │ │ ├── commits.ts │ │ │ │ ├── milestones.ts │ │ │ │ ├── projects.ts │ │ │ │ ├── pulls.ts │ │ │ │ └── releases.ts │ │ │ ├── search.ts │ │ │ └── search │ │ │ │ └── issues.ts │ │ ├── github.d.ts │ │ ├── index.ts │ │ └── jsonexport.d.ts │ ├── test │ │ ├── commands │ │ │ ├── repo.test.ts │ │ │ ├── repo │ │ │ │ ├── commits.test.ts │ │ │ │ ├── milestones.test.ts │ │ │ │ ├── pulls.test.ts │ │ │ │ └── releases.test.ts │ │ │ └── search │ │ │ │ └── issues.test.ts │ │ ├── mocha.opts │ │ └── tsconfig.json │ └── tsconfig.json ├── core │ ├── .npmrc │ ├── CHANGELOG.md │ ├── README.md │ ├── __tests__ │ │ └── core.test.js │ ├── codegen.yml │ ├── github.d.ts │ ├── graphql.schema.json │ ├── lib │ │ ├── core.d.ts │ │ ├── core.d.ts.map │ │ ├── core.js │ │ ├── github.d.ts │ │ ├── github.d.ts.map │ │ ├── github.js │ │ ├── utils.d.ts │ │ ├── utils.d.ts.map │ │ └── utils.js │ ├── package-lock.json │ ├── package.json │ ├── queries │ │ ├── listIssueComments.graphql │ │ └── searchIssues.graphql │ ├── src │ │ ├── core.ts │ │ ├── github.ts │ │ └── utils.ts │ └── tsconfig.json └── gui │ ├── .eslintignore │ ├── .npmrc │ ├── CHANGELOG.md │ ├── README.md │ ├── __tests__ │ └── gui.test.js │ ├── layout │ ├── Exporter Main - search_issues.png │ └── github-exporter.bmpr │ ├── package-lock.json │ ├── package.json │ ├── src │ ├── index.ts │ ├── jsonexport.d.ts │ ├── renderer │ │ ├── GitHub-Mark-120px-plus.png │ │ ├── about.css │ │ ├── about.html │ │ ├── about.js │ │ ├── index.html │ │ ├── index.js │ │ ├── open-source.css │ │ ├── open-source.js │ │ ├── spectre-exp.min.css │ │ ├── spectre-icons.min.css │ │ └── spectre.min.css │ └── window.ts │ └── tsconfig.json └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/dist/ 2 | **/lib/ 3 | **/node_modules/ 4 | packages/gui/layout/ 5 | packages/core/graphql.schema.json 6 | packages/core/github.d.ts 7 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true 4 | }, 5 | "extends": [ 6 | "plugin:github/recommended", 7 | "plugin:github/typescript", 8 | "plugin:prettier/recommended" 9 | ], 10 | "plugins": ["html"], 11 | "rules": { 12 | "import/no-namespace": "off" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # These owners will be the default owners for everything in the repo # 3 | ###################################################################### 4 | * @jasonmacgowan @chocrates @froi @zkoppert 5 | -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | pull_request: 8 | branches: [main] 9 | 10 | env: 11 | NPM_TOKEN: ${{ secrets.GPR_READ_PACKAGE }} 12 | 13 | jobs: 14 | test: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: actions/setup-node@v1 20 | - run: npm ci 21 | - run: npm run bootstrap 22 | - run: npm test 23 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build and upload release assets 2 | 3 | on: 4 | release: 5 | types: 6 | - created 7 | 8 | env: 9 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 10 | NPM_TOKEN: ${{ secrets.GPR_READ_PACKAGE }} 11 | 12 | jobs: 13 | build: 14 | runs-on: windows-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - uses: actions/setup-node@v1 19 | - run: npm ci 20 | - run: npm run bootstrap 21 | - name: Build executables 22 | run: npx lerna run pkg:windows 23 | - name: Zip up GUI 24 | run: | 25 | cd packages/gui/dist/win-unpacked 26 | 7z a github-exporter.zip . 27 | - name: Upload GitHub Exporter GUI 28 | uses: actions/upload-release-asset@v1 29 | with: 30 | upload_url: ${{ github.event.release.upload_url }} 31 | asset_path: ./packages/gui/dist/win-unpacked/github-exporter.zip 32 | asset_name: github-exporter.zip 33 | asset_content_type: application/zip 34 | - name: Upload GitHub Exporter CLI 35 | uses: actions/upload-release-asset@v1 36 | with: 37 | upload_url: ${{ github.event.release.upload_url }} 38 | asset_path: ./packages/cli/dist/github-exporter-cli.exe 39 | asset_name: github-exporter.exe 40 | asset_content_type: application/vnd.microsoft.portable-executable 41 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | name: "CodeQL" 7 | 8 | on: 9 | push: 10 | branches: [main] 11 | pull_request: 12 | # The branches below must be a subset of the branches above 13 | branches: [main] 14 | schedule: 15 | - cron: '0 0 * * 4' 16 | 17 | jobs: 18 | analyze: 19 | name: Analyze 20 | runs-on: ubuntu-latest 21 | 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | # Override automatic language detection by changing the below list 26 | # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] 27 | language: ['typescript'] 28 | # Learn more... 29 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection 30 | 31 | steps: 32 | - name: Checkout repository 33 | uses: actions/checkout@v2 34 | 35 | # Initializes the CodeQL tools for scanning. 36 | - name: Initialize CodeQL 37 | uses: github/codeql-action/init@v1 38 | with: 39 | languages: ${{ matrix.language }} 40 | # If you wish to specify custom queries, you can do so here or in a config file. 41 | # By default, queries listed here will override any specified in a config file. 42 | # Prefix the list here with "+" to use these queries and those in the config file. 43 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 44 | 45 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 46 | # If this step fails, then you should remove it and run the build manually (see below) 47 | - name: Autobuild 48 | uses: github/codeql-action/autobuild@v1 49 | 50 | # ℹ️ Command-line programs to run using the OS shell. 51 | # 📚 https://git.io/JvXDl 52 | 53 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 54 | # and modify them (or add more) to build your code if your project 55 | # uses a compiled language 56 | 57 | #- run: | 58 | # make bootstrap 59 | # make release 60 | 61 | - name: Perform CodeQL Analysis 62 | uses: github/codeql-action/analyze@v1 63 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Create release 2 | on: 3 | push: 4 | branches: 5 | - main 6 | 7 | jobs: 8 | build: 9 | name: Create Release 10 | runs-on: ubuntu-latest 11 | if: "contains(github.event.head_commit.message, 'chore(release): lerna publish')" 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v2 15 | - name: Create Release 16 | id: create_release 17 | uses: actions/create-release@v1 18 | env: 19 | GITHUB_TOKEN: ${{ secrets.ADMIN_TOKEN }} 20 | with: 21 | tag_name: ${{ github.ref }} 22 | release_name: GitHub Exporter ${{ github.ref }} 23 | draft: false 24 | prerelease: false 25 | -------------------------------------------------------------------------------- /.github/workflows/super-linter.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ########################### 3 | ########################### 4 | ## Linter GitHub Actions ## 5 | ########################### 6 | ########################### 7 | name: Lint Code Base 8 | 9 | # 10 | # Documentation: 11 | # https://help.github.com/en/articles/workflow-syntax-for-github-actions 12 | # 13 | 14 | ############################# 15 | # Start the job on all push # 16 | ############################# 17 | on: 18 | pull_request: 19 | branches: [main] 20 | 21 | ############### 22 | # Set the Job # 23 | ############### 24 | jobs: 25 | build: 26 | # Name the Job 27 | name: Lint Code Base 28 | # Set the agent to run on 29 | runs-on: ubuntu-latest 30 | 31 | ################## 32 | # Load all steps # 33 | ################## 34 | steps: 35 | ########################## 36 | # Checkout the code base # 37 | ########################## 38 | - name: Checkout Code 39 | uses: actions/checkout@v2 40 | with: 41 | fetch-depth: 0 42 | 43 | ################################ 44 | # Run Linter against code base # 45 | ################################ 46 | - name: Lint Code Base 47 | uses: github/super-linter@v4 48 | env: 49 | VALIDATE_ALL_CODEBASE: false 50 | DEFAULT_BRANCH: main 51 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # Snowpack dependency directory (https://snowpack.dev/) 45 | web_modules/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | .parcel-cache 78 | 79 | # Next.js build output 80 | .next 81 | out 82 | 83 | # Nuxt.js build / generate output 84 | .nuxt 85 | dist 86 | 87 | # Gatsby files 88 | .cache/ 89 | # Comment in the public line in if your project uses Gatsby and not Next.js 90 | # https://nextjs.org/blog/next-9-1#public-directory-support 91 | # public 92 | 93 | # vuepress build output 94 | .vuepress/dist 95 | 96 | # Serverless directories 97 | .serverless/ 98 | 99 | # FuseBox cache 100 | .fusebox/ 101 | 102 | # DynamoDB Local files 103 | .dynamodb/ 104 | 105 | # TernJS port file 106 | .tern-port 107 | 108 | # Stores VSCode versions used for testing VSCode extensions 109 | .vscode-test 110 | 111 | # yarn v2 112 | .yarn/cache 113 | .yarn/unplugged 114 | .yarn/build-state.yml 115 | .yarn/install-state.gz 116 | .pnp.* 117 | 118 | packages/gui/lib 119 | **/.DS_Store 120 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | //npm.pkg.github.com/:_authToken=${NPM_TOKEN} 2 | @github:registry=https://npm.pkg.github.com 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [2.0.2](https://github.com/github/github-artifact-exporter/compare/v2.0.1...v2.0.2) (2021-08-09) 7 | 8 | **Note:** Version bump only for package root 9 | 10 | 11 | 12 | 13 | 14 | ## 2.0.1 (2020-11-19) 15 | 16 | **Note:** Version bump only for package root 17 | 18 | 19 | 20 | 21 | 22 | # [2.0.0](https://github.com/github/github-artifact-exporter/compare/v1.8.5...v2.0.0) (2020-11-10) 23 | 24 | **Note:** Version bump only for package root 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | **Note:** Version bump only for package root 33 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | opensource@github.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | 130 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | [fork]: https://github.com/github/github-artifact-exporter/fork 4 | [pr]: https://github.com/github/github-artifact-exporter/compare 5 | [style]: https://styleguide.github.com/js/ 6 | [code-of-conduct]: CODE_OF_CONDUCT.md 7 | 8 | Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great. 9 | 10 | Contributions to this project are [released](https://docs.github.com/en/github/site-policy/github-terms-of-service#6-contributions-under-repository-license) to the public under the [project's open source license](LICENSE). 11 | 12 | Please note that this project is released with a [Contributor Code of Conduct][code-of-conduct]. By participating in this project you agree to abide by its terms. 13 | 14 | ## Submitting a pull request 15 | 16 | 0. [Fork][fork] and clone the repository 17 | 0. Configure and install the dependencies: `npm install` 18 | 0. Make sure the tests pass on your machine: `npm test` 19 | 0. Create a new branch: `git checkout -b my-branch-name` 20 | 0. Make your change, add tests, and make sure the tests still pass 21 | 0. Push to your fork and [submit a pull request][pr] 22 | 0. Pat your self on the back and wait for your pull request to be reviewed and merged. 23 | 24 | Here are a few things you can do that will increase the likelihood of your pull request being accepted: 25 | 26 | - Follow the [style guide][style]. 27 | - Write tests. 28 | - Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests. 29 | - Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). 30 | 31 | ## Releases 32 | - Release are to follow [semantic versioning](https://semver.org/) 33 | - Releases are to utilize the [Release](https://github.com/github/github-artifact-exporter/releases) feature of GitHub 34 | 35 | 1. Clone locally and ensure that you are on the `main` branch 36 | 1. Run locally `lerna publish` 37 | 1. Automation will [create a release](https://github.com/github/github-artifact-exporter/blob/main/.github/workflows/release.yml), [build the artifacts, and upload them to the release](https://github.com/github/github-artifact-exporter/blob/main/.github/workflows/build.yml) 38 | 39 | ## Resources 40 | 41 | - [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/) 42 | - [Using Pull Requests](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests) 43 | - [GitHub Docs](https://docs.github.com) 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 GitHub 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This project has been archived. Please use the GitHub CLI instead. 2 | 3 | # GitHub Exporter 4 | ![Node.js CI](https://github.com/github/github-artifact-exporter/workflows/Node.js%20CI/badge.svg) ![Create release](https://github.com/github/github-artifact-exporter/workflows/Create%20release/badge.svg) ![Build and upload release assets](https://github.com/github/github-artifact-exporter/workflows/Build%20and%20upload%20release%20assets/badge.svg) 5 | 6 | ![Screenshot of the User interface](imgs/screenshot.png) 7 | 8 | The GitHub Exporter is written in Typescript and provides a set of packages to make exporting artifacts from GitHub easier useful for those migrating information out of github.com 9 | 10 | Supported artifacts that you can export are 11 | - Issues (including filtered sub sets) 12 | 13 | Supported formats of the export file are 14 | - JSON Lines 15 | - JSON 16 | - CSV 17 | - JIRA-formatted CSV 18 | 19 | ## Packages 20 | 21 | ### CLI 22 | 23 | [@github/github-exporter-cli](packages/cli) 24 | 25 | ### Core 26 | 27 | [@github/github-exporter-core](packages/core) 28 | 29 | ### GUI 30 | 31 | [@github/github-exporter-gui](packages/gui) 32 | 33 | ## Getting Started 34 | 35 | ### Prerequisites 36 | 1. This is a [lerna](https://github.com/lerna/lerna) project and will need the lerna CLI. 37 | - To install lerna globally run `npm install -g lerna` 38 | 1. Generate and export a PAT so you can pull from GPR. The PAT will need read packages scope. 39 | - `export NPM_TOKEN=` 40 | 41 | ### Building The Application 42 | 43 | ```bash 44 | lerna clean -y 45 | lerna exec npm install 46 | lerna link 47 | lerna bootstrap 48 | # Optional, start the gui to ensure its working 49 | lerna run start 50 | ``` 51 | 52 | ## Contributing 53 | We welcome you to contribute to this project! Check out [Open Issues](https://github.com/github/github-artifact-exporter/issues) and our [`CONTRIBUTING.md`](./CONTRIBUTING.md) to jump in. 54 | 55 | ## License 56 | [MIT](./LICENSE) 57 | When using the GitHub logos, be sure to follow the [GitHub logo guidelines](https://github.com/logos). 58 | 59 | -------------------------------------------------------------------------------- /imgs/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/github/github-artifact-exporter/29f6895ac3174c5b66ddb47075aa3361412443cd/imgs/screenshot.png -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "command": { 3 | "publish": { 4 | "conventionalCommits": true, 5 | "message": "chore(release): lerna publish %s", 6 | "registry": "https://npm.pkg.github.com" 7 | } 8 | }, 9 | "packages": [ 10 | "packages/*" 11 | ], 12 | "version": "2.0.2" 13 | } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "private": true, 4 | "devDependencies": { 5 | "@types/dot-object": "^2.1.1", 6 | "eslint": "^5.16.0", 7 | "eslint-config-oclif": "^3.1.0", 8 | "eslint-config-oclif-typescript": "^0.1.0", 9 | "eslint-config-prettier": "^6.11.0", 10 | "eslint-plugin-github": "^4.0.1", 11 | "eslint-plugin-html": "^6.0.2", 12 | "eslint-plugin-prettier": "^3.1.4", 13 | "lerna": "^3.22.1", 14 | "prettier": "^2.0.5", 15 | "typescript": "^3.9.6" 16 | }, 17 | "scripts": { 18 | "bootstrap": "lerna bootstrap", 19 | "lint": "eslint . --ext .ts --config .eslintrc", 20 | "lint:fix": "eslint . --ext .ts --config .eslintrc --fix", 21 | "test": "lerna run test && npm run lint" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/cli/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # Snowpack dependency directory (https://snowpack.dev/) 45 | web_modules/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | .parcel-cache 78 | 79 | # Next.js build output 80 | .next 81 | out 82 | 83 | # Nuxt.js build / generate output 84 | .nuxt 85 | dist 86 | 87 | # Gatsby files 88 | .cache/ 89 | # Comment in the public line in if your project uses Gatsby and not Next.js 90 | # https://nextjs.org/blog/next-9-1#public-directory-support 91 | # public 92 | 93 | # vuepress build output 94 | .vuepress/dist 95 | 96 | # Serverless directories 97 | .serverless/ 98 | 99 | # FuseBox cache 100 | .fusebox/ 101 | 102 | # DynamoDB Local files 103 | .dynamodb/ 104 | 105 | # TernJS port file 106 | .tern-port 107 | 108 | # Stores VSCode versions used for testing VSCode extensions 109 | .vscode-test 110 | 111 | # yarn v2 112 | .yarn/cache 113 | .yarn/unplugged 114 | .yarn/build-state.yml 115 | .yarn/install-state.gz 116 | .pnp.* 117 | 118 | # oclif 119 | 120 | oclif.manifest.json -------------------------------------------------------------------------------- /packages/cli/.npmrc: -------------------------------------------------------------------------------- 1 | //npm.pkg.github.com/:_authToken=${NPM_TOKEN} 2 | @github:registry=https://npm.pkg.github.com 3 | -------------------------------------------------------------------------------- /packages/cli/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [2.0.2](https://github.com/github/github-artifact-exporter/compare/v2.0.1...v2.0.2) (2021-08-09) 7 | 8 | **Note:** Version bump only for package @github/github-artifact-exporter-cli 9 | 10 | 11 | 12 | 13 | 14 | ## 2.0.1 (2020-11-19) 15 | 16 | **Note:** Version bump only for package @github/github-artifact-exporter-cli 17 | 18 | 19 | 20 | 21 | 22 | # [2.0.0](https://github.com/github/github-artifact-exporter/compare/v1.8.5...v2.0.0) (2020-11-10) 23 | 24 | **Note:** Version bump only for package @github/github-artifact-exporter-cli 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | **Note:** Version bump only for package @github/github-artifact-exporter-cli 33 | -------------------------------------------------------------------------------- /packages/cli/README.md: -------------------------------------------------------------------------------- 1 | # github-artifact-exporter 2 | 3 | Exporter for GitHub 4 | 5 | 6 | * [github-artifact-exporter](#github-artifact-exporter) 7 | * [Usage](#usage) 8 | * [Commands](#commands) 9 | 10 | 11 | # Usage 12 | 13 | This script requires a `personal access token` with access to the repository you are exporting from. The `owner` and `repo` arguments can be found in the repository url. 14 | 15 | See the table below for examples of `owner` and `repo` 16 | 17 | | Description | URL | owner | repo | 18 | | ------------------------- | -------------------------- | ----- | ---- | 19 | | GitHub.com example | https://github.com/foo/bar | foo | bar | 20 | | GitHub Enterprise example | https://ghe.host/foo/bar | foo | bar | 21 | 22 | 23 | ```sh-session 24 | $ npm install -g @github/github-artifact-exporter-cli 25 | $ github-artifacts-exporter COMMAND 26 | running command... 27 | $ github-artifacts-exporter (-v|--version|version) 28 | @github/github-artifact-exporter-cli/2.0.2 darwin-x64 node-v14.4.0 29 | $ github-artifacts-exporter --help [COMMAND] 30 | USAGE 31 | $ github-artifacts-exporter COMMAND 32 | ... 33 | ``` 34 | 35 | 36 | ## Examples 37 | 38 | ### Exporting issues 39 | 40 | ``` 41 | github-artifact-exporter.exe search:issues --owner github --repo caseflow --token --since 2020-06-01 --until 2020-06-08 --format CSV > issue_export.csv 42 | ``` 43 | 44 | ### Exporting closed issues with specific labels 45 | 46 | ``` 47 | github-artifact-exporter.exe search:issues --owner github --repo caseflow --token --state closed --updatedSince 2020-06-01 --updatedUntil 2020-06-08 --labels "Type: Bug" --format CSV > issue_export.csv 48 | ``` 49 | 50 | ### Exporting commits 51 | 52 | ``` 53 | github-artifact-exporter.exe repo:commits --owner github --repo caseflow --token --since 2020-06-01 --until 2020-06-08 --format CSV > commit_export.csv 54 | ``` 55 | 56 | ### Exporting pull requests 57 | 58 | ``` 59 | github-artifact-exporter.exe repo:pulls --owner github --repo caseflow --token $GITHUB_TOKEN --format CSV > pulls_export.csv 60 | ``` 61 | 62 | # Commands 63 | 64 | 65 | * [`github-artifacts-exporter help [COMMAND]`](#github-artifacts-exporter-help-command) 66 | * [`github-artifacts-exporter repo`](#github-artifacts-exporter-repo) 67 | * [`github-artifacts-exporter repo:commits`](#github-artifacts-exporter-repocommits) 68 | * [`github-artifacts-exporter repo:milestones`](#github-artifacts-exporter-repomilestones) 69 | * [`github-artifacts-exporter repo:projects`](#github-artifacts-exporter-repoprojects) 70 | * [`github-artifacts-exporter repo:pulls`](#github-artifacts-exporter-repopulls) 71 | * [`github-artifacts-exporter repo:releases`](#github-artifacts-exporter-reporeleases) 72 | * [`github-artifacts-exporter search`](#github-artifacts-exporter-search) 73 | * [`github-artifacts-exporter search:issues`](#github-artifacts-exporter-searchissues) 74 | 75 | ## `github-artifacts-exporter help [COMMAND]` 76 | 77 | display help for github-artifacts-exporter 78 | 79 | ``` 80 | USAGE 81 | $ github-artifacts-exporter help [COMMAND] 82 | 83 | ARGUMENTS 84 | COMMAND command to show help for 85 | 86 | OPTIONS 87 | --all see all commands in CLI 88 | ``` 89 | 90 | _See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v3.2.0/src/commands/help.ts)_ 91 | 92 | ## `github-artifacts-exporter repo` 93 | 94 | Export GitHub artifacts from a repository 95 | 96 | ``` 97 | USAGE 98 | $ github-artifacts-exporter repo 99 | 100 | OPTIONS 101 | --baseUrl=baseUrl [default: https://api.github.com] GitHub base url 102 | --format=(JSONL|JSON|CSV) [default: JSONL] export format 103 | --owner=owner GitHub repository owner 104 | --repo=repo GitHub repository name 105 | --token=token (required) GitHub personal access token 106 | ``` 107 | 108 | _See code: [src/commands/repo.ts](https://github.com/github/github-artifact-exporter/blob/v2.0.2/src/commands/repo.ts)_ 109 | 110 | ## `github-artifacts-exporter repo:commits` 111 | 112 | Export GitHub Commits for a repository 113 | 114 | ``` 115 | USAGE 116 | $ github-artifacts-exporter repo:commits 117 | 118 | OPTIONS 119 | --baseUrl=baseUrl [default: https://api.github.com] GitHub base url 120 | --branch=branch [default: master] git branch to export commits for 121 | --format=(JSONL|JSON|CSV) [default: JSONL] export format 122 | --owner=owner GitHub repository owner 123 | --repo=repo GitHub repository name 124 | --since=since search commits created after yyyy-mm-dd 125 | --token=token (required) GitHub personal access token 126 | --until=until search commits created before yyyy-mm-dd 127 | ``` 128 | 129 | _See code: [src/commands/repo/commits.ts](https://github.com/github/github-artifact-exporter/blob/v2.0.2/src/commands/repo/commits.ts)_ 130 | 131 | ## `github-artifacts-exporter repo:milestones` 132 | 133 | Export GitHub Milestones for a repository 134 | 135 | ``` 136 | USAGE 137 | $ github-artifacts-exporter repo:milestones 138 | 139 | OPTIONS 140 | --baseUrl=baseUrl [default: https://api.github.com] GitHub base url 141 | --format=(JSONL|JSON|CSV) [default: JSONL] export format 142 | --owner=owner GitHub repository owner 143 | --repo=repo GitHub repository name 144 | --token=token (required) GitHub personal access token 145 | ``` 146 | 147 | _See code: [src/commands/repo/milestones.ts](https://github.com/github/github-artifact-exporter/blob/v2.0.2/src/commands/repo/milestones.ts)_ 148 | 149 | ## `github-artifacts-exporter repo:projects` 150 | 151 | Export GitHub Milestones for a repository 152 | 153 | ``` 154 | USAGE 155 | $ github-artifacts-exporter repo:projects 156 | 157 | OPTIONS 158 | --baseUrl=baseUrl [default: https://api.github.com] GitHub base url 159 | --format=(JSONL|JSON|CSV) [default: JSONL] export format 160 | --owner=owner GitHub repository owner 161 | --projectNumber=projectNumber Project number from where to pull cards 162 | --repo=repo GitHub repository name 163 | --token=token (required) GitHub personal access token 164 | ``` 165 | 166 | _See code: [src/commands/repo/projects.ts](https://github.com/github/github-artifact-exporter/blob/v2.0.2/src/commands/repo/projects.ts)_ 167 | 168 | ## `github-artifacts-exporter repo:pulls` 169 | 170 | Export GitHub Pull Requests for a repository 171 | 172 | ``` 173 | USAGE 174 | $ github-artifacts-exporter repo:pulls 175 | 176 | OPTIONS 177 | --baseUrl=baseUrl [default: https://api.github.com] GitHub base url 178 | --format=(JSONL|JSON|CSV) [default: JSONL] export format 179 | --owner=owner (required) GitHub repository owner 180 | --repo=repo (required) GitHub repository name 181 | --since=since search pull requests created after yyyy-mm-dd 182 | --token=token (required) GitHub personal access token 183 | --until=until search pull requests created before yyyy-mm-dd 184 | ``` 185 | 186 | _See code: [src/commands/repo/pulls.ts](https://github.com/github/github-artifact-exporter/blob/v2.0.2/src/commands/repo/pulls.ts)_ 187 | 188 | ## `github-artifacts-exporter repo:releases` 189 | 190 | Export GitHub Releases for a repository 191 | 192 | ``` 193 | USAGE 194 | $ github-artifacts-exporter repo:releases 195 | 196 | OPTIONS 197 | --baseUrl=baseUrl [default: https://api.github.com] GitHub base url 198 | --format=(JSONL|JSON|CSV) [default: JSONL] export format 199 | --owner=owner GitHub repository owner 200 | --repo=repo GitHub repository name 201 | --token=token (required) GitHub personal access token 202 | ``` 203 | 204 | _See code: [src/commands/repo/releases.ts](https://github.com/github/github-artifact-exporter/blob/v2.0.2/src/commands/repo/releases.ts)_ 205 | 206 | ## `github-artifacts-exporter search` 207 | 208 | GitHub Search base command 209 | 210 | ``` 211 | USAGE 212 | $ github-artifacts-exporter search 213 | 214 | OPTIONS 215 | --baseUrl=baseUrl [default: https://api.github.com] GitHub base url 216 | --format=(JSONL|JSON|CSV) [default: JSONL] export format 217 | --owner=owner GitHub repository owner 218 | --repo=repo GitHub repository name 219 | --token=token (required) GitHub personal access token 220 | ``` 221 | 222 | _See code: [src/commands/search.ts](https://github.com/github/github-artifact-exporter/blob/v2.0.2/src/commands/search.ts)_ 223 | 224 | ## `github-artifacts-exporter search:issues` 225 | 226 | Export GitHub Issues using Search 227 | 228 | ``` 229 | USAGE 230 | $ github-artifacts-exporter search:issues 231 | 232 | OPTIONS 233 | --baseUrl=baseUrl [default: https://api.github.com] GitHub base url 234 | --dateFormat=dateFormat [default: isoDateTime] Date format to use when building issue list. Examples: mm/dd/yyyy 235 | --format=(JSONL|JSON|CSV) [default: JSONL] export format 236 | --jira transform output into a usable format for importing to Jira 237 | --labels=labels search issues with these labels (comma seperated) 238 | --owner=owner GitHub repository owner 239 | --query=query Search query matching GitHub issue search syntax 240 | --repo=repo GitHub repository name 241 | --since=since search issues created after yyyy-mm-dd 242 | --state=(open|closed) search issues in this state 243 | --token=token (required) GitHub personal access token 244 | --until=until search issues created before yyyy-mm-dd 245 | --updatedSince=updatedSince search issues updated after yyyy-mm-dd 246 | --updatedUntil=updatedUntil search issues updated before yyyy-mm-dd 247 | ``` 248 | 249 | _See code: [src/commands/search/issues.ts](https://github.com/github/github-artifact-exporter/blob/v2.0.2/src/commands/search/issues.ts)_ 250 | 251 | -------------------------------------------------------------------------------- /packages/cli/bin/run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('@oclif/command').run() 3 | .then(require('@oclif/command/flush')) 4 | .catch(require('@oclif/errors/handle')) 5 | -------------------------------------------------------------------------------- /packages/cli/bin/run.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | node "%~dp0\run" %* 4 | -------------------------------------------------------------------------------- /packages/cli/lib/base.d.ts: -------------------------------------------------------------------------------- 1 | import Command, { flags as flagTypes } from "@oclif/command"; 2 | import { IConfig } from "@oclif/config"; 3 | import { Octokit } from "@octokit/rest"; 4 | export default abstract class Base extends Command { 5 | constructor(argv: string[], config: IConfig); 6 | github: Octokit; 7 | static flags: { 8 | baseUrl: flagTypes.IOptionFlag; 9 | token: flagTypes.IOptionFlag; 10 | owner: flagTypes.IOptionFlag; 11 | repo: flagTypes.IOptionFlag; 12 | format: flagTypes.IOptionFlag; 13 | }; 14 | /** 15 | * Parse date into ISO or "*" if null/undefined this 16 | * allows it to be used with the `created` filter 17 | * for GitHub Search 18 | * 19 | * @param {string} flagName 20 | * @param {string} date 21 | * @returns {string} 22 | */ 23 | parseDateFlag(flagName: string, date: string | undefined): string; 24 | init(): Promise; 25 | } 26 | //# sourceMappingURL=base.d.ts.map -------------------------------------------------------------------------------- /packages/cli/lib/base.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../src/base.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,EAAE,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC7D,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAGxC,MAAM,CAAC,OAAO,CAAC,QAAQ,OAAO,IAAK,SAAQ,OAAO;gBACpC,IAAI,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,OAAO;IAI3C,MAAM,EAAE,OAAO,CAAiB;IAEhC,MAAM,CAAC,KAAK;;;;;;MA2BV;IAEF;;;;;;;;OAQG;IACH,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM;IAkB3D,IAAI;CAoBX"} -------------------------------------------------------------------------------- /packages/cli/lib/base.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const command_1 = require("@oclif/command"); 4 | const rest_1 = require("@octokit/rest"); 5 | const luxon_1 = require("luxon"); 6 | class Base extends command_1.default { 7 | constructor(argv, config) { 8 | super(argv, config); 9 | // this is immediately overwritten in the init method 10 | this.github = new rest_1.Octokit(); 11 | } 12 | /** 13 | * Parse date into ISO or "*" if null/undefined this 14 | * allows it to be used with the `created` filter 15 | * for GitHub Search 16 | * 17 | * @param {string} flagName 18 | * @param {string} date 19 | * @returns {string} 20 | */ 21 | parseDateFlag(flagName, date) { 22 | let searchDate = "*"; 23 | if (date) { 24 | const datetime = luxon_1.DateTime.fromFormat(date, "yyyy-MM-dd"); 25 | if (!datetime.isValid) { 26 | throw new Error(`unable to parse flag "${flagName}"\n${datetime.invalidExplanation}`); 27 | } 28 | searchDate = datetime.toISODate(); 29 | } 30 | return searchDate; 31 | } 32 | async init() { 33 | const { flags: { baseUrl, token }, } = this.parse(this.constructor); 34 | this.github = new rest_1.Octokit({ 35 | baseUrl, 36 | auth: token, 37 | }); 38 | } 39 | } 40 | exports.default = Base; 41 | Base.flags = { 42 | baseUrl: command_1.flags.string({ 43 | description: "GitHub base url", 44 | default: "https://api.github.com", 45 | }), 46 | token: command_1.flags.string({ 47 | description: "GitHub personal access token", 48 | env: "GITHUB_TOKEN", 49 | required: true, 50 | }), 51 | owner: command_1.flags.string({ 52 | dependsOn: ["repo"], 53 | description: "GitHub repository owner", 54 | }), 55 | repo: command_1.flags.string({ 56 | dependsOn: ["owner"], 57 | description: "GitHub repository name", 58 | }), 59 | format: command_1.flags.enum({ 60 | options: ["JSONL", "JSON", "CSV"], 61 | default: "JSONL", 62 | description: "export format", 63 | }), 64 | }; 65 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYmFzZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9iYXNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBQUEsNENBQTZEO0FBRTdELHdDQUF3QztBQUN4QyxpQ0FBaUM7QUFFakMsTUFBOEIsSUFBSyxTQUFRLGlCQUFPO0lBQ2hELFlBQVksSUFBYyxFQUFFLE1BQWU7UUFDekMsS0FBSyxDQUFDLElBQUksRUFBRSxNQUFNLENBQUMsQ0FBQztRQUV0QixxREFBcUQ7UUFDckQsV0FBTSxHQUFZLElBQUksY0FBTyxFQUFFLENBQUM7SUFGaEMsQ0FBQztJQWlDRDs7Ozs7Ozs7T0FRRztJQUNILGFBQWEsQ0FBQyxRQUFnQixFQUFFLElBQXdCO1FBQ3RELElBQUksVUFBVSxHQUFHLEdBQUcsQ0FBQztRQUVyQixJQUFJLElBQUksRUFBRTtZQUNSLE1BQU0sUUFBUSxHQUFHLGdCQUFRLENBQUMsVUFBVSxDQUFDLElBQUksRUFBRSxZQUFZLENBQUMsQ0FBQztZQUV6RCxJQUFJLENBQUMsUUFBUSxDQUFDLE9BQU8sRUFBRTtnQkFDckIsTUFBTSxJQUFJLEtBQUssQ0FDYix5QkFBeUIsUUFBUSxNQUFNLFFBQVEsQ0FBQyxrQkFBa0IsRUFBRSxDQUNyRSxDQUFDO2FBQ0g7WUFFRCxVQUFVLEdBQUcsUUFBUSxDQUFDLFNBQVMsRUFBRSxDQUFDO1NBQ25DO1FBRUQsT0FBTyxVQUFVLENBQUM7SUFDcEIsQ0FBQztJQUVELEtBQUssQ0FBQyxJQUFJO1FBQ1IsTUFBTSxFQUNKLEtBQUssRUFBRSxFQUFFLE9BQU8sRUFBRSxLQUFLLEVBQUUsR0FXMUIsR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsQ0FBQztRQUVqQyxJQUFJLENBQUMsTUFBTSxHQUFHLElBQUksY0FBTyxDQUFDO1lBQ3hCLE9BQU87WUFDUCxJQUFJLEVBQUUsS0FBSztTQUNaLENBQUMsQ0FBQztJQUNMLENBQUM7O0FBbEZILHVCQW1GQztBQTVFUSxVQUFLLEdBQUc7SUFDYixPQUFPLEVBQUUsZUFBUyxDQUFDLE1BQU0sQ0FBQztRQUN4QixXQUFXLEVBQUUsaUJBQWlCO1FBQzlCLE9BQU8sRUFBRSx3QkFBd0I7S0FDbEMsQ0FBQztJQUVGLEtBQUssRUFBRSxlQUFTLENBQUMsTUFBTSxDQUFDO1FBQ3RCLFdBQVcsRUFBRSw4QkFBOEI7UUFDM0MsR0FBRyxFQUFFLGNBQWM7UUFDbkIsUUFBUSxFQUFFLElBQUk7S0FDZixDQUFDO0lBRUYsS0FBSyxFQUFFLGVBQVMsQ0FBQyxNQUFNLENBQUM7UUFDdEIsU0FBUyxFQUFFLENBQUMsTUFBTSxDQUFDO1FBQ25CLFdBQVcsRUFBRSx5QkFBeUI7S0FDdkMsQ0FBQztJQUVGLElBQUksRUFBRSxlQUFTLENBQUMsTUFBTSxDQUFDO1FBQ3JCLFNBQVMsRUFBRSxDQUFDLE9BQU8sQ0FBQztRQUNwQixXQUFXLEVBQUUsd0JBQXdCO0tBQ3RDLENBQUM7SUFFRixNQUFNLEVBQUUsZUFBUyxDQUFDLElBQUksQ0FBQztRQUNyQixPQUFPLEVBQUUsQ0FBQyxPQUFPLEVBQUUsTUFBTSxFQUFFLEtBQUssQ0FBQztRQUNqQyxPQUFPLEVBQUUsT0FBTztRQUNoQixXQUFXLEVBQUUsZUFBZTtLQUM3QixDQUFDO0NBQ0gsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBDb21tYW5kLCB7IGZsYWdzIGFzIGZsYWdUeXBlcyB9IGZyb20gXCJAb2NsaWYvY29tbWFuZFwiO1xuaW1wb3J0IHsgSUNvbmZpZyB9IGZyb20gXCJAb2NsaWYvY29uZmlnXCI7XG5pbXBvcnQgeyBPY3Rva2l0IH0gZnJvbSBcIkBvY3Rva2l0L3Jlc3RcIjtcbmltcG9ydCB7IERhdGVUaW1lIH0gZnJvbSBcImx1eG9uXCI7XG5cbmV4cG9ydCBkZWZhdWx0IGFic3RyYWN0IGNsYXNzIEJhc2UgZXh0ZW5kcyBDb21tYW5kIHtcbiAgY29uc3RydWN0b3IoYXJndjogc3RyaW5nW10sIGNvbmZpZzogSUNvbmZpZykge1xuICAgIHN1cGVyKGFyZ3YsIGNvbmZpZyk7XG4gIH1cbiAgLy8gdGhpcyBpcyBpbW1lZGlhdGVseSBvdmVyd3JpdHRlbiBpbiB0aGUgaW5pdCBtZXRob2RcbiAgZ2l0aHViOiBPY3Rva2l0ID0gbmV3IE9jdG9raXQoKTtcblxuICBzdGF0aWMgZmxhZ3MgPSB7XG4gICAgYmFzZVVybDogZmxhZ1R5cGVzLnN0cmluZyh7XG4gICAgICBkZXNjcmlwdGlvbjogXCJHaXRIdWIgYmFzZSB1cmxcIixcbiAgICAgIGRlZmF1bHQ6IFwiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbVwiLFxuICAgIH0pLFxuXG4gICAgdG9rZW46IGZsYWdUeXBlcy5zdHJpbmcoe1xuICAgICAgZGVzY3JpcHRpb246IFwiR2l0SHViIHBlcnNvbmFsIGFjY2VzcyB0b2tlblwiLFxuICAgICAgZW52OiBcIkdJVEhVQl9UT0tFTlwiLFxuICAgICAgcmVxdWlyZWQ6IHRydWUsXG4gICAgfSksXG5cbiAgICBvd25lcjogZmxhZ1R5cGVzLnN0cmluZyh7XG4gICAgICBkZXBlbmRzT246IFtcInJlcG9cIl0sXG4gICAgICBkZXNjcmlwdGlvbjogXCJHaXRIdWIgcmVwb3NpdG9yeSBvd25lclwiLFxuICAgIH0pLFxuXG4gICAgcmVwbzogZmxhZ1R5cGVzLnN0cmluZyh7XG4gICAgICBkZXBlbmRzT246IFtcIm93bmVyXCJdLFxuICAgICAgZGVzY3JpcHRpb246IFwiR2l0SHViIHJlcG9zaXRvcnkgbmFtZVwiLFxuICAgIH0pLFxuXG4gICAgZm9ybWF0OiBmbGFnVHlwZXMuZW51bSh7XG4gICAgICBvcHRpb25zOiBbXCJKU09OTFwiLCBcIkpTT05cIiwgXCJDU1ZcIl0sXG4gICAgICBkZWZhdWx0OiBcIkpTT05MXCIsXG4gICAgICBkZXNjcmlwdGlvbjogXCJleHBvcnQgZm9ybWF0XCIsXG4gICAgfSksXG4gIH07XG5cbiAgLyoqXG4gICAqIFBhcnNlIGRhdGUgaW50byBJU08gb3IgXCIqXCIgaWYgbnVsbC91bmRlZmluZWQgdGhpc1xuICAgKiBhbGxvd3MgaXQgdG8gYmUgdXNlZCB3aXRoIHRoZSBgY3JlYXRlZGAgZmlsdGVyXG4gICAqIGZvciBHaXRIdWIgU2VhcmNoXG4gICAqXG4gICAqIEBwYXJhbSB7c3RyaW5nfSBmbGFnTmFtZVxuICAgKiBAcGFyYW0ge3N0cmluZ30gZGF0ZVxuICAgKiBAcmV0dXJucyB7c3RyaW5nfVxuICAgKi9cbiAgcGFyc2VEYXRlRmxhZyhmbGFnTmFtZTogc3RyaW5nLCBkYXRlOiBzdHJpbmcgfCB1bmRlZmluZWQpOiBzdHJpbmcge1xuICAgIGxldCBzZWFyY2hEYXRlID0gXCIqXCI7XG5cbiAgICBpZiAoZGF0ZSkge1xuICAgICAgY29uc3QgZGF0ZXRpbWUgPSBEYXRlVGltZS5mcm9tRm9ybWF0KGRhdGUsIFwieXl5eS1NTS1kZFwiKTtcblxuICAgICAgaWYgKCFkYXRldGltZS5pc1ZhbGlkKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgICBgdW5hYmxlIHRvIHBhcnNlIGZsYWcgXCIke2ZsYWdOYW1lfVwiXFxuJHtkYXRldGltZS5pbnZhbGlkRXhwbGFuYXRpb259YFxuICAgICAgICApO1xuICAgICAgfVxuXG4gICAgICBzZWFyY2hEYXRlID0gZGF0ZXRpbWUudG9JU09EYXRlKCk7XG4gICAgfVxuXG4gICAgcmV0dXJuIHNlYXJjaERhdGU7XG4gIH1cblxuICBhc3luYyBpbml0KCkge1xuICAgIGNvbnN0IHtcbiAgICAgIGZsYWdzOiB7IGJhc2VVcmwsIHRva2VuIH0sXG4gICAgICAvKlxuICAgICAgICogVGhlc2UgbmV4dCBsaW5lcyBhcmUgcmVxdWlyZWQgZm9yIHBhcnNpbmcgZmxhZ3Mgb24gY29tbWFuZHNcbiAgICAgICAqIHRoYXQgZXh0ZW5kIGZyb20gdGhpcyBiYXNlIGNsYXNzIHNpbmNlIHRoZXJlIGlzIGEgdHlwZSBtaXNtYXRjaFxuICAgICAgICogYmV0d2VlbiB0aGlzLnBhcnNlIGFuZCB0aGlzLmNvbnN0cnVjdG9yLlxuICAgICAgICpcbiAgICAgICAqIFNlZSBgaW5pdGAgaW4gaHR0cHM6Ly9vY2xpZi5pby9kb2NzL2Jhc2VfY2xhc3Mgc2hvd2luZyB0aGF0IGlzIHRoZVxuICAgICAgICogcmlnaHQgd2F5IGV2ZW4gdGhvdWdoIHdlIGhhdmUgdG8gZGlzYWJsZSBvdXIgbGludGVyXG4gICAgICAgKi9cbiAgICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBAdHlwZXNjcmlwdC1lc2xpbnQvYmFuLXRzLWlnbm9yZVxuICAgICAgLy8gQHRzLWlnbm9yZVxuICAgIH0gPSB0aGlzLnBhcnNlKHRoaXMuY29uc3RydWN0b3IpO1xuXG4gICAgdGhpcy5naXRodWIgPSBuZXcgT2N0b2tpdCh7XG4gICAgICBiYXNlVXJsLFxuICAgICAgYXV0aDogdG9rZW4sXG4gICAgfSk7XG4gIH1cbn1cbiJdfQ== -------------------------------------------------------------------------------- /packages/cli/lib/commands/repo.d.ts: -------------------------------------------------------------------------------- 1 | import BaseCommand from "../base"; 2 | export default class Repo extends BaseCommand { 3 | static description: string; 4 | run(): Promise; 5 | } 6 | //# sourceMappingURL=repo.d.ts.map -------------------------------------------------------------------------------- /packages/cli/lib/commands/repo.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"repo.d.ts","sourceRoot":"","sources":["../../src/commands/repo.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,MAAM,SAAS,CAAC;AAElC,MAAM,CAAC,OAAO,OAAO,IAAK,SAAQ,WAAW;IAC3C,MAAM,CAAC,WAAW,SAA+C;IAE3D,GAAG;CAGV"} -------------------------------------------------------------------------------- /packages/cli/lib/commands/repo.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const base_1 = require("../base"); 4 | class Repo extends base_1.default { 5 | async run() { 6 | this._help(); 7 | } 8 | } 9 | exports.default = Repo; 10 | Repo.description = "Export GitHub artifacts from a repository"; 11 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVwby5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9jb21tYW5kcy9yZXBvLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBQUEsa0NBQWtDO0FBRWxDLE1BQXFCLElBQUssU0FBUSxjQUFXO0lBRzNDLEtBQUssQ0FBQyxHQUFHO1FBQ1AsSUFBSSxDQUFDLEtBQUssRUFBRSxDQUFDO0lBQ2YsQ0FBQzs7QUFMSCx1QkFNQztBQUxRLGdCQUFXLEdBQUcsMkNBQTJDLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgQmFzZUNvbW1hbmQgZnJvbSBcIi4uL2Jhc2VcIjtcblxuZXhwb3J0IGRlZmF1bHQgY2xhc3MgUmVwbyBleHRlbmRzIEJhc2VDb21tYW5kIHtcbiAgc3RhdGljIGRlc2NyaXB0aW9uID0gXCJFeHBvcnQgR2l0SHViIGFydGlmYWN0cyBmcm9tIGEgcmVwb3NpdG9yeVwiO1xuXG4gIGFzeW5jIHJ1bigpIHtcbiAgICB0aGlzLl9oZWxwKCk7XG4gIH1cbn1cbiJdfQ== -------------------------------------------------------------------------------- /packages/cli/lib/commands/repo/commits.d.ts: -------------------------------------------------------------------------------- 1 | import { flags as flagTypes } from "@oclif/command"; 2 | import BaseCommand from "../../base"; 3 | export default class RepoCommits extends BaseCommand { 4 | static description: string; 5 | static flags: { 6 | branch: flagTypes.IOptionFlag; 7 | since: flagTypes.IOptionFlag; 8 | until: flagTypes.IOptionFlag; 9 | baseUrl: flagTypes.IOptionFlag; 10 | token: flagTypes.IOptionFlag; 11 | owner: flagTypes.IOptionFlag; 12 | repo: flagTypes.IOptionFlag; 13 | format: flagTypes.IOptionFlag; 14 | }; 15 | run(): Promise; 16 | } 17 | //# sourceMappingURL=commits.d.ts.map -------------------------------------------------------------------------------- /packages/cli/lib/commands/repo/commits.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"commits.d.ts","sourceRoot":"","sources":["../../../src/commands/repo/commits.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAKpD,OAAO,WAAW,MAAM,YAAY,CAAC;AAuCrC,MAAM,CAAC,OAAO,OAAO,WAAY,SAAQ,WAAW;IAClD,MAAM,CAAC,WAAW,SAA4C;IAE9D,MAAM,CAAC,KAAK;;;;;;;;;MAYV;IAEI,GAAG;CAgFV"} -------------------------------------------------------------------------------- /packages/cli/lib/commands/repo/commits.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* globals process */ 3 | Object.defineProperty(exports, "__esModule", { value: true }); 4 | const command_1 = require("@oclif/command"); 5 | const debug_1 = require("debug"); 6 | const dot = require("dot-object"); 7 | const ProgressBar = require("progress"); 8 | const jsonexport = require("jsonexport"); 9 | const base_1 = require("../../base"); 10 | const debug = debug_1.default("exporter:repo:commits"); 11 | const LIST_COMMITS_QUERY = `query listCommits($owner: String!, $repo: String!, $branch: String!, $per_page: Int = 50, $after: String, $since: GitTimestamp, $until: GitTimestamp) { 12 | repository(owner: $owner, name: $repo) { 13 | ref(qualifiedName: $branch) { 14 | name 15 | target { 16 | ... on Commit { 17 | history(first: $per_page, after: $after, since: $since, until: $until) { 18 | nodes { 19 | author { 20 | name 21 | email 22 | } 23 | additions 24 | associatedPullRequests(first: 10) { 25 | nodes { 26 | title 27 | url 28 | } 29 | } 30 | messageHeadline 31 | } 32 | pageInfo { 33 | endCursor 34 | hasNextPage 35 | } 36 | totalCount 37 | } 38 | } 39 | } 40 | } 41 | } 42 | } 43 | `; 44 | class RepoCommits extends base_1.default { 45 | async run() { 46 | const commits = []; 47 | const { flags } = this.parse(RepoCommits); 48 | const { branch, owner, repo, format, since, until } = flags; 49 | if (!since || !until) { 50 | this.warn("Exporting commits can be slow. Please consider narrowing your time range with `since` and `until`"); 51 | } 52 | let start; 53 | let end; 54 | if (since) { 55 | start = this.parseDateFlag("since", since); 56 | } 57 | if (until) { 58 | end = this.parseDateFlag("end", until); 59 | } 60 | let results; 61 | let cursor; 62 | let progress; 63 | // paginate through the GraphQL query until we get everything 64 | debug("Pulling commits from API"); 65 | do { 66 | results = await this.github.graphql(LIST_COMMITS_QUERY, { 67 | branch, 68 | owner, 69 | repo, 70 | after: cursor, 71 | since: start, 72 | until: end, 73 | }); 74 | cursor = results.repository.ref.target.history.pageInfo.endCursor; 75 | if (!progress) { 76 | if (results.repository.ref.target.history.totalCount === 0) { 77 | this.warn("No commits found"); 78 | process.exit(1); 79 | } 80 | progress = new ProgressBar("fetching commits [:bar] :current/:total :percent", { 81 | complete: "=", 82 | incomplete: " ", 83 | width: 20, 84 | total: results.repository.ref.target.history.totalCount, 85 | }); 86 | } 87 | progress.tick(results.repository.ref.target.history.nodes.length); 88 | for (const commit of results.repository.ref.target.history.nodes) { 89 | dot.move("associatedPullRequests.nodes", "associatedPullRequests", commit); 90 | } 91 | commits.push(...results.repository.ref.target.history.nodes); 92 | } while (results.repository.ref.target.history.pageInfo.hasNextPage); 93 | if (format === "JSONL") { 94 | for (const release of commits) { 95 | process.stdout.write(`${JSON.stringify(release)}\n`); 96 | } 97 | } 98 | else if (format === "JSON") { 99 | process.stdout.write(JSON.stringify(commits)); 100 | } 101 | else if (format === "CSV") { 102 | const csv = await jsonexport(commits, { fillGaps: true }); 103 | process.stdout.write(csv); 104 | } 105 | } 106 | } 107 | exports.default = RepoCommits; 108 | RepoCommits.description = "Export GitHub Commits for a repository"; 109 | RepoCommits.flags = Object.assign(Object.assign({}, base_1.default.flags), { branch: command_1.flags.string({ 110 | default: "master", 111 | description: "git branch to export commits for", 112 | }), since: command_1.flags.string({ 113 | description: "search commits created after yyyy-mm-dd", 114 | }), until: command_1.flags.string({ 115 | description: "search commits created before yyyy-mm-dd", 116 | }) }); 117 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29tbWl0cy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9jb21tYW5kcy9yZXBvL2NvbW1pdHMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBLHFCQUFxQjs7QUFFckIsNENBQW9EO0FBQ3BELGlDQUFtQztBQUNuQyxrQ0FBa0M7QUFDbEMsd0NBQXdDO0FBQ3hDLHlDQUF5QztBQUN6QyxxQ0FBcUM7QUFHckMsTUFBTSxLQUFLLEdBQUcsZUFBYyxDQUFDLHVCQUF1QixDQUFDLENBQUM7QUFFdEQsTUFBTSxrQkFBa0IsR0FBRzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Q0FnQzFCLENBQUM7QUFFRixNQUFxQixXQUFZLFNBQVEsY0FBVztJQWlCbEQsS0FBSyxDQUFDLEdBQUc7UUFDUCxNQUFNLE9BQU8sR0FBRyxFQUFFLENBQUM7UUFFbkIsTUFBTSxFQUFFLEtBQUssRUFBRSxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsV0FBVyxDQUFDLENBQUM7UUFDMUMsTUFBTSxFQUFFLE1BQU0sRUFBRSxLQUFLLEVBQUUsSUFBSSxFQUFFLE1BQU0sRUFBRSxLQUFLLEVBQUUsS0FBSyxFQUFFLEdBQUcsS0FBSyxDQUFDO1FBRTVELElBQUksQ0FBQyxLQUFLLElBQUksQ0FBQyxLQUFLLEVBQUU7WUFDcEIsSUFBSSxDQUFDLElBQUksQ0FDUCxtR0FBbUcsQ0FDcEcsQ0FBQztTQUNIO1FBRUQsSUFBSSxLQUFLLENBQUM7UUFDVixJQUFJLEdBQUcsQ0FBQztRQUVSLElBQUksS0FBSyxFQUFFO1lBQ1QsS0FBSyxHQUFHLElBQUksQ0FBQyxhQUFhLENBQUMsT0FBTyxFQUFFLEtBQUssQ0FBQyxDQUFDO1NBQzVDO1FBRUQsSUFBSSxLQUFLLEVBQUU7WUFDVCxHQUFHLEdBQUcsSUFBSSxDQUFDLGFBQWEsQ0FBQyxLQUFLLEVBQUUsS0FBSyxDQUFDLENBQUM7U0FDeEM7UUFFRCxJQUFJLE9BQTBCLENBQUM7UUFDL0IsSUFBSSxNQUFNLENBQUM7UUFDWCxJQUFJLFFBQVEsQ0FBQztRQUViLDZEQUE2RDtRQUM3RCxLQUFLLENBQUMsMEJBQTBCLENBQUMsQ0FBQztRQUNsQyxHQUFHO1lBQ0QsT0FBTyxHQUFHLE1BQU0sSUFBSSxDQUFDLE1BQU0sQ0FBQyxPQUFPLENBQUMsa0JBQWtCLEVBQUU7Z0JBQ3RELE1BQU07Z0JBQ04sS0FBSztnQkFDTCxJQUFJO2dCQUNKLEtBQUssRUFBRSxNQUFNO2dCQUNiLEtBQUssRUFBRSxLQUFLO2dCQUNaLEtBQUssRUFBRSxHQUFHO2FBQ1gsQ0FBQyxDQUFDO1lBQ0gsTUFBTSxHQUFHLE9BQU8sQ0FBQyxVQUFVLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDLFNBQVMsQ0FBQztZQUVsRSxJQUFJLENBQUMsUUFBUSxFQUFFO2dCQUNiLElBQUksT0FBTyxDQUFDLFVBQVUsQ0FBQyxHQUFHLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxVQUFVLEtBQUssQ0FBQyxFQUFFO29CQUMxRCxJQUFJLENBQUMsSUFBSSxDQUFDLGtCQUFrQixDQUFDLENBQUM7b0JBQzlCLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7aUJBQ2pCO2dCQUVELFFBQVEsR0FBRyxJQUFJLFdBQVcsQ0FDeEIsa0RBQWtELEVBQ2xEO29CQUNFLFFBQVEsRUFBRSxHQUFHO29CQUNiLFVBQVUsRUFBRSxHQUFHO29CQUNmLEtBQUssRUFBRSxFQUFFO29CQUNULEtBQUssRUFBRSxPQUFPLENBQUMsVUFBVSxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsT0FBTyxDQUFDLFVBQVU7aUJBQ3hELENBQ0YsQ0FBQzthQUNIO1lBRUQsUUFBUSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsVUFBVSxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxNQUFNLENBQUMsQ0FBQztZQUVsRSxLQUFLLE1BQU0sTUFBTSxJQUFJLE9BQU8sQ0FBQyxVQUFVLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxPQUFPLENBQUMsS0FBSyxFQUFFO2dCQUNoRSxHQUFHLENBQUMsSUFBSSxDQUNOLDhCQUE4QixFQUM5Qix3QkFBd0IsRUFDeEIsTUFBTSxDQUNQLENBQUM7YUFDSDtZQUNELE9BQU8sQ0FBQyxJQUFJLENBQUMsR0FBRyxPQUFPLENBQUMsVUFBVSxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxDQUFDO1NBQzlELFFBQVEsT0FBTyxDQUFDLFVBQVUsQ0FBQyxHQUFHLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxRQUFRLENBQUMsV0FBVyxFQUFFO1FBRXJFLElBQUksTUFBTSxLQUFLLE9BQU8sRUFBRTtZQUN0QixLQUFLLE1BQU0sT0FBTyxJQUFJLE9BQU8sRUFBRTtnQkFDN0IsT0FBTyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQzthQUN0RDtTQUNGO2FBQU0sSUFBSSxNQUFNLEtBQUssTUFBTSxFQUFFO1lBQzVCLE9BQU8sQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQztTQUMvQzthQUFNLElBQUksTUFBTSxLQUFLLEtBQUssRUFBRTtZQUMzQixNQUFNLEdBQUcsR0FBRyxNQUFNLFVBQVUsQ0FBQyxPQUFPLEVBQUUsRUFBRSxRQUFRLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQztZQUMxRCxPQUFPLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQztTQUMzQjtJQUNILENBQUM7O0FBaEdILDhCQWlHQztBQWhHUSx1QkFBVyxHQUFHLHdDQUF3QyxDQUFDO0FBRXZELGlCQUFLLG1DQUNQLGNBQVcsQ0FBQyxLQUFLLEtBQ3BCLE1BQU0sRUFBRSxlQUFTLENBQUMsTUFBTSxDQUFDO1FBQ3ZCLE9BQU8sRUFBRSxRQUFRO1FBQ2pCLFdBQVcsRUFBRSxrQ0FBa0M7S0FDaEQsQ0FBQyxFQUNGLEtBQUssRUFBRSxlQUFTLENBQUMsTUFBTSxDQUFDO1FBQ3RCLFdBQVcsRUFBRSx5Q0FBeUM7S0FDdkQsQ0FBQyxFQUNGLEtBQUssRUFBRSxlQUFTLENBQUMsTUFBTSxDQUFDO1FBQ3RCLFdBQVcsRUFBRSwwQ0FBMEM7S0FDeEQsQ0FBQyxJQUNGIiwic291cmNlc0NvbnRlbnQiOlsiLyogZ2xvYmFscyBwcm9jZXNzICovXG5cbmltcG9ydCB7IGZsYWdzIGFzIGZsYWdUeXBlcyB9IGZyb20gXCJAb2NsaWYvY29tbWFuZFwiO1xuaW1wb3J0IGNyZWF0ZURlYnVnZ2VyIGZyb20gXCJkZWJ1Z1wiO1xuaW1wb3J0ICogYXMgZG90IGZyb20gXCJkb3Qtb2JqZWN0XCI7XG5pbXBvcnQgKiBhcyBQcm9ncmVzc0JhciBmcm9tIFwicHJvZ3Jlc3NcIjtcbmltcG9ydCAqIGFzIGpzb25leHBvcnQgZnJvbSBcImpzb25leHBvcnRcIjtcbmltcG9ydCBCYXNlQ29tbWFuZCBmcm9tIFwiLi4vLi4vYmFzZVwiO1xuaW1wb3J0IHsgUmVwb3NpdG9yeUNvbW1pdHMgfSBmcm9tIFwiLi4vLi4vZ2l0aHViXCI7XG5cbmNvbnN0IGRlYnVnID0gY3JlYXRlRGVidWdnZXIoXCJleHBvcnRlcjpyZXBvOmNvbW1pdHNcIik7XG5cbmNvbnN0IExJU1RfQ09NTUlUU19RVUVSWSA9IGBxdWVyeSBsaXN0Q29tbWl0cygkb3duZXI6IFN0cmluZyEsICRyZXBvOiBTdHJpbmchLCAkYnJhbmNoOiBTdHJpbmchLCAkcGVyX3BhZ2U6IEludCA9IDUwLCAkYWZ0ZXI6IFN0cmluZywgJHNpbmNlOiBHaXRUaW1lc3RhbXAsICR1bnRpbDogR2l0VGltZXN0YW1wKSB7XG4gIHJlcG9zaXRvcnkob3duZXI6ICRvd25lciwgbmFtZTogJHJlcG8pIHtcbiAgICByZWYocXVhbGlmaWVkTmFtZTogJGJyYW5jaCkge1xuICAgICAgbmFtZVxuICAgICAgdGFyZ2V0IHtcbiAgICAgICAgLi4uIG9uIENvbW1pdCB7XG4gICAgICAgICAgaGlzdG9yeShmaXJzdDogJHBlcl9wYWdlLCBhZnRlcjogJGFmdGVyLCBzaW5jZTogJHNpbmNlLCB1bnRpbDogJHVudGlsKSB7XG4gICAgICAgICAgICBub2RlcyB7XG4gICAgICAgICAgICAgIGF1dGhvciB7XG4gICAgICAgICAgICAgICAgbmFtZVxuICAgICAgICAgICAgICAgIGVtYWlsXG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgYWRkaXRpb25zXG4gICAgICAgICAgICAgIGFzc29jaWF0ZWRQdWxsUmVxdWVzdHMoZmlyc3Q6IDEwKSB7XG4gICAgICAgICAgICAgICAgbm9kZXMge1xuICAgICAgICAgICAgICAgICAgdGl0bGVcbiAgICAgICAgICAgICAgICAgIHVybFxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICBtZXNzYWdlSGVhZGxpbmVcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHBhZ2VJbmZvIHtcbiAgICAgICAgICAgICAgZW5kQ3Vyc29yXG4gICAgICAgICAgICAgIGhhc05leHRQYWdlXG4gICAgICAgICAgICB9XG4gICAgICAgICAgICB0b3RhbENvdW50XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICB9XG59XG5gO1xuXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBSZXBvQ29tbWl0cyBleHRlbmRzIEJhc2VDb21tYW5kIHtcbiAgc3RhdGljIGRlc2NyaXB0aW9uID0gXCJFeHBvcnQgR2l0SHViIENvbW1pdHMgZm9yIGEgcmVwb3NpdG9yeVwiO1xuXG4gIHN0YXRpYyBmbGFncyA9IHtcbiAgICAuLi5CYXNlQ29tbWFuZC5mbGFncyxcbiAgICBicmFuY2g6IGZsYWdUeXBlcy5zdHJpbmcoe1xuICAgICAgZGVmYXVsdDogXCJtYXN0ZXJcIixcbiAgICAgIGRlc2NyaXB0aW9uOiBcImdpdCBicmFuY2ggdG8gZXhwb3J0IGNvbW1pdHMgZm9yXCIsXG4gICAgfSksXG4gICAgc2luY2U6IGZsYWdUeXBlcy5zdHJpbmcoe1xuICAgICAgZGVzY3JpcHRpb246IFwic2VhcmNoIGNvbW1pdHMgY3JlYXRlZCBhZnRlciB5eXl5LW1tLWRkXCIsXG4gICAgfSksXG4gICAgdW50aWw6IGZsYWdUeXBlcy5zdHJpbmcoe1xuICAgICAgZGVzY3JpcHRpb246IFwic2VhcmNoIGNvbW1pdHMgY3JlYXRlZCBiZWZvcmUgeXl5eS1tbS1kZFwiLFxuICAgIH0pLFxuICB9O1xuXG4gIGFzeW5jIHJ1bigpIHtcbiAgICBjb25zdCBjb21taXRzID0gW107XG5cbiAgICBjb25zdCB7IGZsYWdzIH0gPSB0aGlzLnBhcnNlKFJlcG9Db21taXRzKTtcbiAgICBjb25zdCB7IGJyYW5jaCwgb3duZXIsIHJlcG8sIGZvcm1hdCwgc2luY2UsIHVudGlsIH0gPSBmbGFncztcblxuICAgIGlmICghc2luY2UgfHwgIXVudGlsKSB7XG4gICAgICB0aGlzLndhcm4oXG4gICAgICAgIFwiRXhwb3J0aW5nIGNvbW1pdHMgY2FuIGJlIHNsb3cuIFBsZWFzZSBjb25zaWRlciBuYXJyb3dpbmcgeW91ciB0aW1lIHJhbmdlIHdpdGggYHNpbmNlYCBhbmQgYHVudGlsYFwiXG4gICAgICApO1xuICAgIH1cblxuICAgIGxldCBzdGFydDtcbiAgICBsZXQgZW5kO1xuXG4gICAgaWYgKHNpbmNlKSB7XG4gICAgICBzdGFydCA9IHRoaXMucGFyc2VEYXRlRmxhZyhcInNpbmNlXCIsIHNpbmNlKTtcbiAgICB9XG5cbiAgICBpZiAodW50aWwpIHtcbiAgICAgIGVuZCA9IHRoaXMucGFyc2VEYXRlRmxhZyhcImVuZFwiLCB1bnRpbCk7XG4gICAgfVxuXG4gICAgbGV0IHJlc3VsdHM6IFJlcG9zaXRvcnlDb21taXRzO1xuICAgIGxldCBjdXJzb3I7XG4gICAgbGV0IHByb2dyZXNzO1xuXG4gICAgLy8gcGFnaW5hdGUgdGhyb3VnaCB0aGUgR3JhcGhRTCBxdWVyeSB1bnRpbCB3ZSBnZXQgZXZlcnl0aGluZ1xuICAgIGRlYnVnKFwiUHVsbGluZyBjb21taXRzIGZyb20gQVBJXCIpO1xuICAgIGRvIHtcbiAgICAgIHJlc3VsdHMgPSBhd2FpdCB0aGlzLmdpdGh1Yi5ncmFwaHFsKExJU1RfQ09NTUlUU19RVUVSWSwge1xuICAgICAgICBicmFuY2gsXG4gICAgICAgIG93bmVyLFxuICAgICAgICByZXBvLFxuICAgICAgICBhZnRlcjogY3Vyc29yLFxuICAgICAgICBzaW5jZTogc3RhcnQsXG4gICAgICAgIHVudGlsOiBlbmQsXG4gICAgICB9KTtcbiAgICAgIGN1cnNvciA9IHJlc3VsdHMucmVwb3NpdG9yeS5yZWYudGFyZ2V0Lmhpc3RvcnkucGFnZUluZm8uZW5kQ3Vyc29yO1xuXG4gICAgICBpZiAoIXByb2dyZXNzKSB7XG4gICAgICAgIGlmIChyZXN1bHRzLnJlcG9zaXRvcnkucmVmLnRhcmdldC5oaXN0b3J5LnRvdGFsQ291bnQgPT09IDApIHtcbiAgICAgICAgICB0aGlzLndhcm4oXCJObyBjb21taXRzIGZvdW5kXCIpO1xuICAgICAgICAgIHByb2Nlc3MuZXhpdCgxKTtcbiAgICAgICAgfVxuXG4gICAgICAgIHByb2dyZXNzID0gbmV3IFByb2dyZXNzQmFyKFxuICAgICAgICAgIFwiZmV0Y2hpbmcgY29tbWl0cyBbOmJhcl0gOmN1cnJlbnQvOnRvdGFsIDpwZXJjZW50XCIsXG4gICAgICAgICAge1xuICAgICAgICAgICAgY29tcGxldGU6IFwiPVwiLFxuICAgICAgICAgICAgaW5jb21wbGV0ZTogXCIgXCIsXG4gICAgICAgICAgICB3aWR0aDogMjAsXG4gICAgICAgICAgICB0b3RhbDogcmVzdWx0cy5yZXBvc2l0b3J5LnJlZi50YXJnZXQuaGlzdG9yeS50b3RhbENvdW50LFxuICAgICAgICAgIH1cbiAgICAgICAgKTtcbiAgICAgIH1cblxuICAgICAgcHJvZ3Jlc3MudGljayhyZXN1bHRzLnJlcG9zaXRvcnkucmVmLnRhcmdldC5oaXN0b3J5Lm5vZGVzLmxlbmd0aCk7XG5cbiAgICAgIGZvciAoY29uc3QgY29tbWl0IG9mIHJlc3VsdHMucmVwb3NpdG9yeS5yZWYudGFyZ2V0Lmhpc3Rvcnkubm9kZXMpIHtcbiAgICAgICAgZG90Lm1vdmUoXG4gICAgICAgICAgXCJhc3NvY2lhdGVkUHVsbFJlcXVlc3RzLm5vZGVzXCIsXG4gICAgICAgICAgXCJhc3NvY2lhdGVkUHVsbFJlcXVlc3RzXCIsXG4gICAgICAgICAgY29tbWl0XG4gICAgICAgICk7XG4gICAgICB9XG4gICAgICBjb21taXRzLnB1c2goLi4ucmVzdWx0cy5yZXBvc2l0b3J5LnJlZi50YXJnZXQuaGlzdG9yeS5ub2Rlcyk7XG4gICAgfSB3aGlsZSAocmVzdWx0cy5yZXBvc2l0b3J5LnJlZi50YXJnZXQuaGlzdG9yeS5wYWdlSW5mby5oYXNOZXh0UGFnZSk7XG5cbiAgICBpZiAoZm9ybWF0ID09PSBcIkpTT05MXCIpIHtcbiAgICAgIGZvciAoY29uc3QgcmVsZWFzZSBvZiBjb21taXRzKSB7XG4gICAgICAgIHByb2Nlc3Muc3Rkb3V0LndyaXRlKGAke0pTT04uc3RyaW5naWZ5KHJlbGVhc2UpfVxcbmApO1xuICAgICAgfVxuICAgIH0gZWxzZSBpZiAoZm9ybWF0ID09PSBcIkpTT05cIikge1xuICAgICAgcHJvY2Vzcy5zdGRvdXQud3JpdGUoSlNPTi5zdHJpbmdpZnkoY29tbWl0cykpO1xuICAgIH0gZWxzZSBpZiAoZm9ybWF0ID09PSBcIkNTVlwiKSB7XG4gICAgICBjb25zdCBjc3YgPSBhd2FpdCBqc29uZXhwb3J0KGNvbW1pdHMsIHsgZmlsbEdhcHM6IHRydWUgfSk7XG4gICAgICBwcm9jZXNzLnN0ZG91dC53cml0ZShjc3YpO1xuICAgIH1cbiAgfVxufVxuIl19 -------------------------------------------------------------------------------- /packages/cli/lib/commands/repo/milestones.d.ts: -------------------------------------------------------------------------------- 1 | import BaseCommand from "../../base"; 2 | export default class RepoMilestones extends BaseCommand { 3 | static description: string; 4 | static flags: { 5 | baseUrl: import("@oclif/command/lib/flags").IOptionFlag; 6 | token: import("@oclif/command/lib/flags").IOptionFlag; 7 | owner: import("@oclif/command/lib/flags").IOptionFlag; 8 | repo: import("@oclif/command/lib/flags").IOptionFlag; 9 | format: import("@oclif/command/lib/flags").IOptionFlag; 10 | }; 11 | run(): Promise; 12 | } 13 | //# sourceMappingURL=milestones.d.ts.map -------------------------------------------------------------------------------- /packages/cli/lib/commands/repo/milestones.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"milestones.d.ts","sourceRoot":"","sources":["../../../src/commands/repo/milestones.ts"],"names":[],"mappings":"AAKA,OAAO,WAAW,MAAM,YAAY,CAAC;AAiCrC,MAAM,CAAC,OAAO,OAAO,cAAe,SAAQ,WAAW;IACrD,MAAM,CAAC,WAAW,SAA+C;IAEjE,MAAM,CAAC,KAAK;;;;;;MAEV;IAEI,GAAG;CAoDV"} -------------------------------------------------------------------------------- /packages/cli/lib/commands/repo/milestones.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* globals process */ 3 | Object.defineProperty(exports, "__esModule", { value: true }); 4 | const debug_1 = require("debug"); 5 | const ProgressBar = require("progress"); 6 | const jsonexport = require("jsonexport"); 7 | const base_1 = require("../../base"); 8 | const debug = debug_1.default("exporter:repo:milestones"); 9 | const LIST_RELEASES_QUERY = `query listMilestones($owner: String!, $repo: String!, $per_page: Int = 100, $after: String) { 10 | repository(owner: $owner, name: $repo) { 11 | milestones(first: $per_page, after: $after) { 12 | nodes { 13 | closed 14 | closedAt 15 | createdAt 16 | creator { 17 | login 18 | } 19 | description 20 | dueOn 21 | id 22 | number 23 | state 24 | title 25 | updatedAt 26 | } 27 | pageInfo { 28 | endCursor 29 | hasNextPage 30 | } 31 | totalCount 32 | } 33 | } 34 | } 35 | `; 36 | class RepoMilestones extends base_1.default { 37 | async run() { 38 | const milestones = []; 39 | const { flags } = this.parse(RepoMilestones); 40 | const { owner, repo, format } = flags; 41 | let results; 42 | let cursor; 43 | let progress; 44 | // paginate through the GraphQL query until we get everything 45 | debug("Pulling milestones from API"); 46 | do { 47 | results = await this.github.graphql(LIST_RELEASES_QUERY, { 48 | owner, 49 | repo, 50 | after: cursor, 51 | }); 52 | cursor = results.repository.milestones.pageInfo.endCursor; 53 | if (!progress) { 54 | if (results.repository.milestones.totalCount === 0) { 55 | this.warn("No milestones found"); 56 | process.exit(1); 57 | } 58 | progress = new ProgressBar("fetching milestones [:bar] :current/:total :percent", { 59 | complete: "=", 60 | incomplete: " ", 61 | width: 20, 62 | total: results.repository.milestones.totalCount, 63 | }); 64 | } 65 | progress.tick(results.repository.milestones.nodes.length); 66 | milestones.push(...results.repository.milestones.nodes); 67 | } while (results.repository.milestones.pageInfo.hasNextPage); 68 | if (format === "JSONL") { 69 | for (const release of milestones) { 70 | process.stdout.write(`${JSON.stringify(release)}\n`); 71 | } 72 | } 73 | else if (format === "JSON") { 74 | process.stdout.write(JSON.stringify(milestones)); 75 | } 76 | else if (format === "CSV") { 77 | const csv = await jsonexport(milestones, { fillGaps: true }); 78 | process.stdout.write(csv); 79 | } 80 | } 81 | } 82 | exports.default = RepoMilestones; 83 | RepoMilestones.description = "Export GitHub Milestones for a repository"; 84 | RepoMilestones.flags = Object.assign({}, base_1.default.flags); 85 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWlsZXN0b25lcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9jb21tYW5kcy9yZXBvL21pbGVzdG9uZXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBLHFCQUFxQjs7QUFFckIsaUNBQW1DO0FBQ25DLHdDQUF3QztBQUN4Qyx5Q0FBeUM7QUFDekMscUNBQXFDO0FBR3JDLE1BQU0sS0FBSyxHQUFHLGVBQWMsQ0FBQywwQkFBMEIsQ0FBQyxDQUFDO0FBRXpELE1BQU0sbUJBQW1CLEdBQUc7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0NBMEIzQixDQUFDO0FBRUYsTUFBcUIsY0FBZSxTQUFRLGNBQVc7SUFPckQsS0FBSyxDQUFDLEdBQUc7UUFDUCxNQUFNLFVBQVUsR0FBRyxFQUFFLENBQUM7UUFFdEIsTUFBTSxFQUFFLEtBQUssRUFBRSxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsY0FBYyxDQUFDLENBQUM7UUFDN0MsTUFBTSxFQUFFLEtBQUssRUFBRSxJQUFJLEVBQUUsTUFBTSxFQUFFLEdBQUcsS0FBSyxDQUFDO1FBRXRDLElBQUksT0FBNkIsQ0FBQztRQUNsQyxJQUFJLE1BQU0sQ0FBQztRQUNYLElBQUksUUFBUSxDQUFDO1FBRWIsNkRBQTZEO1FBQzdELEtBQUssQ0FBQyw2QkFBNkIsQ0FBQyxDQUFDO1FBQ3JDLEdBQUc7WUFDRCxPQUFPLEdBQUcsTUFBTSxJQUFJLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxtQkFBbUIsRUFBRTtnQkFDdkQsS0FBSztnQkFDTCxJQUFJO2dCQUNKLEtBQUssRUFBRSxNQUFNO2FBQ2QsQ0FBQyxDQUFDO1lBQ0gsTUFBTSxHQUFHLE9BQU8sQ0FBQyxVQUFVLENBQUMsVUFBVSxDQUFDLFFBQVEsQ0FBQyxTQUFTLENBQUM7WUFFMUQsSUFBSSxDQUFDLFFBQVEsRUFBRTtnQkFDYixJQUFJLE9BQU8sQ0FBQyxVQUFVLENBQUMsVUFBVSxDQUFDLFVBQVUsS0FBSyxDQUFDLEVBQUU7b0JBQ2xELElBQUksQ0FBQyxJQUFJLENBQUMscUJBQXFCLENBQUMsQ0FBQztvQkFDakMsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztpQkFDakI7Z0JBRUQsUUFBUSxHQUFHLElBQUksV0FBVyxDQUN4QixxREFBcUQsRUFDckQ7b0JBQ0UsUUFBUSxFQUFFLEdBQUc7b0JBQ2IsVUFBVSxFQUFFLEdBQUc7b0JBQ2YsS0FBSyxFQUFFLEVBQUU7b0JBQ1QsS0FBSyxFQUFFLE9BQU8sQ0FBQyxVQUFVLENBQUMsVUFBVSxDQUFDLFVBQVU7aUJBQ2hELENBQ0YsQ0FBQzthQUNIO1lBRUQsUUFBUSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsVUFBVSxDQUFDLFVBQVUsQ0FBQyxLQUFLLENBQUMsTUFBTSxDQUFDLENBQUM7WUFDMUQsVUFBVSxDQUFDLElBQUksQ0FBQyxHQUFHLE9BQU8sQ0FBQyxVQUFVLENBQUMsVUFBVSxDQUFDLEtBQUssQ0FBQyxDQUFDO1NBQ3pELFFBQVEsT0FBTyxDQUFDLFVBQVUsQ0FBQyxVQUFVLENBQUMsUUFBUSxDQUFDLFdBQVcsRUFBRTtRQUU3RCxJQUFJLE1BQU0sS0FBSyxPQUFPLEVBQUU7WUFDdEIsS0FBSyxNQUFNLE9BQU8sSUFBSSxVQUFVLEVBQUU7Z0JBQ2hDLE9BQU8sQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLEdBQUcsSUFBSSxDQUFDLFNBQVMsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUM7YUFDdEQ7U0FDRjthQUFNLElBQUksTUFBTSxLQUFLLE1BQU0sRUFBRTtZQUM1QixPQUFPLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLFVBQVUsQ0FBQyxDQUFDLENBQUM7U0FDbEQ7YUFBTSxJQUFJLE1BQU0sS0FBSyxLQUFLLEVBQUU7WUFDM0IsTUFBTSxHQUFHLEdBQUcsTUFBTSxVQUFVLENBQUMsVUFBVSxFQUFFLEVBQUUsUUFBUSxFQUFFLElBQUksRUFBRSxDQUFDLENBQUM7WUFDN0QsT0FBTyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUM7U0FDM0I7SUFDSCxDQUFDOztBQTFESCxpQ0EyREM7QUExRFEsMEJBQVcsR0FBRywyQ0FBMkMsQ0FBQztBQUUxRCxvQkFBSyxxQkFDUCxjQUFXLENBQUMsS0FBSyxFQUNwQiIsInNvdXJjZXNDb250ZW50IjpbIi8qIGdsb2JhbHMgcHJvY2VzcyAqL1xuXG5pbXBvcnQgY3JlYXRlRGVidWdnZXIgZnJvbSBcImRlYnVnXCI7XG5pbXBvcnQgKiBhcyBQcm9ncmVzc0JhciBmcm9tIFwicHJvZ3Jlc3NcIjtcbmltcG9ydCAqIGFzIGpzb25leHBvcnQgZnJvbSBcImpzb25leHBvcnRcIjtcbmltcG9ydCBCYXNlQ29tbWFuZCBmcm9tIFwiLi4vLi4vYmFzZVwiO1xuaW1wb3J0IHsgUmVwb3NpdG9yeU1pbGVzdG9uZXMgfSBmcm9tIFwiLi4vLi4vZ2l0aHViXCI7XG5cbmNvbnN0IGRlYnVnID0gY3JlYXRlRGVidWdnZXIoXCJleHBvcnRlcjpyZXBvOm1pbGVzdG9uZXNcIik7XG5cbmNvbnN0IExJU1RfUkVMRUFTRVNfUVVFUlkgPSBgcXVlcnkgbGlzdE1pbGVzdG9uZXMoJG93bmVyOiBTdHJpbmchLCAkcmVwbzogU3RyaW5nISwgJHBlcl9wYWdlOiBJbnQgPSAxMDAsICRhZnRlcjogU3RyaW5nKSB7XG4gIHJlcG9zaXRvcnkob3duZXI6ICRvd25lciwgbmFtZTogJHJlcG8pIHtcbiAgICBtaWxlc3RvbmVzKGZpcnN0OiAkcGVyX3BhZ2UsIGFmdGVyOiAkYWZ0ZXIpIHtcbiAgICAgIG5vZGVzIHtcbiAgICAgICAgY2xvc2VkXG4gICAgICAgIGNsb3NlZEF0XG4gICAgICAgIGNyZWF0ZWRBdFxuICAgICAgICBjcmVhdG9yIHtcbiAgICAgICAgICBsb2dpblxuICAgICAgICB9XG4gICAgICAgIGRlc2NyaXB0aW9uXG4gICAgICAgIGR1ZU9uXG4gICAgICAgIGlkXG4gICAgICAgIG51bWJlclxuICAgICAgICBzdGF0ZVxuICAgICAgICB0aXRsZVxuICAgICAgICB1cGRhdGVkQXRcbiAgICAgIH1cbiAgICAgIHBhZ2VJbmZvIHtcbiAgICAgICAgZW5kQ3Vyc29yXG4gICAgICAgIGhhc05leHRQYWdlXG4gICAgICB9XG4gICAgICB0b3RhbENvdW50XG4gICAgfVxuICB9XG59XG5gO1xuXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBSZXBvTWlsZXN0b25lcyBleHRlbmRzIEJhc2VDb21tYW5kIHtcbiAgc3RhdGljIGRlc2NyaXB0aW9uID0gXCJFeHBvcnQgR2l0SHViIE1pbGVzdG9uZXMgZm9yIGEgcmVwb3NpdG9yeVwiO1xuXG4gIHN0YXRpYyBmbGFncyA9IHtcbiAgICAuLi5CYXNlQ29tbWFuZC5mbGFncyxcbiAgfTtcblxuICBhc3luYyBydW4oKSB7XG4gICAgY29uc3QgbWlsZXN0b25lcyA9IFtdO1xuXG4gICAgY29uc3QgeyBmbGFncyB9ID0gdGhpcy5wYXJzZShSZXBvTWlsZXN0b25lcyk7XG4gICAgY29uc3QgeyBvd25lciwgcmVwbywgZm9ybWF0IH0gPSBmbGFncztcblxuICAgIGxldCByZXN1bHRzOiBSZXBvc2l0b3J5TWlsZXN0b25lcztcbiAgICBsZXQgY3Vyc29yO1xuICAgIGxldCBwcm9ncmVzcztcblxuICAgIC8vIHBhZ2luYXRlIHRocm91Z2ggdGhlIEdyYXBoUUwgcXVlcnkgdW50aWwgd2UgZ2V0IGV2ZXJ5dGhpbmdcbiAgICBkZWJ1ZyhcIlB1bGxpbmcgbWlsZXN0b25lcyBmcm9tIEFQSVwiKTtcbiAgICBkbyB7XG4gICAgICByZXN1bHRzID0gYXdhaXQgdGhpcy5naXRodWIuZ3JhcGhxbChMSVNUX1JFTEVBU0VTX1FVRVJZLCB7XG4gICAgICAgIG93bmVyLFxuICAgICAgICByZXBvLFxuICAgICAgICBhZnRlcjogY3Vyc29yLFxuICAgICAgfSk7XG4gICAgICBjdXJzb3IgPSByZXN1bHRzLnJlcG9zaXRvcnkubWlsZXN0b25lcy5wYWdlSW5mby5lbmRDdXJzb3I7XG5cbiAgICAgIGlmICghcHJvZ3Jlc3MpIHtcbiAgICAgICAgaWYgKHJlc3VsdHMucmVwb3NpdG9yeS5taWxlc3RvbmVzLnRvdGFsQ291bnQgPT09IDApIHtcbiAgICAgICAgICB0aGlzLndhcm4oXCJObyBtaWxlc3RvbmVzIGZvdW5kXCIpO1xuICAgICAgICAgIHByb2Nlc3MuZXhpdCgxKTtcbiAgICAgICAgfVxuXG4gICAgICAgIHByb2dyZXNzID0gbmV3IFByb2dyZXNzQmFyKFxuICAgICAgICAgIFwiZmV0Y2hpbmcgbWlsZXN0b25lcyBbOmJhcl0gOmN1cnJlbnQvOnRvdGFsIDpwZXJjZW50XCIsXG4gICAgICAgICAge1xuICAgICAgICAgICAgY29tcGxldGU6IFwiPVwiLFxuICAgICAgICAgICAgaW5jb21wbGV0ZTogXCIgXCIsXG4gICAgICAgICAgICB3aWR0aDogMjAsXG4gICAgICAgICAgICB0b3RhbDogcmVzdWx0cy5yZXBvc2l0b3J5Lm1pbGVzdG9uZXMudG90YWxDb3VudCxcbiAgICAgICAgICB9XG4gICAgICAgICk7XG4gICAgICB9XG5cbiAgICAgIHByb2dyZXNzLnRpY2socmVzdWx0cy5yZXBvc2l0b3J5Lm1pbGVzdG9uZXMubm9kZXMubGVuZ3RoKTtcbiAgICAgIG1pbGVzdG9uZXMucHVzaCguLi5yZXN1bHRzLnJlcG9zaXRvcnkubWlsZXN0b25lcy5ub2Rlcyk7XG4gICAgfSB3aGlsZSAocmVzdWx0cy5yZXBvc2l0b3J5Lm1pbGVzdG9uZXMucGFnZUluZm8uaGFzTmV4dFBhZ2UpO1xuXG4gICAgaWYgKGZvcm1hdCA9PT0gXCJKU09OTFwiKSB7XG4gICAgICBmb3IgKGNvbnN0IHJlbGVhc2Ugb2YgbWlsZXN0b25lcykge1xuICAgICAgICBwcm9jZXNzLnN0ZG91dC53cml0ZShgJHtKU09OLnN0cmluZ2lmeShyZWxlYXNlKX1cXG5gKTtcbiAgICAgIH1cbiAgICB9IGVsc2UgaWYgKGZvcm1hdCA9PT0gXCJKU09OXCIpIHtcbiAgICAgIHByb2Nlc3Muc3Rkb3V0LndyaXRlKEpTT04uc3RyaW5naWZ5KG1pbGVzdG9uZXMpKTtcbiAgICB9IGVsc2UgaWYgKGZvcm1hdCA9PT0gXCJDU1ZcIikge1xuICAgICAgY29uc3QgY3N2ID0gYXdhaXQganNvbmV4cG9ydChtaWxlc3RvbmVzLCB7IGZpbGxHYXBzOiB0cnVlIH0pO1xuICAgICAgcHJvY2Vzcy5zdGRvdXQud3JpdGUoY3N2KTtcbiAgICB9XG4gIH1cbn1cbiJdfQ== -------------------------------------------------------------------------------- /packages/cli/lib/commands/repo/projects.d.ts: -------------------------------------------------------------------------------- 1 | import { flags as flagTypes } from "@oclif/command"; 2 | import BaseCommand from "../../base"; 3 | export default class RepoProjects extends BaseCommand { 4 | static description: string; 5 | static flags: { 6 | projectNumber: import("@oclif/parser/lib/flags").IOptionFlag; 7 | baseUrl: flagTypes.IOptionFlag; 8 | token: flagTypes.IOptionFlag; 9 | owner: flagTypes.IOptionFlag; 10 | repo: flagTypes.IOptionFlag; 11 | format: flagTypes.IOptionFlag; 12 | }; 13 | run(): Promise; 14 | } 15 | //# sourceMappingURL=projects.d.ts.map -------------------------------------------------------------------------------- /packages/cli/lib/commands/repo/projects.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"projects.d.ts","sourceRoot":"","sources":["../../../src/commands/repo/projects.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAKpD,OAAO,WAAW,MAAM,YAAY,CAAC;AAwCrC,MAAM,CAAC,OAAO,OAAO,YAAa,SAAQ,WAAW;IACnD,MAAM,CAAC,WAAW,SAA+C;IAEjE,MAAM,CAAC,KAAK;;;;;;;MAKV;IAEI,GAAG;CAwGV"} -------------------------------------------------------------------------------- /packages/cli/lib/commands/repo/pulls.d.ts: -------------------------------------------------------------------------------- 1 | import { flags as flagTypes } from "@oclif/command"; 2 | import BaseCommand from "../../base"; 3 | import { PullRequest } from "../../github"; 4 | export default class RepoPulls extends BaseCommand { 5 | static description: string; 6 | static flags: { 7 | owner: flagTypes.IOptionFlag; 8 | repo: flagTypes.IOptionFlag; 9 | since: flagTypes.IOptionFlag; 10 | until: flagTypes.IOptionFlag; 11 | baseUrl: flagTypes.IOptionFlag; 12 | token: flagTypes.IOptionFlag; 13 | format: flagTypes.IOptionFlag; 14 | }; 15 | fetchPulls(query: string, owner: string, repo: string): Promise; 16 | fetchComments(query: string, pull: PullRequest, progress: any): Promise; 17 | run(): Promise; 18 | } 19 | //# sourceMappingURL=pulls.d.ts.map -------------------------------------------------------------------------------- /packages/cli/lib/commands/repo/pulls.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"pulls.d.ts","sourceRoot":"","sources":["../../../src/commands/repo/pulls.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAKpD,OAAO,WAAW,MAAM,YAAY,CAAC;AACrC,OAAO,EAGL,WAAW,EACZ,MAAM,cAAc,CAAC;AAqFtB,MAAM,CAAC,OAAO,OAAO,SAAU,SAAQ,WAAW;IAChD,MAAM,CAAC,WAAW,SAAkD;IAEpE,MAAM,CAAC,KAAK;;;;;;;;MAkBV;IAEI,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;IA0CrD,aAAa,CACjB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,WAAW,EAEjB,QAAQ,EAAE,GAAG,GACZ,OAAO,CAAC,IAAI,CAAC;IAiBV,GAAG;CAqEV"} -------------------------------------------------------------------------------- /packages/cli/lib/commands/repo/releases.d.ts: -------------------------------------------------------------------------------- 1 | import BaseCommand from "../../base"; 2 | export default class RepoReleases extends BaseCommand { 3 | static description: string; 4 | static flags: { 5 | baseUrl: import("@oclif/command/lib/flags").IOptionFlag; 6 | token: import("@oclif/command/lib/flags").IOptionFlag; 7 | owner: import("@oclif/command/lib/flags").IOptionFlag; 8 | repo: import("@oclif/command/lib/flags").IOptionFlag; 9 | format: import("@oclif/command/lib/flags").IOptionFlag; 10 | }; 11 | run(): Promise; 12 | } 13 | //# sourceMappingURL=releases.d.ts.map -------------------------------------------------------------------------------- /packages/cli/lib/commands/repo/releases.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"releases.d.ts","sourceRoot":"","sources":["../../../src/commands/repo/releases.ts"],"names":[],"mappings":"AAKA,OAAO,WAAW,MAAM,YAAY,CAAC;AAiCrC,MAAM,CAAC,OAAO,OAAO,YAAa,SAAQ,WAAW;IACnD,MAAM,CAAC,WAAW,SAA6C;IAE/D,MAAM,CAAC,KAAK;;;;;;MAEV;IAEI,GAAG;CAoDV"} -------------------------------------------------------------------------------- /packages/cli/lib/commands/repo/releases.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* globals process */ 3 | Object.defineProperty(exports, "__esModule", { value: true }); 4 | const debug_1 = require("debug"); 5 | const ProgressBar = require("progress"); 6 | const jsonexport = require("jsonexport"); 7 | const base_1 = require("../../base"); 8 | const debug = debug_1.default("exporter:repo:releases"); 9 | const LIST_RELEASES_QUERY = `query listReleases($owner: String!, $repo: String!, $per_page: Int = 100, $after: String) { 10 | repository(owner: $owner, name: $repo) { 11 | releases(first: $per_page, after: $after) { 12 | nodes { 13 | author { 14 | login 15 | } 16 | createdAt 17 | description 18 | id 19 | isDraft 20 | isPrerelease 21 | name 22 | publishedAt 23 | shortDescriptionHTML 24 | tagName 25 | updatedAt 26 | } 27 | pageInfo { 28 | endCursor 29 | hasNextPage 30 | } 31 | totalCount 32 | } 33 | } 34 | } 35 | `; 36 | class RepoReleases extends base_1.default { 37 | async run() { 38 | const releases = []; 39 | const { flags } = this.parse(RepoReleases); 40 | const { owner, repo, format } = flags; 41 | let results; 42 | let cursor; 43 | let progress; 44 | // paginate through the GraphQL query until we get everything 45 | debug("Pulling releases from API"); 46 | do { 47 | results = await this.github.graphql(LIST_RELEASES_QUERY, { 48 | owner, 49 | repo, 50 | after: cursor, 51 | }); 52 | cursor = results.repository.releases.pageInfo.endCursor; 53 | if (!progress) { 54 | if (results.repository.releases.totalCount === 0) { 55 | this.warn("No releases found"); 56 | process.exit(1); 57 | } 58 | progress = new ProgressBar("fetching releases [:bar] :current/:total :percent", { 59 | complete: "=", 60 | incomplete: " ", 61 | width: 20, 62 | total: results.repository.releases.totalCount, 63 | }); 64 | } 65 | progress.tick(results.repository.releases.nodes.length); 66 | releases.push(...results.repository.releases.nodes); 67 | } while (results.repository.releases.pageInfo.hasNextPage); 68 | if (format === "JSONL") { 69 | for (const release of releases) { 70 | process.stdout.write(`${JSON.stringify(release)}\n`); 71 | } 72 | } 73 | else if (format === "JSON") { 74 | process.stdout.write(JSON.stringify(releases)); 75 | } 76 | else if (format === "CSV") { 77 | const csv = await jsonexport(releases, { fillGaps: true }); 78 | process.stdout.write(csv); 79 | } 80 | } 81 | } 82 | exports.default = RepoReleases; 83 | RepoReleases.description = "Export GitHub Releases for a repository"; 84 | RepoReleases.flags = Object.assign({}, base_1.default.flags); 85 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVsZWFzZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvY29tbWFuZHMvcmVwby9yZWxlYXNlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUEscUJBQXFCOztBQUVyQixpQ0FBbUM7QUFDbkMsd0NBQXdDO0FBQ3hDLHlDQUF5QztBQUN6QyxxQ0FBcUM7QUFHckMsTUFBTSxLQUFLLEdBQUcsZUFBYyxDQUFDLHdCQUF3QixDQUFDLENBQUM7QUFFdkQsTUFBTSxtQkFBbUIsR0FBRzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Q0EwQjNCLENBQUM7QUFFRixNQUFxQixZQUFhLFNBQVEsY0FBVztJQU9uRCxLQUFLLENBQUMsR0FBRztRQUNQLE1BQU0sUUFBUSxHQUFHLEVBQUUsQ0FBQztRQUVwQixNQUFNLEVBQUUsS0FBSyxFQUFFLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxZQUFZLENBQUMsQ0FBQztRQUMzQyxNQUFNLEVBQUUsS0FBSyxFQUFFLElBQUksRUFBRSxNQUFNLEVBQUUsR0FBRyxLQUFLLENBQUM7UUFFdEMsSUFBSSxPQUEyQixDQUFDO1FBQ2hDLElBQUksTUFBTSxDQUFDO1FBQ1gsSUFBSSxRQUFRLENBQUM7UUFFYiw2REFBNkQ7UUFDN0QsS0FBSyxDQUFDLDJCQUEyQixDQUFDLENBQUM7UUFDbkMsR0FBRztZQUNELE9BQU8sR0FBRyxNQUFNLElBQUksQ0FBQyxNQUFNLENBQUMsT0FBTyxDQUFDLG1CQUFtQixFQUFFO2dCQUN2RCxLQUFLO2dCQUNMLElBQUk7Z0JBQ0osS0FBSyxFQUFFLE1BQU07YUFDZCxDQUFDLENBQUM7WUFDSCxNQUFNLEdBQUcsT0FBTyxDQUFDLFVBQVUsQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDLFNBQVMsQ0FBQztZQUV4RCxJQUFJLENBQUMsUUFBUSxFQUFFO2dCQUNiLElBQUksT0FBTyxDQUFDLFVBQVUsQ0FBQyxRQUFRLENBQUMsVUFBVSxLQUFLLENBQUMsRUFBRTtvQkFDaEQsSUFBSSxDQUFDLElBQUksQ0FBQyxtQkFBbUIsQ0FBQyxDQUFDO29CQUMvQixPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDO2lCQUNqQjtnQkFFRCxRQUFRLEdBQUcsSUFBSSxXQUFXLENBQ3hCLG1EQUFtRCxFQUNuRDtvQkFDRSxRQUFRLEVBQUUsR0FBRztvQkFDYixVQUFVLEVBQUUsR0FBRztvQkFDZixLQUFLLEVBQUUsRUFBRTtvQkFDVCxLQUFLLEVBQUUsT0FBTyxDQUFDLFVBQVUsQ0FBQyxRQUFRLENBQUMsVUFBVTtpQkFDOUMsQ0FDRixDQUFDO2FBQ0g7WUFFRCxRQUFRLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxVQUFVLENBQUMsUUFBUSxDQUFDLEtBQUssQ0FBQyxNQUFNLENBQUMsQ0FBQztZQUN4RCxRQUFRLENBQUMsSUFBSSxDQUFDLEdBQUcsT0FBTyxDQUFDLFVBQVUsQ0FBQyxRQUFRLENBQUMsS0FBSyxDQUFDLENBQUM7U0FDckQsUUFBUSxPQUFPLENBQUMsVUFBVSxDQUFDLFFBQVEsQ0FBQyxRQUFRLENBQUMsV0FBVyxFQUFFO1FBRTNELElBQUksTUFBTSxLQUFLLE9BQU8sRUFBRTtZQUN0QixLQUFLLE1BQU0sT0FBTyxJQUFJLFFBQVEsRUFBRTtnQkFDOUIsT0FBTyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQzthQUN0RDtTQUNGO2FBQU0sSUFBSSxNQUFNLEtBQUssTUFBTSxFQUFFO1lBQzVCLE9BQU8sQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQztTQUNoRDthQUFNLElBQUksTUFBTSxLQUFLLEtBQUssRUFBRTtZQUMzQixNQUFNLEdBQUcsR0FBRyxNQUFNLFVBQVUsQ0FBQyxRQUFRLEVBQUUsRUFBRSxRQUFRLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQztZQUMzRCxPQUFPLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQztTQUMzQjtJQUNILENBQUM7O0FBMURILCtCQTJEQztBQTFEUSx3QkFBVyxHQUFHLHlDQUF5QyxDQUFDO0FBRXhELGtCQUFLLHFCQUNQLGNBQVcsQ0FBQyxLQUFLLEVBQ3BCIiwic291cmNlc0NvbnRlbnQiOlsiLyogZ2xvYmFscyBwcm9jZXNzICovXG5cbmltcG9ydCBjcmVhdGVEZWJ1Z2dlciBmcm9tIFwiZGVidWdcIjtcbmltcG9ydCAqIGFzIFByb2dyZXNzQmFyIGZyb20gXCJwcm9ncmVzc1wiO1xuaW1wb3J0ICogYXMganNvbmV4cG9ydCBmcm9tIFwianNvbmV4cG9ydFwiO1xuaW1wb3J0IEJhc2VDb21tYW5kIGZyb20gXCIuLi8uLi9iYXNlXCI7XG5pbXBvcnQgeyBSZXBvc2l0b3J5UmVsZWFzZXMgfSBmcm9tIFwiLi4vLi4vZ2l0aHViXCI7XG5cbmNvbnN0IGRlYnVnID0gY3JlYXRlRGVidWdnZXIoXCJleHBvcnRlcjpyZXBvOnJlbGVhc2VzXCIpO1xuXG5jb25zdCBMSVNUX1JFTEVBU0VTX1FVRVJZID0gYHF1ZXJ5IGxpc3RSZWxlYXNlcygkb3duZXI6IFN0cmluZyEsICRyZXBvOiBTdHJpbmchLCAkcGVyX3BhZ2U6IEludCA9IDEwMCwgJGFmdGVyOiBTdHJpbmcpIHtcbiAgcmVwb3NpdG9yeShvd25lcjogJG93bmVyLCBuYW1lOiAkcmVwbykge1xuICAgIHJlbGVhc2VzKGZpcnN0OiAkcGVyX3BhZ2UsIGFmdGVyOiAkYWZ0ZXIpIHtcbiAgICAgIG5vZGVzIHtcbiAgICAgICAgYXV0aG9yIHtcbiAgICAgICAgICBsb2dpblxuICAgICAgICB9XG4gICAgICAgIGNyZWF0ZWRBdFxuICAgICAgICBkZXNjcmlwdGlvblxuICAgICAgICBpZFxuICAgICAgICBpc0RyYWZ0XG4gICAgICAgIGlzUHJlcmVsZWFzZVxuICAgICAgICBuYW1lXG4gICAgICAgIHB1Ymxpc2hlZEF0XG4gICAgICAgIHNob3J0RGVzY3JpcHRpb25IVE1MXG4gICAgICAgIHRhZ05hbWVcbiAgICAgICAgdXBkYXRlZEF0XG4gICAgICB9XG4gICAgICBwYWdlSW5mbyB7XG4gICAgICAgIGVuZEN1cnNvclxuICAgICAgICBoYXNOZXh0UGFnZVxuICAgICAgfVxuICAgICAgdG90YWxDb3VudFxuICAgIH1cbiAgfVxufVxuYDtcblxuZXhwb3J0IGRlZmF1bHQgY2xhc3MgUmVwb1JlbGVhc2VzIGV4dGVuZHMgQmFzZUNvbW1hbmQge1xuICBzdGF0aWMgZGVzY3JpcHRpb24gPSBcIkV4cG9ydCBHaXRIdWIgUmVsZWFzZXMgZm9yIGEgcmVwb3NpdG9yeVwiO1xuXG4gIHN0YXRpYyBmbGFncyA9IHtcbiAgICAuLi5CYXNlQ29tbWFuZC5mbGFncyxcbiAgfTtcblxuICBhc3luYyBydW4oKSB7XG4gICAgY29uc3QgcmVsZWFzZXMgPSBbXTtcblxuICAgIGNvbnN0IHsgZmxhZ3MgfSA9IHRoaXMucGFyc2UoUmVwb1JlbGVhc2VzKTtcbiAgICBjb25zdCB7IG93bmVyLCByZXBvLCBmb3JtYXQgfSA9IGZsYWdzO1xuXG4gICAgbGV0IHJlc3VsdHM6IFJlcG9zaXRvcnlSZWxlYXNlcztcbiAgICBsZXQgY3Vyc29yO1xuICAgIGxldCBwcm9ncmVzcztcblxuICAgIC8vIHBhZ2luYXRlIHRocm91Z2ggdGhlIEdyYXBoUUwgcXVlcnkgdW50aWwgd2UgZ2V0IGV2ZXJ5dGhpbmdcbiAgICBkZWJ1ZyhcIlB1bGxpbmcgcmVsZWFzZXMgZnJvbSBBUElcIik7XG4gICAgZG8ge1xuICAgICAgcmVzdWx0cyA9IGF3YWl0IHRoaXMuZ2l0aHViLmdyYXBocWwoTElTVF9SRUxFQVNFU19RVUVSWSwge1xuICAgICAgICBvd25lcixcbiAgICAgICAgcmVwbyxcbiAgICAgICAgYWZ0ZXI6IGN1cnNvcixcbiAgICAgIH0pO1xuICAgICAgY3Vyc29yID0gcmVzdWx0cy5yZXBvc2l0b3J5LnJlbGVhc2VzLnBhZ2VJbmZvLmVuZEN1cnNvcjtcblxuICAgICAgaWYgKCFwcm9ncmVzcykge1xuICAgICAgICBpZiAocmVzdWx0cy5yZXBvc2l0b3J5LnJlbGVhc2VzLnRvdGFsQ291bnQgPT09IDApIHtcbiAgICAgICAgICB0aGlzLndhcm4oXCJObyByZWxlYXNlcyBmb3VuZFwiKTtcbiAgICAgICAgICBwcm9jZXNzLmV4aXQoMSk7XG4gICAgICAgIH1cblxuICAgICAgICBwcm9ncmVzcyA9IG5ldyBQcm9ncmVzc0JhcihcbiAgICAgICAgICBcImZldGNoaW5nIHJlbGVhc2VzIFs6YmFyXSA6Y3VycmVudC86dG90YWwgOnBlcmNlbnRcIixcbiAgICAgICAgICB7XG4gICAgICAgICAgICBjb21wbGV0ZTogXCI9XCIsXG4gICAgICAgICAgICBpbmNvbXBsZXRlOiBcIiBcIixcbiAgICAgICAgICAgIHdpZHRoOiAyMCxcbiAgICAgICAgICAgIHRvdGFsOiByZXN1bHRzLnJlcG9zaXRvcnkucmVsZWFzZXMudG90YWxDb3VudCxcbiAgICAgICAgICB9XG4gICAgICAgICk7XG4gICAgICB9XG5cbiAgICAgIHByb2dyZXNzLnRpY2socmVzdWx0cy5yZXBvc2l0b3J5LnJlbGVhc2VzLm5vZGVzLmxlbmd0aCk7XG4gICAgICByZWxlYXNlcy5wdXNoKC4uLnJlc3VsdHMucmVwb3NpdG9yeS5yZWxlYXNlcy5ub2Rlcyk7XG4gICAgfSB3aGlsZSAocmVzdWx0cy5yZXBvc2l0b3J5LnJlbGVhc2VzLnBhZ2VJbmZvLmhhc05leHRQYWdlKTtcblxuICAgIGlmIChmb3JtYXQgPT09IFwiSlNPTkxcIikge1xuICAgICAgZm9yIChjb25zdCByZWxlYXNlIG9mIHJlbGVhc2VzKSB7XG4gICAgICAgIHByb2Nlc3Muc3Rkb3V0LndyaXRlKGAke0pTT04uc3RyaW5naWZ5KHJlbGVhc2UpfVxcbmApO1xuICAgICAgfVxuICAgIH0gZWxzZSBpZiAoZm9ybWF0ID09PSBcIkpTT05cIikge1xuICAgICAgcHJvY2Vzcy5zdGRvdXQud3JpdGUoSlNPTi5zdHJpbmdpZnkocmVsZWFzZXMpKTtcbiAgICB9IGVsc2UgaWYgKGZvcm1hdCA9PT0gXCJDU1ZcIikge1xuICAgICAgY29uc3QgY3N2ID0gYXdhaXQganNvbmV4cG9ydChyZWxlYXNlcywgeyBmaWxsR2FwczogdHJ1ZSB9KTtcbiAgICAgIHByb2Nlc3Muc3Rkb3V0LndyaXRlKGNzdik7XG4gICAgfVxuICB9XG59XG4iXX0= -------------------------------------------------------------------------------- /packages/cli/lib/commands/search.d.ts: -------------------------------------------------------------------------------- 1 | import BaseCommand from "../base"; 2 | export default abstract class Search extends BaseCommand { 3 | static description: string; 4 | static flags: { 5 | baseUrl: import("@oclif/command/lib/flags").IOptionFlag; 6 | token: import("@oclif/command/lib/flags").IOptionFlag; 7 | owner: import("@oclif/command/lib/flags").IOptionFlag; 8 | repo: import("@oclif/command/lib/flags").IOptionFlag; 9 | format: import("@oclif/command/lib/flags").IOptionFlag; 10 | }; 11 | run(): Promise; 12 | } 13 | //# sourceMappingURL=search.d.ts.map -------------------------------------------------------------------------------- /packages/cli/lib/commands/search.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/commands/search.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,MAAM,SAAS,CAAC;AAElC,MAAM,CAAC,OAAO,CAAC,QAAQ,OAAO,MAAO,SAAQ,WAAW;IACtD,MAAM,CAAC,WAAW,SAAgC;IAElD,MAAM,CAAC,KAAK;;;;;;MAEV;IAEI,GAAG;CAGV"} -------------------------------------------------------------------------------- /packages/cli/lib/commands/search.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const base_1 = require("../base"); 4 | class Search extends base_1.default { 5 | async run() { 6 | this._help(); 7 | } 8 | } 9 | exports.default = Search; 10 | Search.description = "GitHub Search base command"; 11 | Search.flags = Object.assign({}, base_1.default.flags); 12 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VhcmNoLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2NvbW1hbmRzL3NlYXJjaC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFBLGtDQUFrQztBQUVsQyxNQUE4QixNQUFPLFNBQVEsY0FBVztJQU90RCxLQUFLLENBQUMsR0FBRztRQUNQLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQztJQUNmLENBQUM7O0FBVEgseUJBVUM7QUFUUSxrQkFBVyxHQUFHLDRCQUE0QixDQUFDO0FBRTNDLFlBQUsscUJBQ1AsY0FBVyxDQUFDLEtBQUssRUFDcEIiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgQmFzZUNvbW1hbmQgZnJvbSBcIi4uL2Jhc2VcIjtcblxuZXhwb3J0IGRlZmF1bHQgYWJzdHJhY3QgY2xhc3MgU2VhcmNoIGV4dGVuZHMgQmFzZUNvbW1hbmQge1xuICBzdGF0aWMgZGVzY3JpcHRpb24gPSBcIkdpdEh1YiBTZWFyY2ggYmFzZSBjb21tYW5kXCI7XG5cbiAgc3RhdGljIGZsYWdzID0ge1xuICAgIC4uLkJhc2VDb21tYW5kLmZsYWdzLFxuICB9O1xuXG4gIGFzeW5jIHJ1bigpIHtcbiAgICB0aGlzLl9oZWxwKCk7XG4gIH1cbn1cbiJdfQ== -------------------------------------------------------------------------------- /packages/cli/lib/commands/search/issues.d.ts: -------------------------------------------------------------------------------- 1 | import { flags as flagTypes } from "@oclif/command"; 2 | import SearchCommand from "../search"; 3 | import { Issue } from "@github/github-artifact-exporter-core"; 4 | export default class SearchIssues extends SearchCommand { 5 | static description: string; 6 | static flags: { 7 | since: flagTypes.IOptionFlag; 8 | until: flagTypes.IOptionFlag; 9 | updatedSince: flagTypes.IOptionFlag; 10 | updatedUntil: flagTypes.IOptionFlag; 11 | state: flagTypes.IOptionFlag; 12 | labels: flagTypes.IOptionFlag; 13 | jira: import("@oclif/parser/lib/flags").IBooleanFlag; 14 | query: flagTypes.IOptionFlag; 15 | dateFormat: flagTypes.IOptionFlag; 16 | baseUrl: flagTypes.IOptionFlag; 17 | token: flagTypes.IOptionFlag; 18 | owner: flagTypes.IOptionFlag; 19 | repo: flagTypes.IOptionFlag; 20 | format: flagTypes.IOptionFlag; 21 | }; 22 | /** 23 | * Transform an Issue object's comments from an array to number properties 24 | * and format them in a way that Jira understands. 25 | * 26 | * Example: 27 | * 28 | * Issue { 29 | * ... 30 | * comments: [{ 31 | * author: { 32 | * login: "mona" 33 | * }, 34 | * createdAt: "1970-01-01T00:00:00", 35 | * body: "I think this is great" 36 | * }, { 37 | * author: { 38 | * login: "mona" 39 | * }, 40 | * createdAt: "1970-01-02T00:00:00", 41 | * body: "I think this is great as well" 42 | * }] 43 | * } 44 | * 45 | * is transformed to 46 | * 47 | * Issue { 48 | * comment0: "1970-01-01T00:00:00;mona;I think this is great" 49 | * comment1: "1970-01-02T00:00:00;mona;I think this is great as well" 50 | * } 51 | * 52 | * @param issue 53 | */ 54 | jiraFormatComments(issue: Issue): void; 55 | run(): Promise; 56 | } 57 | //# sourceMappingURL=issues.d.ts.map -------------------------------------------------------------------------------- /packages/cli/lib/commands/search/issues.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"issues.d.ts","sourceRoot":"","sources":["../../../src/commands/search/issues.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAKpD,OAAO,aAAa,MAAM,WAAW,CAAC;AAEtC,OAAO,EACL,KAAK,EAON,MAAM,uCAAuC,CAAC;AAM/C,MAAM,CAAC,OAAO,OAAO,YAAa,SAAQ,aAAa;IACrD,MAAM,CAAC,WAAW,SAAuC;IAEzD,MAAM,CAAC,KAAK;;;;;;;;;;;;;;;MAkCV;IAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+BG;IACH,kBAAkB,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAchC,GAAG;CAmKV"} -------------------------------------------------------------------------------- /packages/cli/lib/index.d.ts: -------------------------------------------------------------------------------- 1 | export { run } from "@oclif/command"; 2 | //# sourceMappingURL=index.d.ts.map -------------------------------------------------------------------------------- /packages/cli/lib/index.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC"} -------------------------------------------------------------------------------- /packages/cli/lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var command_1 = require("@oclif/command"); 4 | Object.defineProperty(exports, "run", { enumerable: true, get: function () { return command_1.run; } }); 5 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFBQSwwQ0FBcUM7QUFBNUIsOEZBQUEsR0FBRyxPQUFBIiwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0IHsgcnVuIH0gZnJvbSBcIkBvY2xpZi9jb21tYW5kXCI7XG4iXX0= -------------------------------------------------------------------------------- /packages/cli/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@github/github-artifact-exporter-cli", 3 | "description": "Exporter for GitHub", 4 | "version": "2.0.2", 5 | "author": "Jason Macgowan", 6 | "bin": { 7 | "exporter": "./bin/run" 8 | }, 9 | "bugs": "https://github.com/github/github-artifact-exporter/issues", 10 | "dependencies": { 11 | "@github/github-artifact-exporter-core": "^2.0.2", 12 | "@oclif/command": "^1.8.0", 13 | "@oclif/config": "^1.17.0", 14 | "@oclif/plugin-help": "^3.2.0", 15 | "@octokit/rest": "^18.0.7", 16 | "@types/dateformat": "^3.0.1", 17 | "@types/debug": "^4.1.5", 18 | "@types/dot-object": "^2.1.2", 19 | "@types/luxon": "^1.25.0", 20 | "@types/progress": "^2.0.3", 21 | "dateformat": "^3.0.3", 22 | "debug": "^4.2.0", 23 | "dot-object": "^2.1.4", 24 | "jsonexport": "^3.0.1", 25 | "luxon": "^1.25.0", 26 | "progress": "^2.0.3", 27 | "tslib": "^1.14.1" 28 | }, 29 | "devDependencies": { 30 | "@oclif/dev-cli": "^1.22.2", 31 | "@oclif/test": "^1.2.7", 32 | "@types/chai": "^4.2.14", 33 | "@types/mocha": "^5.2.7", 34 | "@types/node": "^10.17.44", 35 | "chai": "^4.2.0", 36 | "globby": "^10.0.2", 37 | "mocha": "^5.2.0", 38 | "nock": "^12.0.3", 39 | "nyc": "^14.1.1", 40 | "pkg": "^4.4.9", 41 | "rimraf": "^3.0.2", 42 | "ts-node": "^8.10.2", 43 | "typescript": "^3.9.7" 44 | }, 45 | "engines": { 46 | "node": ">=8.0.0" 47 | }, 48 | "files": [ 49 | "/bin", 50 | "/lib", 51 | "/npm-shrinkwrap.json", 52 | "/oclif.manifest.json" 53 | ], 54 | "homepage": "https://github.com/github/github-artifact-exporter", 55 | "keywords": [ 56 | "oclif" 57 | ], 58 | "license": "MIT", 59 | "main": "lib/index.js", 60 | "oclif": { 61 | "commands": "./lib/commands", 62 | "bin": "github-artifacts-exporter", 63 | "plugins": [ 64 | "@oclif/plugin-help" 65 | ] 66 | }, 67 | "repository": { 68 | "type": "git", 69 | "url": "git+https://github.com/github/github-artifact-exporter.git", 70 | "directory": "packages/cli" 71 | }, 72 | "scripts": { 73 | "pkg:linux": "npm run prepack && pkg package.json --target node12-linux-x64 --out-path ./dist && npm run postpack", 74 | "pkg:macos": "npm run prepack && pkg package.json --target node12-macos-x64 --out-path ./dist && npm run postpack", 75 | "pkg:windows": "npm run prepack && pkg package.json --target node12-win-x64 --out-path ./dist && npm run postpack", 76 | "postpack": "rm -f oclif.manifest.json", 77 | "prepack": "rimraf lib && tsc -b && oclif-dev manifest && oclif-dev readme", 78 | "test": "nyc --extension .ts mocha --forbid-only \"test/**/*.test.ts\"", 79 | "version": "oclif-dev readme && git add README.md", 80 | "build": "npm run prepack" 81 | }, 82 | "types": "lib/index.d.ts", 83 | "pkg": { 84 | "scripts": "./lib/**/*.js", 85 | "assets": [ 86 | "ca/*.pem", 87 | "package.json" 88 | ] 89 | }, 90 | "gitHead": "bebf4402de364eb82675f1ea2bf90085804881ee" 91 | } 92 | -------------------------------------------------------------------------------- /packages/cli/src/base.ts: -------------------------------------------------------------------------------- 1 | import Command, { flags as flagTypes } from "@oclif/command"; 2 | import { IConfig } from "@oclif/config"; 3 | import { Octokit } from "@octokit/rest"; 4 | import { DateTime } from "luxon"; 5 | 6 | export default abstract class Base extends Command { 7 | constructor(argv: string[], config: IConfig) { 8 | super(argv, config); 9 | } 10 | // this is immediately overwritten in the init method 11 | github: Octokit = new Octokit(); 12 | 13 | static flags = { 14 | baseUrl: flagTypes.string({ 15 | description: "GitHub base url", 16 | default: "https://api.github.com", 17 | }), 18 | 19 | token: flagTypes.string({ 20 | description: "GitHub personal access token", 21 | env: "GITHUB_TOKEN", 22 | required: true, 23 | }), 24 | 25 | owner: flagTypes.string({ 26 | dependsOn: ["repo"], 27 | description: "GitHub repository owner", 28 | }), 29 | 30 | repo: flagTypes.string({ 31 | dependsOn: ["owner"], 32 | description: "GitHub repository name", 33 | }), 34 | 35 | format: flagTypes.enum({ 36 | options: ["JSONL", "JSON", "CSV"], 37 | default: "JSONL", 38 | description: "export format", 39 | }), 40 | }; 41 | 42 | /** 43 | * Parse date into ISO or "*" if null/undefined this 44 | * allows it to be used with the `created` filter 45 | * for GitHub Search 46 | * 47 | * @param {string} flagName 48 | * @param {string} date 49 | * @returns {string} 50 | */ 51 | parseDateFlag(flagName: string, date: string | undefined): string { 52 | let searchDate = "*"; 53 | 54 | if (date) { 55 | const datetime = DateTime.fromFormat(date, "yyyy-MM-dd"); 56 | 57 | if (!datetime.isValid) { 58 | throw new Error( 59 | `unable to parse flag "${flagName}"\n${datetime.invalidExplanation}` 60 | ); 61 | } 62 | 63 | searchDate = datetime.toISODate(); 64 | } 65 | 66 | return searchDate; 67 | } 68 | 69 | async init() { 70 | const { 71 | flags: { baseUrl, token }, 72 | /* 73 | * These next lines are required for parsing flags on commands 74 | * that extend from this base class since there is a type mismatch 75 | * between this.parse and this.constructor. 76 | * 77 | * See `init` in https://oclif.io/docs/base_class showing that is the 78 | * right way even though we have to disable our linter 79 | */ 80 | // eslint-disable-next-line @typescript-eslint/ban-ts-ignore 81 | // @ts-ignore 82 | } = this.parse(this.constructor); 83 | 84 | this.github = new Octokit({ 85 | baseUrl, 86 | auth: token, 87 | }); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /packages/cli/src/commands/repo.ts: -------------------------------------------------------------------------------- 1 | import BaseCommand from "../base"; 2 | 3 | export default class Repo extends BaseCommand { 4 | static description = "Export GitHub artifacts from a repository"; 5 | 6 | async run() { 7 | this._help(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/cli/src/commands/repo/commits.ts: -------------------------------------------------------------------------------- 1 | /* globals process */ 2 | 3 | import { flags as flagTypes } from "@oclif/command"; 4 | import createDebugger from "debug"; 5 | import * as dot from "dot-object"; 6 | import * as ProgressBar from "progress"; 7 | import * as jsonexport from "jsonexport"; 8 | import BaseCommand from "../../base"; 9 | import { RepositoryCommits } from "../../github"; 10 | 11 | const debug = createDebugger("exporter:repo:commits"); 12 | 13 | const LIST_COMMITS_QUERY = `query listCommits($owner: String!, $repo: String!, $branch: String!, $per_page: Int = 50, $after: String, $since: GitTimestamp, $until: GitTimestamp) { 14 | repository(owner: $owner, name: $repo) { 15 | ref(qualifiedName: $branch) { 16 | name 17 | target { 18 | ... on Commit { 19 | history(first: $per_page, after: $after, since: $since, until: $until) { 20 | nodes { 21 | author { 22 | name 23 | email 24 | } 25 | additions 26 | associatedPullRequests(first: 10) { 27 | nodes { 28 | title 29 | url 30 | } 31 | } 32 | messageHeadline 33 | } 34 | pageInfo { 35 | endCursor 36 | hasNextPage 37 | } 38 | totalCount 39 | } 40 | } 41 | } 42 | } 43 | } 44 | } 45 | `; 46 | 47 | export default class RepoCommits extends BaseCommand { 48 | static description = "Export GitHub Commits for a repository"; 49 | 50 | static flags = { 51 | ...BaseCommand.flags, 52 | branch: flagTypes.string({ 53 | default: "master", 54 | description: "git branch to export commits for", 55 | }), 56 | since: flagTypes.string({ 57 | description: "search commits created after yyyy-mm-dd", 58 | }), 59 | until: flagTypes.string({ 60 | description: "search commits created before yyyy-mm-dd", 61 | }), 62 | }; 63 | 64 | async run() { 65 | const commits = []; 66 | 67 | const { flags } = this.parse(RepoCommits); 68 | const { branch, owner, repo, format, since, until } = flags; 69 | 70 | if (!since || !until) { 71 | this.warn( 72 | "Exporting commits can be slow. Please consider narrowing your time range with `since` and `until`" 73 | ); 74 | } 75 | 76 | let start; 77 | let end; 78 | 79 | if (since) { 80 | start = this.parseDateFlag("since", since); 81 | } 82 | 83 | if (until) { 84 | end = this.parseDateFlag("end", until); 85 | } 86 | 87 | let results: RepositoryCommits; 88 | let cursor; 89 | let progress; 90 | 91 | // paginate through the GraphQL query until we get everything 92 | debug("Pulling commits from API"); 93 | do { 94 | results = await this.github.graphql(LIST_COMMITS_QUERY, { 95 | branch, 96 | owner, 97 | repo, 98 | after: cursor, 99 | since: start, 100 | until: end, 101 | }); 102 | cursor = results.repository.ref.target.history.pageInfo.endCursor; 103 | 104 | if (!progress) { 105 | if (results.repository.ref.target.history.totalCount === 0) { 106 | this.warn("No commits found"); 107 | process.exit(1); 108 | } 109 | 110 | progress = new ProgressBar( 111 | "fetching commits [:bar] :current/:total :percent", 112 | { 113 | complete: "=", 114 | incomplete: " ", 115 | width: 20, 116 | total: results.repository.ref.target.history.totalCount, 117 | } 118 | ); 119 | } 120 | 121 | progress.tick(results.repository.ref.target.history.nodes.length); 122 | 123 | for (const commit of results.repository.ref.target.history.nodes) { 124 | dot.move( 125 | "associatedPullRequests.nodes", 126 | "associatedPullRequests", 127 | commit 128 | ); 129 | } 130 | commits.push(...results.repository.ref.target.history.nodes); 131 | } while (results.repository.ref.target.history.pageInfo.hasNextPage); 132 | 133 | if (format === "JSONL") { 134 | for (const release of commits) { 135 | process.stdout.write(`${JSON.stringify(release)}\n`); 136 | } 137 | } else if (format === "JSON") { 138 | process.stdout.write(JSON.stringify(commits)); 139 | } else if (format === "CSV") { 140 | const csv = await jsonexport(commits, { fillGaps: true }); 141 | process.stdout.write(csv); 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /packages/cli/src/commands/repo/milestones.ts: -------------------------------------------------------------------------------- 1 | /* globals process */ 2 | 3 | import createDebugger from "debug"; 4 | import * as ProgressBar from "progress"; 5 | import * as jsonexport from "jsonexport"; 6 | import BaseCommand from "../../base"; 7 | import { RepositoryMilestones } from "../../github"; 8 | 9 | const debug = createDebugger("exporter:repo:milestones"); 10 | 11 | const LIST_RELEASES_QUERY = `query listMilestones($owner: String!, $repo: String!, $per_page: Int = 100, $after: String) { 12 | repository(owner: $owner, name: $repo) { 13 | milestones(first: $per_page, after: $after) { 14 | nodes { 15 | closed 16 | closedAt 17 | createdAt 18 | creator { 19 | login 20 | } 21 | description 22 | dueOn 23 | id 24 | number 25 | state 26 | title 27 | updatedAt 28 | } 29 | pageInfo { 30 | endCursor 31 | hasNextPage 32 | } 33 | totalCount 34 | } 35 | } 36 | } 37 | `; 38 | 39 | export default class RepoMilestones extends BaseCommand { 40 | static description = "Export GitHub Milestones for a repository"; 41 | 42 | static flags = { 43 | ...BaseCommand.flags, 44 | }; 45 | 46 | async run() { 47 | const milestones = []; 48 | 49 | const { flags } = this.parse(RepoMilestones); 50 | const { owner, repo, format } = flags; 51 | 52 | let results: RepositoryMilestones; 53 | let cursor; 54 | let progress; 55 | 56 | // paginate through the GraphQL query until we get everything 57 | debug("Pulling milestones from API"); 58 | do { 59 | results = await this.github.graphql(LIST_RELEASES_QUERY, { 60 | owner, 61 | repo, 62 | after: cursor, 63 | }); 64 | cursor = results.repository.milestones.pageInfo.endCursor; 65 | 66 | if (!progress) { 67 | if (results.repository.milestones.totalCount === 0) { 68 | this.warn("No milestones found"); 69 | process.exit(1); 70 | } 71 | 72 | progress = new ProgressBar( 73 | "fetching milestones [:bar] :current/:total :percent", 74 | { 75 | complete: "=", 76 | incomplete: " ", 77 | width: 20, 78 | total: results.repository.milestones.totalCount, 79 | } 80 | ); 81 | } 82 | 83 | progress.tick(results.repository.milestones.nodes.length); 84 | milestones.push(...results.repository.milestones.nodes); 85 | } while (results.repository.milestones.pageInfo.hasNextPage); 86 | 87 | if (format === "JSONL") { 88 | for (const release of milestones) { 89 | process.stdout.write(`${JSON.stringify(release)}\n`); 90 | } 91 | } else if (format === "JSON") { 92 | process.stdout.write(JSON.stringify(milestones)); 93 | } else if (format === "CSV") { 94 | const csv = await jsonexport(milestones, { fillGaps: true }); 95 | process.stdout.write(csv); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /packages/cli/src/commands/repo/projects.ts: -------------------------------------------------------------------------------- 1 | /* globals process */ 2 | 3 | import { flags as flagTypes } from "@oclif/command"; 4 | import createDebugger from "debug"; 5 | import * as ProgressBar from "progress"; 6 | import * as jsonexport from "jsonexport"; 7 | import * as dot from "dot-object"; 8 | import BaseCommand from "../../base"; 9 | import { RepositoryProject } from "../../github"; 10 | 11 | const debug = createDebugger("exporter:repo:milestones"); 12 | 13 | const LIST_PROJECTS_QUERY = `query listMilestones($owner: String!, $repo: String!, $project: Int!, $per_page: Int = 100, $columnCursor: String, $cardCursor: String) { 14 | repository(owner: $owner, name: $repo) { 15 | project(number: $project) { 16 | columns(first: $per_page, after: $columnCursor) { 17 | nodes { 18 | name 19 | cards(first: $per_page, after: $cardCursor) { 20 | nodes { 21 | note 22 | content { 23 | ... on Issue { 24 | id 25 | number 26 | title 27 | } 28 | } 29 | } 30 | pageInfo { 31 | hasNextPage 32 | endCursor 33 | } 34 | totalCount 35 | } 36 | } 37 | pageInfo { 38 | hasNextPage 39 | endCursor 40 | } 41 | totalCount 42 | } 43 | } 44 | } 45 | } 46 | `; 47 | 48 | export default class RepoProjects extends BaseCommand { 49 | static description = "Export GitHub Milestones for a repository"; 50 | 51 | static flags = { 52 | ...BaseCommand.flags, 53 | projectNumber: flagTypes.integer({ 54 | description: "Project number from where to pull cards", 55 | }), 56 | }; 57 | 58 | async run() { 59 | const projects = []; 60 | 61 | const { flags } = this.parse(RepoProjects); 62 | const { owner, repo, format } = flags; 63 | 64 | let results: RepositoryProject; 65 | let currentCursor; 66 | let columnCursor; 67 | let cardCursor; 68 | let progress; 69 | 70 | // paginate through the GraphQL query until we get everything 71 | debug("Pulling projects from API"); 72 | do { 73 | results = await this.github.graphql(LIST_PROJECTS_QUERY, { 74 | owner, 75 | repo, 76 | project: flags.projectNumber, 77 | columnCursor, 78 | cardCursor, 79 | }); 80 | currentCursor = columnCursor; 81 | columnCursor = results.repository.project.columns.pageInfo.endCursor; 82 | 83 | if (!progress) { 84 | if (results.repository.project.columns.totalCount === 0) { 85 | this.warn("No projects found"); 86 | process.exit(1); 87 | } 88 | 89 | progress = new ProgressBar( 90 | "fetching projects [:bar] :current/:total :percent", 91 | { 92 | complete: "=", 93 | incomplete: " ", 94 | width: 20, 95 | total: results.repository.project.columns.totalCount, 96 | } 97 | ); 98 | } 99 | 100 | progress.tick(results.repository.project.columns.nodes.length); 101 | projects.push(...results.repository.project.columns.nodes); 102 | 103 | // Loop through cards 104 | for (const column of results.repository.project.columns.nodes) { 105 | cardCursor = column.cards.pageInfo.endCursor; 106 | let hasNextPage = column.cards.pageInfo.hasNextPage; 107 | while (hasNextPage) { 108 | results = await this.github.graphql(LIST_PROJECTS_QUERY, { 109 | owner, 110 | repo, 111 | project: flags.projectNumber, 112 | columnCursor: currentCursor, 113 | cardCursor, 114 | }); 115 | 116 | projects 117 | .filter((col) => { 118 | return col.name === column.name; 119 | })[0] 120 | .cards.nodes.push( 121 | ...results.repository.project.columns.nodes.filter((col) => { 122 | return col.name === column.name; 123 | })[0].cards.nodes 124 | ); 125 | cardCursor = results.repository.project.columns.nodes.filter( 126 | (col) => { 127 | return col.name === column.name; 128 | } 129 | )[0].cards.pageInfo.endCursor; 130 | hasNextPage = results.repository.project.columns.nodes.filter( 131 | (col) => { 132 | return col.name === column.name; 133 | } 134 | )[0].cards.pageInfo.hasNextPage; 135 | } 136 | } 137 | } while (results.repository.project.columns.pageInfo.hasNextPage); 138 | 139 | // Fix formatting 140 | // massage the data to remove GraphQL pagination data 141 | for (const column of projects) { 142 | //dot.move("name", "column_name", column); 143 | dot.del("cards.pageInfo", column); 144 | dot.del("cards.totalCount", column); 145 | for (const card of column.cards.nodes) { 146 | dot.del("content.id", card); 147 | dot.del("content.number", card); 148 | } 149 | } 150 | 151 | if (format === "JSONL") { 152 | for (const release of projects) { 153 | process.stdout.write(`${JSON.stringify(release)}\n`); 154 | } 155 | } else if (format === "JSON") { 156 | process.stdout.write(JSON.stringify(projects)); 157 | } else if (format === "CSV") { 158 | const csv = await jsonexport(projects, { fillGaps: true }); 159 | process.stdout.write(csv); 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /packages/cli/src/commands/repo/pulls.ts: -------------------------------------------------------------------------------- 1 | /* globals process */ 2 | 3 | import { flags as flagTypes } from "@oclif/command"; 4 | import * as dot from "dot-object"; 5 | import createDebugger from "debug"; 6 | import * as ProgressBar from "progress"; 7 | import * as jsonexport from "jsonexport"; 8 | import BaseCommand from "../../base"; 9 | import { 10 | CommentsList, 11 | RepositoryPullRequests, 12 | PullRequest, 13 | } from "../../github"; 14 | 15 | const debug = createDebugger("exporter:repo:pulls"); 16 | 17 | const LIST_PULLS_QUERY = `query listPulls($owner: String!, $repo: String!, $per_page: Int = 10, $after: String) { 18 | repository(owner: $owner, name: $repo) { 19 | pullRequests(first: $per_page, after: $after) { 20 | nodes { 21 | reviewDecision 22 | merged 23 | mergedAt 24 | mergedBy { 25 | login 26 | } 27 | assignees(first: 10) { 28 | nodes { 29 | login 30 | } 31 | } 32 | author { 33 | login 34 | } 35 | comments(first: 10) { 36 | nodes { 37 | author { 38 | login 39 | } 40 | body 41 | createdAt 42 | } 43 | pageInfo { 44 | endCursor 45 | hasNextPage 46 | } 47 | totalCount 48 | } 49 | createdAt 50 | id 51 | labels(first: 100) { 52 | nodes { 53 | name 54 | } 55 | } 56 | state 57 | title 58 | updatedAt 59 | milestone { 60 | title 61 | state 62 | url 63 | } 64 | closedAt 65 | } 66 | pageInfo { 67 | endCursor 68 | hasNextPage 69 | } 70 | totalCount 71 | } 72 | } 73 | } 74 | `; 75 | 76 | const LIST_COMMENTS_QUERY = `query listComments($id: ID!, $per_page: Int = 100, $after: String) { 77 | node(id: $id) { 78 | ... on PullRequest { 79 | comments(first: $per_page, after: $after) { 80 | nodes { 81 | author { 82 | login 83 | } 84 | body 85 | createdAt 86 | } 87 | pageInfo { 88 | endCursor 89 | hasNextPage 90 | } 91 | totalCount 92 | } 93 | } 94 | } 95 | } 96 | `; 97 | 98 | export default class RepoPulls extends BaseCommand { 99 | static description = "Export GitHub Pull Requests for a repository"; 100 | 101 | static flags = { 102 | ...BaseCommand.flags, 103 | owner: flagTypes.string({ 104 | dependsOn: ["repo"], 105 | description: "GitHub repository owner", 106 | required: true, 107 | }), 108 | repo: flagTypes.string({ 109 | dependsOn: ["owner"], 110 | description: "GitHub repository name", 111 | required: true, 112 | }), 113 | since: flagTypes.string({ 114 | description: "search pull requests created after yyyy-mm-dd", 115 | }), 116 | until: flagTypes.string({ 117 | description: "search pull requests created before yyyy-mm-dd", 118 | }), 119 | }; 120 | 121 | async fetchPulls(query: string, owner: string, repo: string) { 122 | const pulls = []; 123 | 124 | let results: RepositoryPullRequests; 125 | let cursor; 126 | let progress; 127 | 128 | // paginate through the GraphQL Search query until we get everything 129 | debug("Pulling pull requests from API"); 130 | do { 131 | results = await this.github.graphql(query, { 132 | owner, 133 | repo, 134 | after: cursor, 135 | }); 136 | cursor = results.repository.pullRequests.pageInfo.endCursor; 137 | 138 | if (!progress) { 139 | if (results.repository.pullRequests.totalCount === 0) { 140 | this.warn("No pull requests found"); 141 | process.exit(1); 142 | } 143 | 144 | progress = new ProgressBar( 145 | "fetching pulls [:bar] :current/:total :percent", 146 | { 147 | complete: "=", 148 | incomplete: " ", 149 | width: 20, 150 | total: results.repository.pullRequests.totalCount, 151 | } 152 | ); 153 | } 154 | 155 | progress.tick(results.repository.pullRequests.nodes.length); 156 | 157 | pulls.push(...results.repository.pullRequests.nodes); 158 | } while (results.repository.pullRequests.pageInfo.hasNextPage); 159 | 160 | return pulls; 161 | } 162 | 163 | async fetchComments( 164 | query: string, 165 | pull: PullRequest, 166 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 167 | progress: any 168 | ): Promise { 169 | let results: CommentsList = { 170 | node: { 171 | comments: pull.comments, 172 | }, 173 | }; 174 | 175 | while (results.node.comments.pageInfo.hasNextPage) { 176 | results = await this.github.graphql(query, { 177 | id: pull.id, 178 | after: results.node.comments.pageInfo.endCursor, 179 | }); 180 | pull.comments.nodes.push(...results.node.comments.nodes); 181 | progress.tick(results.node.comments.nodes.length); 182 | } 183 | } 184 | 185 | async run() { 186 | const { flags } = this.parse(RepoPulls); 187 | const { owner, repo, format, since, until } = flags; 188 | 189 | let pulls = await this.fetchPulls(LIST_PULLS_QUERY, owner, repo); 190 | 191 | // filter out pulls before since or after until 192 | if (since) { 193 | pulls = pulls.filter((pull) => { 194 | return +new Date(pull.createdAt) >= +new Date(since); 195 | }); 196 | } 197 | if (until) { 198 | pulls = pulls.filter((pull) => { 199 | return +new Date(until) >= +new Date(pull.createdAt); 200 | }); 201 | } 202 | 203 | const pullsWithComments = pulls.filter( 204 | (issue) => issue.comments.totalCount - issue.comments.nodes.length > 0 205 | ); 206 | 207 | if (pullsWithComments.length > 0) { 208 | const remainingComments = pullsWithComments 209 | .map((issue) => { 210 | return issue.comments.totalCount - issue.comments.nodes.length; 211 | }) 212 | .reduce((x, y) => x + y); 213 | 214 | const progress = new ProgressBar( 215 | "fetching comments [:bar] :current/:total :percent", 216 | { 217 | complete: "=", 218 | incomplete: " ", 219 | width: 20, 220 | total: remainingComments, 221 | } 222 | ); 223 | 224 | const promises = pullsWithComments.map((issue) => 225 | this.fetchComments(LIST_COMMENTS_QUERY, issue, progress) 226 | ); 227 | 228 | await Promise.all(promises); 229 | } 230 | 231 | // massage the data to remove GraphQL pagination data 232 | for (const pull of pulls) { 233 | dot.del("id", pull); 234 | dot.move("assignees.nodes", "assignees", pull); 235 | dot.move("comments.nodes", "comments", pull); 236 | dot.move("labels.nodes", "labels", pull); 237 | 238 | // eslint-disable-next-line @typescript-eslint/ban-ts-ignore 239 | // @ts-ignore 240 | pull.labels = pull.labels.map(({ name }) => name).join(", "); 241 | } 242 | 243 | if (format === "JSONL") { 244 | for (const pull of pulls) { 245 | process.stdout.write(`${JSON.stringify(pull)}\n`); 246 | } 247 | } else if (format === "JSON") { 248 | process.stdout.write(JSON.stringify(pulls)); 249 | } else if (format === "CSV") { 250 | const csv = await jsonexport(pulls, { fillGaps: true }); 251 | process.stdout.write(csv); 252 | } 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /packages/cli/src/commands/repo/releases.ts: -------------------------------------------------------------------------------- 1 | /* globals process */ 2 | 3 | import createDebugger from "debug"; 4 | import * as ProgressBar from "progress"; 5 | import * as jsonexport from "jsonexport"; 6 | import BaseCommand from "../../base"; 7 | import { RepositoryReleases } from "../../github"; 8 | 9 | const debug = createDebugger("exporter:repo:releases"); 10 | 11 | const LIST_RELEASES_QUERY = `query listReleases($owner: String!, $repo: String!, $per_page: Int = 100, $after: String) { 12 | repository(owner: $owner, name: $repo) { 13 | releases(first: $per_page, after: $after) { 14 | nodes { 15 | author { 16 | login 17 | } 18 | createdAt 19 | description 20 | id 21 | isDraft 22 | isPrerelease 23 | name 24 | publishedAt 25 | shortDescriptionHTML 26 | tagName 27 | updatedAt 28 | } 29 | pageInfo { 30 | endCursor 31 | hasNextPage 32 | } 33 | totalCount 34 | } 35 | } 36 | } 37 | `; 38 | 39 | export default class RepoReleases extends BaseCommand { 40 | static description = "Export GitHub Releases for a repository"; 41 | 42 | static flags = { 43 | ...BaseCommand.flags, 44 | }; 45 | 46 | async run() { 47 | const releases = []; 48 | 49 | const { flags } = this.parse(RepoReleases); 50 | const { owner, repo, format } = flags; 51 | 52 | let results: RepositoryReleases; 53 | let cursor; 54 | let progress; 55 | 56 | // paginate through the GraphQL query until we get everything 57 | debug("Pulling releases from API"); 58 | do { 59 | results = await this.github.graphql(LIST_RELEASES_QUERY, { 60 | owner, 61 | repo, 62 | after: cursor, 63 | }); 64 | cursor = results.repository.releases.pageInfo.endCursor; 65 | 66 | if (!progress) { 67 | if (results.repository.releases.totalCount === 0) { 68 | this.warn("No releases found"); 69 | process.exit(1); 70 | } 71 | 72 | progress = new ProgressBar( 73 | "fetching releases [:bar] :current/:total :percent", 74 | { 75 | complete: "=", 76 | incomplete: " ", 77 | width: 20, 78 | total: results.repository.releases.totalCount, 79 | } 80 | ); 81 | } 82 | 83 | progress.tick(results.repository.releases.nodes.length); 84 | releases.push(...results.repository.releases.nodes); 85 | } while (results.repository.releases.pageInfo.hasNextPage); 86 | 87 | if (format === "JSONL") { 88 | for (const release of releases) { 89 | process.stdout.write(`${JSON.stringify(release)}\n`); 90 | } 91 | } else if (format === "JSON") { 92 | process.stdout.write(JSON.stringify(releases)); 93 | } else if (format === "CSV") { 94 | const csv = await jsonexport(releases, { fillGaps: true }); 95 | process.stdout.write(csv); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /packages/cli/src/commands/search.ts: -------------------------------------------------------------------------------- 1 | import BaseCommand from "../base"; 2 | 3 | export default abstract class Search extends BaseCommand { 4 | static description = "GitHub Search base command"; 5 | 6 | static flags = { 7 | ...BaseCommand.flags, 8 | }; 9 | 10 | async run() { 11 | this._help(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/cli/src/commands/search/issues.ts: -------------------------------------------------------------------------------- 1 | /* globals process */ 2 | 3 | import { flags as flagTypes } from "@oclif/command"; 4 | import * as dot from "dot-object"; 5 | import createDebugger from "debug"; 6 | import * as ProgressBar from "progress"; 7 | import * as jsonexport from "jsonexport"; 8 | import SearchCommand from "../search"; 9 | 10 | import { 11 | Issue, 12 | IssueComment, 13 | getIssues, 14 | getComments, 15 | User, 16 | getIssuesWithComments, 17 | iterateObject, 18 | } from "@github/github-artifact-exporter-core"; 19 | 20 | import dateformat = require("dateformat"); 21 | 22 | const debug = createDebugger("exporter:search:issues"); 23 | 24 | export default class SearchIssues extends SearchCommand { 25 | static description = "Export GitHub Issues using Search"; 26 | 27 | static flags = { 28 | ...SearchCommand.flags, 29 | since: flagTypes.string({ 30 | description: "search issues created after yyyy-mm-dd", 31 | }), 32 | until: flagTypes.string({ 33 | description: "search issues created before yyyy-mm-dd", 34 | }), 35 | updatedSince: flagTypes.string({ 36 | description: "search issues updated after yyyy-mm-dd", 37 | }), 38 | updatedUntil: flagTypes.string({ 39 | description: "search issues updated before yyyy-mm-dd", 40 | }), 41 | state: flagTypes.enum({ 42 | options: ["open", "closed"], 43 | description: "search issues in this state", 44 | }), 45 | labels: flagTypes.string({ 46 | description: "search issues with these labels (comma separated)", 47 | }), 48 | jira: flagTypes.boolean({ 49 | description: 50 | "transform output into a usable format for importing to Jira", 51 | dependsOn: ["format"], 52 | }), 53 | query: flagTypes.string({ 54 | description: "Search query matching GitHub issue search syntax", 55 | }), 56 | dateFormat: flagTypes.string({ 57 | description: 58 | "Date format to use when building issue list. Examples: mm/dd/yyyy", 59 | default: "isoDateTime", 60 | }), 61 | }; 62 | 63 | /** 64 | * Transform an Issue object's comments from an array to number properties 65 | * and format them in a way that Jira understands. 66 | * 67 | * Example: 68 | * 69 | * Issue { 70 | * ... 71 | * comments: [{ 72 | * author: { 73 | * login: "mona" 74 | * }, 75 | * createdAt: "1970-01-01T00:00:00", 76 | * body: "I think this is great" 77 | * }, { 78 | * author: { 79 | * login: "mona" 80 | * }, 81 | * createdAt: "1970-01-02T00:00:00", 82 | * body: "I think this is great as well" 83 | * }] 84 | * } 85 | * 86 | * is transformed to 87 | * 88 | * Issue { 89 | * comment0: "1970-01-01T00:00:00;mona;I think this is great" 90 | * comment1: "1970-01-02T00:00:00;mona;I think this is great as well" 91 | * } 92 | * 93 | * @param issue 94 | */ 95 | jiraFormatComments(issue: Issue): void { 96 | let i; 97 | for (i = 0; i < (issue.comments.nodes as IssueComment[]).length; i++) { 98 | const comment = (issue.comments.nodes as IssueComment[])[i]; 99 | // eslint-disable-next-line @typescript-eslint/ban-ts-ignore 100 | // @ts-ignore 101 | issue[`comment${i}`] = [ 102 | comment.createdAt, 103 | (comment.author as User).login, 104 | comment.body, 105 | ].join(";"); 106 | } 107 | } 108 | 109 | async run() { 110 | const { flags } = this.parse(SearchIssues); 111 | const { 112 | owner, 113 | repo, 114 | since, 115 | until, 116 | format, 117 | updatedSince, 118 | updatedUntil, 119 | state, 120 | labels, 121 | jira, 122 | query, 123 | dateFormat, 124 | } = flags; 125 | const searchTerms = ["is:issue"]; 126 | 127 | if (jira && format !== "CSV") { 128 | this.error("--jira is only compatible with --format=CSV."); 129 | } 130 | 131 | if (repo && owner) { 132 | searchTerms.push(`repo:${owner}/${repo}`); 133 | } 134 | if (query) { 135 | searchTerms.push(...query.split(" ")); 136 | } else { 137 | if (state) { 138 | searchTerms.push(`state:${state}`); 139 | } 140 | 141 | if (labels) { 142 | searchTerms.push(`label:"${labels}"`); 143 | } 144 | 145 | /* 146 | * Convert --since and --until into a query range 147 | */ 148 | if (since || until) { 149 | const start = this.parseDateFlag("since", since); 150 | const end = this.parseDateFlag("until", until); 151 | searchTerms.push(`created:"${start}..${end}"`); 152 | } 153 | 154 | /* 155 | * Convert --updatedSince and --updatedUntil into a query range 156 | */ 157 | if (updatedSince || updatedUntil) { 158 | const updateStart = this.parseDateFlag("updatedSince", updatedSince); 159 | const updateEnd = this.parseDateFlag("updatedUntil", updatedUntil); 160 | searchTerms.push(`updated:"${updateStart}..${updateEnd}"`); 161 | } 162 | } 163 | const searchQuery = searchTerms.join(" "); 164 | debug(`Using query: ${searchQuery}`); 165 | 166 | let issueProgress: ProgressBar; 167 | 168 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 169 | const progressCallback = (result: any) => { 170 | if (!issueProgress) { 171 | issueProgress = new ProgressBar( 172 | "fetching issues [:bar] :current/:total :percent", 173 | { 174 | complete: "=", 175 | incomplete: " ", 176 | width: 20, 177 | total: result.search.issueCount, 178 | } 179 | ); 180 | } 181 | const resultLength = result.search.nodes?.length; 182 | if (resultLength) { 183 | issueProgress.tick(resultLength); 184 | } 185 | }; 186 | 187 | const issues = await getIssues(this.github, searchQuery, progressCallback); 188 | 189 | const issuesWithComments = getIssuesWithComments(issues); 190 | 191 | const remainingComments = issuesWithComments 192 | .map((issue: Issue) => { 193 | return ( 194 | issue.comments.totalCount - 195 | (issue.comments.nodes as IssueComment[]).length 196 | ); 197 | }) 198 | .reduce((x: number, y: number) => x + y, 0); 199 | 200 | const progress = new ProgressBar( 201 | "fetching comments [:bar] :current/:total :percent", 202 | { 203 | complete: "=", 204 | incomplete: " ", 205 | width: 20, 206 | total: remainingComments, 207 | } 208 | ); 209 | 210 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 211 | const commentProgressCallback = (result: any) => { 212 | progress.tick( 213 | ((result.node as Issue).comments.nodes as IssueComment[]).length 214 | ); 215 | }; 216 | 217 | if (issuesWithComments.length > 0) { 218 | await getComments( 219 | this.github, 220 | issuesWithComments, 221 | commentProgressCallback 222 | ); 223 | } 224 | 225 | // massage the data to remove GraphQL pagination data 226 | for (const issue of issues) { 227 | if (jira) { 228 | // We have to do surgery on the Issue object 229 | this.jiraFormatComments(issue); 230 | dot.del("comments.nodes", issue); 231 | } else { 232 | dot.move("comments.nodes", "comments", issue); 233 | } 234 | 235 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 236 | iterateObject(issue, (obj: any, prop: any) => { 237 | if (["createdAt", "updatedAt", "closedAt"].indexOf(prop) > -1) { 238 | obj[prop] = dateformat(obj[prop], dateFormat); 239 | } 240 | }); 241 | 242 | dot.del("id", issue); 243 | dot.move("assignees.nodes", "assignees", issue); 244 | dot.move("labels.nodes", "labels", issue); 245 | 246 | // eslint-disable-next-line @typescript-eslint/ban-ts-ignore 247 | // @ts-ignore 248 | issue.labels = issue.labels.map(({ name }) => name).join(", "); 249 | } 250 | 251 | if (format === "JSONL") { 252 | for (const issue of issues) { 253 | process.stdout.write(`${JSON.stringify(issue)}\n`); 254 | } 255 | } else if (format === "JSON") { 256 | process.stdout.write(JSON.stringify(issues)); 257 | } else if (format === "CSV") { 258 | let mapHeaders: Function | null = null; 259 | 260 | if (jira) { 261 | // Jira expects all comments to have a header of just "comment" 262 | // so we map comment0, comment1, comment2 etc to comment 263 | mapHeaders = function (header: string) { 264 | return header.replace(/comment[0-9]+/, "comment"); 265 | }; 266 | } 267 | 268 | const csv = await jsonexport(issues, { fillGaps: true, mapHeaders }); 269 | process.stdout.write(csv); 270 | } 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /packages/cli/src/github.d.ts: -------------------------------------------------------------------------------- 1 | export interface Connection { 2 | totalCount: number; 3 | nodes: [T]; 4 | pageInfo: { 5 | endCursor: string; 6 | hasNextPage: boolean; 7 | hasPreviousPage: boolean; 8 | startCursor: string; 9 | }; 10 | } 11 | 12 | export interface SearchResults { 13 | search: { 14 | issueCount: number; 15 | nodes: [T]; 16 | pageInfo: { 17 | endCursor: string; 18 | hasNextPage: boolean; 19 | hasPreviousPage: boolean; 20 | startCursor: string; 21 | }; 22 | }; 23 | } 24 | 25 | export interface Actor { 26 | login: string; 27 | } 28 | 29 | export interface IssueComment { 30 | author: Actor; 31 | body: string; 32 | createdAt: string; 33 | } 34 | 35 | export interface Label { 36 | name: string; 37 | } 38 | 39 | export enum IssueState { 40 | CLOSED, 41 | OPEN, 42 | } 43 | 44 | export interface Issue { 45 | assignees: Connection; 46 | author: Actor; 47 | comments: Connection; 48 | createdAt: string; 49 | id: string; 50 | labels: Connection