├── .devcontainer
└── .devcontainer.json
├── .env_example
├── .github
├── dependabot.yml
└── workflows
│ ├── ci.yml
│ ├── dependency-review.yml
│ ├── email.yml
│ ├── scorecard.yml
│ ├── scorecards.yml
│ ├── usage-team.yml
│ └── usage.yml
├── .gitignore
├── .pre-commit-config.yaml
├── LICENSE
├── README.md
├── __tests__
├── main.test.ts
└── mock
│ ├── sample-copilot-details-output.md
│ ├── sample-copilot-details.json
│ ├── sample-copilot-seats-output.md
│ ├── sample-copilot-seats.json
│ ├── sample-output.md
│ └── sample.json
├── action.yml
├── dist
├── LICENSE
├── index.js
├── index.js.map
├── job-summary.js
├── job-summary.js.map
├── mermaid.js
├── mermaid.js.map
├── run.js
├── run.js.map
├── sourcemap-register.js
├── utility.js
└── utility.js.map
├── eslint.config.mjs
├── package-lock.json
├── package.json
├── src
├── index.ts
├── job-summary.ts
├── mermaid.ts
├── run.ts
└── utility.ts
└── tsconfig.json
/.devcontainer/.devcontainer.json:
--------------------------------------------------------------------------------
1 | {
2 | "image": "mcr.microsoft.com/devcontainers/typescript-node:20",
3 | "name": "Austen",
4 | "postCreateCommand": "npm i; echo 'Ensure you have set the token in the .env file if you want to run tests.';",
5 | "features": {
6 | "ghcr.io/devcontainers/features/git:1": {
7 | "version": "latest",
8 | "ppa": "false"
9 | }
10 | },
11 | "customizations": {
12 | "vscode": {
13 | "settings": {
14 | "terminal.integrated.profiles.linux": {
15 | "bash": {
16 | "path": "bash",
17 | "icon": "terminal-bash"
18 | }
19 | },
20 | "terminal.integrated.defaultProfile.linux": "bash",
21 | "extensions.ignoreRecommendations": true
22 | },
23 | "extensions": [
24 | "eamodio.gitlens",
25 | "ms-vsliveshare.vsliveshare",
26 | "visualstudioexptteam.vscodeintellicode",
27 | "github.vscode-pull-request-github",
28 | "ms-vscode.vscode-typescript-next",
29 | "github.vscode-github-actions",
30 | "GitHub.copilot",
31 | "GitHub.copilot-chat",
32 | "GitHub.remotehub",
33 | "dbaeumer.vscode-eslint"
34 | ]
35 | }
36 | }
37 | }
--------------------------------------------------------------------------------
/.env_example:
--------------------------------------------------------------------------------
1 | GITHUB_TOKEN=ghp_123
2 | GITHUB_ORG=org
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: github-actions
4 | directory: /
5 | schedule:
6 | interval: daily
7 | - package-ecosystem: npm
8 | directory: /
9 | schedule:
10 | interval: daily
11 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on:
3 | pull_request:
4 | branches: [ main ]
5 | paths-ignore:
6 | - "**.md"
7 | push:
8 | branches: [ main ]
9 | paths-ignore:
10 | - "**.md"
11 | permissions:
12 | contents: read
13 |
14 | jobs:
15 | ci:
16 | runs-on: ubuntu-latest
17 | steps:
18 | - uses: actions/checkout@v4
19 | - uses: actions/setup-node@v4
20 | - run: npm ci
21 | - run: npm run lint
22 | continue-on-error: true
23 | - run: npm run build
24 | - run: npm test
25 | env:
26 | TZ: 'America/New_York'
27 |
--------------------------------------------------------------------------------
/.github/workflows/dependency-review.yml:
--------------------------------------------------------------------------------
1 | # Dependency Review Action
2 | #
3 | # This Action will scan dependency manifest files that change as part of a Pull Request,
4 | # surfacing known-vulnerable versions of the packages declared or updated in the PR.
5 | # Once installed, if the workflow run is marked as required,
6 | # PRs introducing known-vulnerable packages will be blocked from merging.
7 | #
8 | # Source repository: https://github.com/actions/dependency-review-action
9 | name: 'Dependency Review'
10 | on: [pull_request]
11 |
12 | permissions:
13 | contents: read
14 |
15 | jobs:
16 | dependency-review:
17 | runs-on: ubuntu-latest
18 | steps:
19 | - name: Harden Runner
20 | uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
21 | with:
22 | egress-policy: audit
23 |
24 | - name: 'Checkout Repository'
25 | uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
26 | - name: 'Dependency Review'
27 | uses: actions/dependency-review-action@da24556b548a50705dd671f47852072ea4c105d9 # v4.7.1
28 |
--------------------------------------------------------------------------------
/.github/workflows/email.yml:
--------------------------------------------------------------------------------
1 | name: Email Copilot Report
2 | on:
3 | workflow_dispatch:
4 | schedule:
5 | - cron: '0 0 * * 0' # Sunday at midnight (00:00).
6 | permissions:
7 | contents: read
8 |
9 | jobs:
10 | run:
11 | name: Copilot Usage
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: austenstone/copilot-usage@main
15 | id: usage
16 | with:
17 | organization: ${{ vars.ORG }}
18 | github-token: ${{ secrets.TOKEN }}
19 | - uses: austenstone/job-summary@v2.0
20 | id: pdf
21 | with:
22 | name: copilot-usage
23 | - uses: dawidd6/action-send-mail@v5
24 | with:
25 | server_address: smtp.gmail.com
26 | server_port: 465
27 | username: ${{ secrets.EMAIL }}
28 | password: ${{ secrets.PASSWORD }}
29 | from: ${{ secrets.EMAIL }}
30 | to: ${{ secrets.EMAIL }} # Recipient email
31 | subject: "Copilot Usage Report (${{ steps.usage.outputs.since }} - ${{ steps.usage.outputs.until }})"
32 | html_body: |
33 |
34 |
35 |
36 |
37 | Copilot Usage Report
38 | Attached is the Copilot Usage Report for ${{ steps.usage.outputs.since }} - ${{ steps.usage.outputs.until }}!
39 |
40 | View the full report on
41 | GitHub.com
42 |
43 |
44 | ${{ steps.pdf.outputs.job-summary-html }}
45 |
46 |
47 |
48 |
49 | attachments: ${{ steps.pdf.outputs.pdf-file }}
--------------------------------------------------------------------------------
/.github/workflows/scorecard.yml:
--------------------------------------------------------------------------------
1 | # This workflow uses actions that are not certified by GitHub. They are provided
2 | # by a third-party and are governed by separate terms of service, privacy
3 | # policy, and support documentation.
4 |
5 | name: Scorecard supply-chain security
6 | on:
7 | # For Branch-Protection check. Only the default branch is supported. See
8 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
9 | branch_protection_rule:
10 | # To guarantee Maintained check is occasionally updated. See
11 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
12 | schedule:
13 | - cron: '24 22 * * 1'
14 | push:
15 | branches: [ "main" ]
16 |
17 | # Declare default permissions as read only.
18 | permissions: read-all
19 |
20 | jobs:
21 | analysis:
22 | name: Scorecard analysis
23 | runs-on: ubuntu-latest
24 | permissions:
25 | # Needed to upload the results to code-scanning dashboard.
26 | security-events: write
27 | # Needed to publish results and get a badge (see publish_results below).
28 | id-token: write
29 | # Uncomment the permissions below if installing in a private repository.
30 | # contents: read
31 | # actions: read
32 |
33 | steps:
34 | - name: "Checkout code"
35 | uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
36 | with:
37 | persist-credentials: false
38 |
39 | - name: "Run analysis"
40 | uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2
41 | with:
42 | results_file: results.sarif
43 | results_format: sarif
44 | # (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
45 | # - you want to enable the Branch-Protection check on a *public* repository, or
46 | # - you are installing Scorecard on a *private* repository
47 | # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action?tab=readme-ov-file#authentication-with-fine-grained-pat-optional.
48 | # repo_token: ${{ secrets.SCORECARD_TOKEN }}
49 |
50 | # Public repositories:
51 | # - Publish results to OpenSSF REST API for easy access by consumers
52 | # - Allows the repository to include the Scorecard badge.
53 | # - See https://github.com/ossf/scorecard-action#publishing-results.
54 | # For private repositories:
55 | # - `publish_results` will always be set to `false`, regardless
56 | # of the value entered here.
57 | publish_results: true
58 |
59 | # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
60 | # format to the repository Actions tab.
61 | - name: "Upload artifact"
62 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
63 | with:
64 | name: SARIF file
65 | path: results.sarif
66 | retention-days: 5
67 |
68 | # Upload the results to GitHub's code scanning dashboard (optional).
69 | # Commenting out will disable upload of results to your repo's Code Scanning dashboard
70 | - name: "Upload to code-scanning"
71 | uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
72 | with:
73 | sarif_file: results.sarif
74 |
--------------------------------------------------------------------------------
/.github/workflows/scorecards.yml:
--------------------------------------------------------------------------------
1 | # This workflow uses actions that are not certified by GitHub. They are provided
2 | # by a third-party and are governed by separate terms of service, privacy
3 | # policy, and support documentation.
4 |
5 | name: Scorecard supply-chain security
6 | on:
7 | # For Branch-Protection check. Only the default branch is supported. See
8 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
9 | branch_protection_rule:
10 | # To guarantee Maintained check is occasionally updated. See
11 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
12 | schedule:
13 | - cron: '20 7 * * 2'
14 | push:
15 | branches: ["main"]
16 |
17 | # Declare default permissions as read only.
18 | permissions: read-all
19 |
20 | jobs:
21 | analysis:
22 | name: Scorecard analysis
23 | runs-on: ubuntu-latest
24 | permissions:
25 | # Needed to upload the results to code-scanning dashboard.
26 | security-events: write
27 | # Needed to publish results and get a badge (see publish_results below).
28 | id-token: write
29 | contents: read
30 | actions: read
31 | # To allow GraphQL ListCommits to work
32 | issues: read
33 | pull-requests: read
34 | # To detect SAST tools
35 | checks: read
36 |
37 | steps:
38 | - name: Harden Runner
39 | uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
40 | with:
41 | egress-policy: audit
42 |
43 | - name: "Checkout code"
44 | uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
45 | with:
46 | persist-credentials: false
47 |
48 | - name: "Run analysis"
49 | uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2
50 | with:
51 | results_file: results.sarif
52 | results_format: sarif
53 | # (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
54 | # - you want to enable the Branch-Protection check on a *public* repository, or
55 | # - you are installing Scorecards on a *private* repository
56 | # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat.
57 | # repo_token: ${{ secrets.SCORECARD_TOKEN }}
58 |
59 | # Public repositories:
60 | # - Publish results to OpenSSF REST API for easy access by consumers
61 | # - Allows the repository to include the Scorecard badge.
62 | # - See https://github.com/ossf/scorecard-action#publishing-results.
63 | # For private repositories:
64 | # - `publish_results` will always be set to `false`, regardless
65 | # of the value entered here.
66 | publish_results: true
67 |
68 | # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
69 | # format to the repository Actions tab.
70 | - name: "Upload artifact"
71 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
72 | with:
73 | name: SARIF file
74 | path: results.sarif
75 | retention-days: 5
76 |
77 | # Upload the results to GitHub's code scanning dashboard.
78 | - name: "Upload to code-scanning"
79 | uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
80 | with:
81 | sarif_file: results.sarif
82 |
--------------------------------------------------------------------------------
/.github/workflows/usage-team.yml:
--------------------------------------------------------------------------------
1 | name: Usage Teams
2 | on:
3 | push:
4 | branches: [ main ]
5 | paths-ignore:
6 | - "**.md"
7 | workflow_dispatch:
8 | permissions:
9 | contents: read
10 |
11 | jobs:
12 | run:
13 | if: ${{ github.actor != 'dependabot[bot]' }}
14 | name: Copilot Usage
15 | runs-on: ubuntu-latest
16 | strategy:
17 | matrix:
18 | # Add your teams here
19 | team:
20 | - corporate-solutions-eng
21 | - bookstore-developers
22 | steps:
23 | - uses: actions/checkout@v4
24 | - uses: ./
25 | with:
26 | # Add your organization here
27 | organization: ${{ vars.ORG }}
28 | team: ${{ matrix.team }}
29 | # Add your token here
30 | github-token: ${{ secrets.TOKEN }}
31 | time-zone: "EST"
32 | artifact-name: ${{ matrix.team }}-copilot-usage
33 | - uses: austenstone/job-summary@main
34 | with:
35 | name: ${{ matrix.team }}-copilot-usage
36 | artifact-name: ${{ matrix.team }}
37 |
--------------------------------------------------------------------------------
/.github/workflows/usage.yml:
--------------------------------------------------------------------------------
1 | name: Usage
2 | on:
3 | push:
4 | branches: [ main ]
5 | paths-ignore:
6 | - "**.md"
7 | pull_request:
8 | branches: [ main ]
9 | paths-ignore:
10 | - "**.md"
11 | workflow_dispatch:
12 | permissions:
13 | contents: read
14 |
15 | jobs:
16 | run:
17 | if: ${{ github.actor != 'dependabot[bot]' }}
18 | name: Copilot Usage
19 | runs-on: ubuntu-latest
20 | steps:
21 | - uses: actions/checkout@v4
22 | - uses: ./
23 | id: copilot-usage
24 | with:
25 | organization: ${{ vars.ORG }}
26 | github-token: ${{ secrets.TOKEN }}
27 | time-zone: "EST"
28 | json: true
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | .env
4 |
5 | copilot-usage.csv
6 | copilot-usage.md
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | repos:
2 | - repo: https://github.com/gitleaks/gitleaks
3 | rev: v8.16.3
4 | hooks:
5 | - id: gitleaks
6 | - repo: https://github.com/pre-commit/mirrors-eslint
7 | rev: v8.38.0
8 | hooks:
9 | - id: eslint
10 | - repo: https://github.com/pre-commit/pre-commit-hooks
11 | rev: v4.4.0
12 | hooks:
13 | - id: end-of-file-fixer
14 | - id: trailing-whitespace
15 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright 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.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Copilot Usage Action
2 |
3 | https://github.com/user-attachments/assets/23bfb179-6e12-4db5-9ec6-be0019a692f8
4 |
5 | Get Copilot usage data as:
6 | * Md Job Summary
7 | * CSV
8 | * XML
9 | * JSON
10 |
11 | Powered by the [REST API endpoints for GitHub Copilot usage metrics](https://docs.github.com/en/rest/copilot/copilot-usage).
12 |
13 | > [!TIP]
14 | > 🚀 Get this running FAST by using the [template](https://github.com/austenstone/copilot-usage-template)
15 |
16 | ## Usage
17 | Create a workflow (eg: `.github/workflows/copilot-usage.yml`). See [Creating a Workflow file](https://help.github.com/en/articles/configuring-a-workflow#creating-a-workflow-file).
18 |
19 | ### PAT(Personal Access Token)
20 |
21 | You will need to [create a PAT(Personal Access Token)](https://github.com/settings/tokens/new?scopes=manage_billing:copilot) that has the `manage_billing:copilot`, `read:org`, or `read:enterprise` scopes to use this endpoint.
22 |
23 | Add this PAT as a secret so we can use it as input `github-token`, see [Creating encrypted secrets for a repository](https://docs.github.com/en/enterprise-cloud@latest/actions/security-guides/encrypted-secrets#creating-encrypted-secrets-for-a-repository).
24 |
25 | #### Basic Example
26 |
27 | The default behavior is to get the usage for the repository owner which is likely the organization.
28 |
29 | > [!IMPORTANT]
30 | > You need to set the secret `TOKEN` in your repository settings.
31 |
32 | ```yml
33 | name: Copilot Usage
34 | on:
35 | schedule:
36 | - cron: '0 0 * * *'
37 | workflow_dispatch:
38 |
39 | jobs:
40 | run:
41 | name: Run Action
42 | runs-on: ubuntu-latest
43 | steps:
44 | - uses: austenstone/copilot-usage@v5.2
45 | with:
46 | github-token: ${{ secrets.TOKEN }}
47 | time-zone: 'EST'
48 | ```
49 |
50 | #### Example get team usage
51 |
52 | ```yml
53 | - uses: austenstone/copilot-usage@v5.2
54 | with:
55 | github-token: ${{ secrets.TOKEN }}
56 | organization: 'org-slug'
57 | team: 'team-slug'
58 | ```
59 |
60 | #### Example get CSV
61 |
62 | ```yml
63 | - uses: austenstone/copilot-usage@v5.2
64 | with:
65 | github-token: ${{ secrets.TOKEN }}
66 | csv: true
67 | ```
68 |
69 | #### Example multiple teams
70 | ```yml
71 | strategy:
72 | matrix:
73 | team:
74 | - 'team-slug1'
75 | - 'team-slug2'
76 | steps:
77 | - uses: actions/checkout@v4
78 | - uses: austenstone/copilot-usage@v5.2
79 | with:
80 | github-token: ${{ secrets.TOKEN }}
81 | organization: 'org-slug'
82 | team: ${{ matrix.team }}
83 | ```
84 |
85 | #### Example specific timezone
86 |
87 | You probably want to specify the timezone to get the usage in your local time. The default is UTC.
88 | EX: `EST`, `PST`, `CST`, [etc](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones).
89 |
90 | ```yml
91 | - uses: austenstone/copilot-usage@v5.2
92 | with:
93 | github-token: ${{ secrets.TOKEN }}
94 | organization: 'org-slug'
95 | time-zone: 'EST'
96 | ```
97 |
98 | #### Example sending email PDF report
99 |
100 | > [!IMPORTANT]
101 | > You must set secrets for `EMAIL` and `PASSWORD` to send the email. You must use an [App Password](https://support.google.com/accounts/answer/185833?visit_id=638496193361004722-1436339969&p=InvalidSecondFactor&rd=1#app-passwords) for Gmail.
102 |
103 | ```yml
104 | name: Email Copilot Report
105 | on:
106 | workflow_dispatch:
107 | schedule:
108 | - cron: '0 0 * * *'
109 |
110 | jobs:
111 | run:
112 | runs-on: ubuntu-latest
113 | steps:
114 | - uses: austenstone/copilot-usage@v5.2
115 | with:
116 | github-token: ${{ secrets.TOKEN }}
117 | - uses: austenstone/job-summary@v2.0
118 | id: pdf
119 | with:
120 | name: copilot-usage
121 | - uses: dawidd6/action-send-mail@v4
122 | with:
123 | server_address: smtp.gmail.com
124 | server_port: 465
125 | username: ${{ secrets.EMAIL }}
126 | password: ${{ secrets.PASSWORD }}
127 | from: ${{ secrets.EMAIL }}
128 | to: ${{ secrets.EMAIL }} # Recipient email
129 | subject: "Copilot Usage Report (${{ steps.usage.outputs.since }} - ${{ steps.usage.outputs.until }})"
130 | html_body: |
131 |
132 |
133 |
134 |
135 | Copilot Usage Report
136 | Attached is the Copilot Usage Report for ${{ steps.usage.outputs.since }} - ${{ steps.usage.outputs.until }}!
137 |
138 | View the full report on
139 | GitHub.com
140 |
141 |
142 | ${{ steps.pdf.outputs.job-summary-html }}
143 |
144 |
145 |
146 |
147 | attachments: ${{ steps.pdf.outputs.pdf-file }}
148 | ```
149 |
150 | > [!TIP]
151 | > Try using other messaging systems such as [slack](https://github.com/marketplace/actions/slack-send), [teams](https://github.com/marketplace/actions/microsoft-teams-notification), [discord](https://github.com/marketplace/actions/discord-message-notify), etc.
152 |
153 | 
154 |
155 | ## ➡️ Inputs
156 |
157 | If no `organization` or `team` input are provided, we default to the repository owner which is likely the organization.
158 |
159 | Various inputs are defined in [`action.yml`](action.yml):
160 |
161 | | Name | Description | Default |
162 | | --- | --- | --- |
163 | | github-token | The GitHub token used to create an authenticated client | |
164 | | organization | The organization slug | ${{ github.repository_owner }} |
165 | | job-summary | Whether to generate a report | true |
166 | | csv | Whether to generate a CSV as a workflow artifact | false |
167 | | csv-options | The options for the CSV report | |
168 | | xml | Whether to generate an XML as a workflow artifact | false |
169 | | xml-options | The options for the XML report | |
170 | | team | The team slug | |
171 | | days | The number of days to show usage metrics for | |
172 | | since | Show usage metrics since this date. This is a timestamp, in `YYYY-MM-DD` format. Maximum value is 28 days ago | |
173 | | until | Show usage metrics until this date. This is a timestamp, in `YYYY-MM-DD` format. Maximum value is 28 days ago | |
174 | | time-zone | The time zone to use for the report | UTC |
175 |
176 | ## ⬅️ Outputs
177 | | Name | Description |
178 | | --- | - |
179 | | result | The copilot usage as a JSON string |
180 | | since | The date since which the usage metrics are shown |
181 | | until | The date until which the usage metrics are shown |
182 |
183 | ### Endpoints
184 |
185 | The endpoints used by this action...
186 |
187 | * GET /orgs/{org}/team/{team}/copilot/usage
188 | * GET /orgs/{org}/copilot/usage
189 | * GET /orgs/{org}/copilot/billing
190 | * GET /orgs/{org}/copilot/billing/seats
191 |
192 | ## Example Job Summary
193 |
194 | [View latest reports](https://github.com/austenstone/copilot-usage/actions/workflows/usage.yml)
195 |
196 |
--------------------------------------------------------------------------------
/__tests__/main.test.ts:
--------------------------------------------------------------------------------
1 | import { test, beforeAll, beforeEach, expect } from 'vitest';
2 | import dotenv from 'dotenv'
3 | dotenv.config({ override: true })
4 | import { createJobSummaryCopilotDetails, createJobSummarySeatAssignments, createJobSummaryUsage } from '../src/job-summary';
5 | import { sumNestedValue } from '../src/job-summary'; // Import sumNestedValue function
6 | import { summary } from '@actions/core/lib/summary';
7 | import { read, readFileSync, writeFileSync } from 'fs';
8 |
9 | const getSummaryBuffer = (_summary: typeof summary): string => {
10 | return (_summary as unknown as {
11 | _buffer: string,
12 | _filePath?: string;
13 | })._buffer
14 | }
15 |
16 | beforeAll(async () => {
17 | // await createMockData();
18 | });
19 |
20 | beforeEach(() => {
21 | summary.emptyBuffer();
22 | });
23 |
24 | const sample = readFileSync('./__tests__/mock/sample.json', 'utf-8');
25 | const exampleResponseEnterprise = JSON.parse(sample);
26 | const sampleCopilotDetails = readFileSync('./__tests__/mock/sample-copilot-details.json', 'utf-8');
27 | const exampleResponseCopilotDetails = JSON.parse(sampleCopilotDetails);
28 | const sampleCopilotSeats = readFileSync('./__tests__/mock/sample-copilot-seats.json', 'utf-8');
29 | const exampleResponseCopilotSeats = JSON.parse(sampleCopilotSeats);
30 |
31 | test('createJobSummaryUsage(enterpriseUsage)', async () => {
32 | const summary = await createJobSummaryUsage(exampleResponseEnterprise, 'enterprise');
33 | writeFileSync('./__tests__/mock/sample-output.md', summary.stringify());
34 | expect(summary).toBeDefined();
35 | });
36 |
37 | test('createJobSummaryCopilotDetails(enterpriseUsage)', async () => {
38 | const summary = await createJobSummaryCopilotDetails(exampleResponseCopilotDetails);
39 | writeFileSync('./__tests__/mock/sample-copilot-details-output.md', summary.stringify());
40 | expect(summary).toBeDefined();
41 | });
42 |
43 | test('createJobSummaryCopilotSeats(enterpriseUsage)', async () => {
44 | const summary = await createJobSummarySeatAssignments(exampleResponseCopilotSeats.seats);
45 | writeFileSync('./__tests__/mock/sample-copilot-seats-output.md', summary.stringify());
46 | expect(summary).toBeDefined();
47 | });
48 |
49 |
50 | // Tests for sumNestedValue function
51 | test('sumNestedValue with simple objects', () => {
52 | const data = [
53 | { a: { b: 10 } },
54 | { a: { b: 20 } },
55 | { a: { b: 30 } }
56 | ];
57 | expect(sumNestedValue(data, ['a', 'b'])).toBe(60);
58 | });
59 |
60 | test('sumNestedValue with missing paths', () => {
61 | const data = [
62 | { a: { b: 10 } },
63 | { a: { c: 20 } }, // Missing 'b' key
64 | { a: { b: 30 } }
65 | ];
66 | expect(sumNestedValue(data, ['a', 'b'])).toBe(40); // Should skip the object with missing path
67 | });
68 |
69 | test('sumNestedValue with deeply nested objects', () => {
70 | const data = [
71 | { level1: { level2: { level3: 100 } } },
72 | { level1: { level2: { level3: 200 } } }
73 | ];
74 | expect(sumNestedValue(data, ['level1', 'level2', 'level3'])).toBe(300);
75 | });
76 |
77 | test('sumNestedValue with non-numeric values', () => {
78 | const data = [
79 | { a: { b: 10 } },
80 | { a: { b: "20" } }, // String value instead of number
81 | { a: { b: 30 } }
82 | ];
83 | expect(sumNestedValue(data, ['a', 'b'])).toBe(40); // Should only sum numeric values
84 | });
85 |
86 | test('sumNestedValue with empty data array', () => {
87 | expect(sumNestedValue([], ['a', 'b'])).toBe(0); // Should return 0 for empty array
88 | });
89 |
90 | test('sumNestedValue with completely missing path', () => {
91 | const data = [
92 | { x: { y: 10 } },
93 | { x: { y: 20 } }
94 | ];
95 | expect(sumNestedValue(data, ['a', 'b'])).toBe(0); // Path doesn't exist at all
96 | });
97 |
98 | // New test for array traversal
99 | test('sumNestedValue with array traversal', () => {
100 | const data = [
101 | {
102 | a: {
103 | items: [
104 | { value: 5 },
105 | { value: 10 }
106 | ]
107 | }
108 | },
109 | {
110 | a: {
111 | items: [
112 | { value: 15 },
113 | { value: 20 }
114 | ]
115 | }
116 | }
117 | ];
118 | expect(sumNestedValue(data, ['a', 'items', 'value'])).toBe(50); // Should sum all values in the arrays
119 | });
120 |
121 | test('sumNestedValue with exampleResponseEnterprise data', () => {
122 | // Test with real data paths
123 | const totalChatEngagedUsers = sumNestedValue(exampleResponseEnterprise, ['copilot_ide_chat', 'total_engaged_users']);
124 | expect(totalChatEngagedUsers).toBeGreaterThan(0);
125 |
126 | // Calculate total active users across all days
127 | const totalActiveUsers = sumNestedValue(exampleResponseEnterprise, ['total_active_users']);
128 | expect(totalActiveUsers).toBeGreaterThan(0);
129 |
130 | // Test with a more specific path - this needed to be adjusted to match the actual data structure
131 | const totalEngagedUsers = sumNestedValue(exampleResponseEnterprise, ['total_engaged_users']);
132 | expect(totalEngagedUsers).toBeGreaterThan(0);
133 |
134 | // Test a path that should return 0 (non-existent path)
135 | const nonExistentPath = sumNestedValue(exampleResponseEnterprise, ['non', 'existent', 'path']);
136 | expect(nonExistentPath).toBe(0);
137 | });
138 |
--------------------------------------------------------------------------------
/__tests__/mock/sample-copilot-details-output.md:
--------------------------------------------------------------------------------
1 | Seat Info
2 | Organization Copilot Details
3 | Plan Type | ENTERPRISE |
Seat Management Setting | Assign All |
4 | Seat Breakdown
5 | Total Seats | 867 |
Added this cycle | 1 |
Pending invites | 0 |
Pending cancellations | 0 |
Active this cycle | 94 |
Inactive this cycle | 773 |
6 | Policies
7 | Public Code Suggestions Enabled | Allowed |
IDE Chat Enabled | ENABLED |
Platform Chat Enabled | ENABLED |
CLI Enabled | ENABLED |
8 |
--------------------------------------------------------------------------------
/__tests__/mock/sample-copilot-details.json:
--------------------------------------------------------------------------------
1 | {
2 | "seat_breakdown": {
3 | "pending_invitation": 0,
4 | "pending_cancellation": 0,
5 | "added_this_cycle": 1,
6 | "total": 867,
7 | "active_this_cycle": 94,
8 | "inactive_this_cycle": 773
9 | },
10 | "seat_management_setting": "assign_all",
11 | "plan_type": "enterprise",
12 | "public_code_suggestions": "allow",
13 | "ide_chat": "enabled",
14 | "cli": "enabled",
15 | "platform_chat": "enabled"
16 | }
--------------------------------------------------------------------------------
/__tests__/mock/sample-output.md:
--------------------------------------------------------------------------------
1 | Copilot Usage for enterprise
5/6 - 6/1
2 | Metrics for the last 27 daysTotals
3 | Code Suggestions | 208,069 |
Code Acceptances | 36,847 |
Acceptance Rate | 17.71% |
Lines of Code Accepted | 48,359 |
Chat Interactions | 78,458 |
Chat Copy Events | 2,131 |
Chat Insertion Events | 480 |
4 | Daily Engaged Users
5 |
6 | ```mermaid
7 | ---
8 | config:
9 | xyChart:
10 | width: 900
11 | height: 500
12 | xAxis:
13 | labelPadding: 20
14 | yAxis:
15 | labelPadding: 20
16 | themeVariables:
17 | xyChart:
18 | backgroundColor: "transparent"
19 | ---
20 | xychart-beta
21 |
22 | x-axis [6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 1]
23 | y-axis 0 --> 384
24 | bar [16, 19, 11, 14, 3, 1, 330, 366, 378, 372, 333, 100, 111, 340, 370, 377, 372, 343, 88, 80, 160, 344, 384, 367, 306, 83, 76]
25 | bar [16, 16, 11, 12, 3, 1, 309, 336, 350, 339, 303, 89, 100, 307, 342, 347, 344, 311, 84, 72, 143, 318, 349, 334, 276, 77, 65]
26 | ```
27 |
28 |  Active  EngagedDaily Engaged Users by Product
29 |
30 | ```mermaid
31 | ---
32 | config:
33 | xyChart:
34 | width: 900
35 | height: 500
36 | xAxis:
37 | labelPadding: 20
38 | yAxis:
39 | labelPadding: 20
40 | themeVariables:
41 | xyChart:
42 | backgroundColor: "transparent"
43 | ---
44 | xychart-beta
45 |
46 | x-axis [6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 1]
47 | y-axis 0 --> 235
48 | line [9, 5, 5, 4, 0, 0, 97, 108, 116, 114, 88, 34, 39, 101, 101, 105, 110, 86, 35, 32, 54, 106, 91, 84, 84, 27, 29]
49 | line [9, 11, 8, 6, 1, 0, 139, 175, 177, 171, 134, 55, 56, 155, 168, 162, 161, 147, 53, 46, 89, 160, 162, 146, 125, 44, 37]
50 | line [7, 9, 5, 8, 2, 1, 184, 175, 206, 195, 185, 36, 48, 189, 221, 229, 230, 197, 35, 25, 76, 198, 235, 235, 180, 39, 23]
51 | line [6, 4, 4, 4, 0, 0, 20, 21, 21, 23, 13, 1, 5, 22, 26, 19, 21, 14, 1, 1, 12, 25, 23, 17, 16, 1, 2]
52 | ```
53 |
54 |  IDE Code Completions  IDE Chat  Dotcom Chat  Dotcom Pull RequestsIDE Completion
55 | Suggestions vs. Acceptances
56 |
57 | ```mermaid
58 | ---
59 | config:
60 | xyChart:
61 | width: 900
62 | height: 500
63 | xAxis:
64 | labelPadding: 20
65 | yAxis:
66 | labelPadding: 20
67 | themeVariables:
68 | xyChart:
69 | backgroundColor: "transparent"
70 | ---
71 | xychart-beta
72 |
73 | x-axis [6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 1]
74 | y-axis 0 --> 15379
75 | bar [1322, 772, 284, 904, 26, 0, 13352, 13728, 14011, 12325, 15011, 3742, 6278, 12151, 11105, 14531, 15379, 10264, 2335, 3424, 8553, 10685, 10706, 9161, 10736, 3680, 3604]
76 | bar [290, 138, 48, 49, 0, 0, 2129, 2866, 2854, 2359, 2997, 648, 1167, 1867, 1937, 2638, 2535, 1684, 400, 392, 1498, 2003, 1819, 1508, 1677, 636, 708]
77 | ```
78 |
79 |  Suggestions  AcceptancesLines Suggested vs. Accepted
80 |
81 | ```mermaid
82 | ---
83 | config:
84 | xyChart:
85 | width: 900
86 | height: 500
87 | xAxis:
88 | labelPadding: 20
89 | yAxis:
90 | labelPadding: 20
91 | themeVariables:
92 | xyChart:
93 | backgroundColor: "transparent"
94 | ---
95 | xychart-beta
96 |
97 | x-axis [6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 1]
98 | y-axis 0 --> 23270
99 | bar [2182, 1417, 596, 1448, 39, 0, 22232, 20908, 21717, 18932, 23212, 5175, 11072, 17133, 16592, 21789, 23270, 15247, 3485, 5031, 12578, 16940, 16025, 14250, 15221, 6828, 5316]
100 | bar [428, 186, 56, 96, 0, 0, 3144, 3624, 4018, 2970, 3272, 785, 1540, 2401, 2594, 3850, 3258, 2266, 413, 404, 1822, 2888, 2534, 2059, 1976, 969, 806]
101 | ```
102 |
103 |  Lines Suggested  Lines AcceptedAcceptance Rate
104 |
105 | ```mermaid
106 | ---
107 | config:
108 | xyChart:
109 | width: 900
110 | height: 500
111 | xAxis:
112 | labelPadding: 20
113 | yAxis:
114 | labelPadding: 20
115 | themeVariables:
116 | xyChart:
117 | backgroundColor: "transparent"
118 | ---
119 | xychart-beta
120 |
121 | x-axis [6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 1]
122 | y-axis 0 --> 100
123 | line [22, 18, 17, 5, 0, 0, 16, 21, 20, 19, 20, 17, 19, 15, 17, 18, 16, 16, 17, 11, 18, 19, 17, 16, 16, 17, 20]
124 | ```
125 | Acceptance Rate by Language
126 |
127 | ```mermaid
128 | ---
129 | config:
130 | xyChart:
131 | width: 900
132 | height: 500
133 | xAxis:
134 | labelPadding: 20
135 | yAxis:
136 | labelPadding: 20
137 | themeVariables:
138 | xyChart:
139 | backgroundColor: "transparent"
140 | ---
141 | xychart-beta
142 |
143 | x-axis [6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 1]
144 | y-axis 0 --> 100
145 | line [12, 9, 10, 3, 0, 0, 10, 16, 13, 15, 13, 17, 15, 8, 16, 8, 9, 11, 13, 13, 12, 13, 12, 8, 10, 14, 9]
146 | line [0, 0, 33, 0, 0, 0, 19, 32, 36, 25, 20, 20, 31, 19, 22, 30, 19, 18, 18, 12, 29, 30, 20, 19, 18, 16, 24]
147 | line [50, 0, 0, 10, 0, 0, 19, 31, 24, 24, 28, 21, 22, 17, 27, 19, 18, 35, 31, 28, 22, 18, 21, 19, 17, 26, 29]
148 | line [23, 19, 19, 6, 0, 0, 23, 24, 26, 28, 35, 24, 21, 33, 26, 24, 26, 17, 23, 27, 17, 22, 27, 26, 29, 35, 21]
149 | line [34, 0, 0, 0, 0, 0, 25, 26, 20, 22, 29, 21, 4, 22, 26, 25, 23, 18, 0, 5, 31, 26, 27, 25, 24, 29, 0]
150 | ```
151 |
152 |  markdown  python  typescriptreact  typescript  goLanguage Usage by Engaged Users
153 |
154 | ```mermaid
155 | ---
156 | config:
157 |
158 | ---
159 | pie
160 |
161 | "markdown" : 36
162 | "typescriptreact" : 23
163 | "typescript" : 21
164 | "yaml" : 19
165 | "python" : 19
166 | "ruby" : 17
167 | "javascript" : 14
168 | "go" : 11
169 | "json" : 9
170 | "java" : 9
171 | "shellscript" : 8
172 | "css" : 8
173 | "git-commit" : 6
174 | "jsonc" : 6
175 | "plaintext" : 5
176 | "csharp" : 5
177 | "html" : 5
178 | "ignore" : 4
179 | "dotenv" : 4
180 | "terraform" : 3
181 | ```
182 | Editor Usage by Engaged Users
183 |
184 | ```mermaid
185 | ---
186 | config:
187 |
188 | ---
189 | pie
190 |
191 | "vscode" : 288
192 | "JetBrains" : 13
193 | "Neovim" : 10
194 | "VisualStudio" : 8
195 | "Eclipse" : 4
196 | "Vim" : 3
197 | "Xcode" : 2
198 | "Emacs" : 2
199 | ```
200 | IDE Copilot Chat
201 |
202 | ```mermaid
203 | ---
204 | config:
205 | xyChart:
206 | width: 900
207 | height: 500
208 | xAxis:
209 | labelPadding: 20
210 | yAxis:
211 | labelPadding: 20
212 | themeVariables:
213 | xyChart:
214 | backgroundColor: "transparent"
215 | ---
216 | xychart-beta
217 |
218 | x-axis [6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 1]
219 | y-axis 0 --> 5549
220 | bar [291, 249, 342, 250, 4, 0, 3902, 4870, 4510, 4423, 4055, 1857, 2475, 4366, 4277, 4483, 4330, 3884, 2024, 1747, 3394, 5549, 4044, 4606, 4854, 1594, 2078]
221 | line [2, 5, 12, 1, 0, 0, 95, 135, 173, 97, 99, 16, 82, 167, 129, 139, 134, 124, 41, 33, 70, 148, 87, 114, 103, 55, 70]
222 | line [3, 4, 2, 0, 0, 0, 25, 57, 46, 36, 36, 16, 10, 29, 24, 37, 26, 18, 4, 8, 6, 39, 15, 12, 14, 3, 10]
223 | ```
224 |
225 |  Total Chats  Copy Events  Insert EventsCopilot .COM Chat
226 | Total Chats
227 |
228 | ```mermaid
229 | ---
230 | config:
231 | xyChart:
232 | width: 900
233 | height: 500
234 | xAxis:
235 | labelPadding: 20
236 | yAxis:
237 | labelPadding: 20
238 | themeVariables:
239 | xyChart:
240 | backgroundColor: "transparent"
241 | ---
242 | xychart-beta
243 |
244 | x-axis [6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 1]
245 | y-axis 0 --> 2124
246 | bar [34, 60, 38, 68, 7, 15, 1235, 1045, 1293, 1235, 1522, 297, 313, 1264, 1524, 2124, 1518, 1584, 195, 214, 450, 1270, 1649, 1601, 1366, 301, 204]
247 | ```
248 |
249 |  Total ChatsCopilot .COM Pull Request
250 | Summaries Created
251 |
252 | ```mermaid
253 | ---
254 | config:
255 | xyChart:
256 | width: 900
257 | height: 500
258 | xAxis:
259 | labelPadding: 20
260 | yAxis:
261 | labelPadding: 20
262 | themeVariables:
263 | xyChart:
264 | backgroundColor: "transparent"
265 | ---
266 | xychart-beta
267 |
268 | x-axis [6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 1]
269 | y-axis 0 --> 100
270 | bar [6, 8, 4, 4, 0, 0, 31, 28, 35, 50, 18, 1, 7, 30, 35, 32, 34, 18, 1, 1, 15, 53, 40, 31, 30, 1, 3]
271 | ```
272 |
273 |  Total PR Summaries Created
--------------------------------------------------------------------------------
/action.yml:
--------------------------------------------------------------------------------
1 | name: Copilot Usage Action
2 | author: Austen Stone
3 | description: Get reports on GitHub Copilot Usage
4 | branding:
5 | icon: "bar-chart-2"
6 | color: "blue"
7 |
8 | inputs:
9 | github-token:
10 | description: The GitHub token used to create an authenticated client
11 | required: true
12 | enterprise:
13 | description: The GitHub enterprise slug
14 | required: false
15 | organization:
16 | description: The organization slug
17 | default: ${{ github.repository_owner }}
18 | required: false
19 | team:
20 | description: The team slug
21 | required: false
22 | days:
23 | description: The number of days to show usage metrics for.
24 | required: false
25 | since:
26 | description: Show usage metrics since this date. This is a timestamp in the format (YYYY-MM-DD). Maximum value is 28 days ago.
27 | required: false
28 | until:
29 | description: Show usage metrics until this date. This is a timestamp in the format (YYYY-MM-DD). Maximum value is 28 days ago.
30 | required: false
31 | job-summary:
32 | description: Whether to generate a report
33 | required: false
34 | default: true
35 | json:
36 | description: Whether to generate JSON as a workflow artifact
37 | required: false
38 | default: true
39 | csv:
40 | description: Whether to generate CSV as a workflow artifact
41 | required: false
42 | default: false
43 | csv-options:
44 | description: Options for the CSV generation
45 | required: false
46 | xml:
47 | description: Whether to generate XML as a workflow artifact
48 | required: false
49 | default: false
50 | xml-options:
51 | description: Options for the XML generation
52 | required: false
53 | time-zone:
54 | description: The time zone to use for the report
55 | required: false
56 | default: "UTC"
57 | artifact-name:
58 | description: The name of the artifact to create
59 | required: false
60 | default: "copilot-usage"
61 | outputs:
62 | result:
63 | description: The copilot usage as a JSON string
64 | since:
65 | description: The date since which the usage metrics are shown
66 | until:
67 | description: The date until which the usage metrics are shown
68 | runs:
69 | using: "node20"
70 | main: "dist/index.js"
71 |
--------------------------------------------------------------------------------
/dist/job-summary.js:
--------------------------------------------------------------------------------
1 | import { summary } from "@actions/core";
2 | import { createPieChart, createXYChart, DEFAULT_CHART_CONFIGS } from "./mermaid";
3 | import { dateFormat } from "./utility";
4 | const getEmptyBaseMetrics = () => ({
5 | total_engaged_users: 0,
6 | total_code_acceptances: 0,
7 | total_code_suggestions: 0,
8 | total_code_lines_accepted: 0,
9 | total_code_lines_suggested: 0
10 | });
11 | export const sumNestedValue = (data, path) => {
12 | return data.reduce((sum, obj) => {
13 | let result = 0;
14 | const traverse = (current, pathIndex) => {
15 | if (current === undefined || current === null)
16 | return;
17 | if (pathIndex >= path.length) {
18 | if (typeof current === 'number') {
19 | result += current;
20 | }
21 | return;
22 | }
23 | const key = path[pathIndex];
24 | if (Array.isArray(current)) {
25 | current.forEach(item => traverse(item, pathIndex));
26 | }
27 | else if (typeof current === 'object') {
28 | if (key in current) {
29 | traverse(current[key], pathIndex + 1);
30 | }
31 | }
32 | };
33 | traverse(obj, 0);
34 | return sum + result;
35 | }, 0);
36 | };
37 | const aggregateMetricsBy = (data, groupFn) => {
38 | return data.reduce((acc, day) => {
39 | const dayMetrics = groupFn(day);
40 | Object.entries(dayMetrics).forEach(([key, metrics]) => {
41 | acc[key] = acc[key] || getEmptyBaseMetrics();
42 | Object.entries(metrics).forEach(([metric, value]) => {
43 | if (metric === 'total_engaged_users') {
44 | acc[key][metric] = Math.max(acc[key][metric], value);
45 | }
46 | else {
47 | acc[key][metric] += value;
48 | }
49 | });
50 | });
51 | return acc;
52 | }, {});
53 | };
54 | const groupLanguageMetrics = (day) => {
55 | const metrics = {};
56 | day.copilot_ide_code_completions?.editors?.forEach(editor => {
57 | editor.models?.forEach(model => {
58 | model.languages?.forEach(lang => {
59 | const language = lang.name || 'unknown';
60 | metrics[language] = metrics[language] || getEmptyBaseMetrics();
61 | Object.entries(lang).forEach(([key, value]) => {
62 | if (key in metrics[language] && typeof value === 'number') {
63 | metrics[language][key] += value;
64 | }
65 | });
66 | });
67 | });
68 | });
69 | return metrics;
70 | };
71 | const groupEditorMetrics = (day) => {
72 | const metrics = {};
73 | day.copilot_ide_code_completions?.editors?.forEach(editor => {
74 | const editorName = editor.name || 'unknown';
75 | metrics[editorName] = metrics[editorName] || getEmptyBaseMetrics();
76 | metrics[editorName].total_engaged_users = editor.total_engaged_users || 0;
77 | editor.models?.forEach(model => {
78 | model.languages?.forEach(lang => {
79 | Object.entries(lang).forEach(([key, value]) => {
80 | if (key in metrics[editorName] && typeof value === 'number') {
81 | metrics[editorName][key] += value;
82 | }
83 | });
84 | });
85 | });
86 | });
87 | return metrics;
88 | };
89 | const getChatMetrics = (dailyTotals) => ({
90 | totalChats: dailyTotals.reduce((sum, day) => sum + (day.total_chats || 0), 0),
91 | totalCopyEvents: dailyTotals.reduce((sum, day) => sum + (day.total_chat_copy_events || 0), 0),
92 | totalInsertEvents: dailyTotals.reduce((sum, day) => sum + (day.total_chat_insert_events || 0), 0)
93 | });
94 | export const createJobSummaryUsage = (data, name) => {
95 | const languageMetrics = aggregateMetricsBy(data, groupLanguageMetrics);
96 | const editorMetrics = aggregateMetricsBy(data, groupEditorMetrics);
97 | const dailyTotals = data.map(day => ({
98 | date: day.date,
99 | total_active_users: day.total_active_users || 0,
100 | total_engaged_users: day.total_engaged_users || 0,
101 | total_code_acceptances: sumNestedValue([day], ['copilot_ide_code_completions', 'editors', 'models', 'languages', 'total_code_acceptances']),
102 | total_code_suggestions: sumNestedValue([day], ['copilot_ide_code_completions', 'editors', 'models', 'languages', 'total_code_suggestions']),
103 | total_code_lines_accepted: sumNestedValue([day], ['copilot_ide_code_completions', 'editors', 'models', 'languages', 'total_code_lines_accepted']),
104 | total_code_lines_suggested: sumNestedValue([day], ['copilot_ide_code_completions', 'editors', 'models', 'languages', 'total_code_lines_suggested']),
105 | total_chats: sumNestedValue([day], ['copilot_ide_chat', 'editors', 'models', 'total_chats']),
106 | total_chat_copy_events: sumNestedValue([day], ['copilot_ide_chat', 'editors', 'models', 'total_chat_copy_events']),
107 | total_chat_insert_events: sumNestedValue([day], ['copilot_ide_chat', 'editors', 'models', 'total_chat_insertion_events']),
108 | total_dotcom_chat_chats: sumNestedValue([day], ['copilot_dotcom_chat', 'models', 'total_chats']),
109 | total_dotcom_pr_summaries_created: sumNestedValue([day], ['copilot_dotcom_pull_requests', 'repositories', 'models', 'total_pr_summaries_created']),
110 | }));
111 | const chatMetrics = getChatMetrics(dailyTotals);
112 | const topLanguages = Object.entries(languageMetrics)
113 | .sort((a, b) => b[1].total_code_suggestions - a[1].total_code_suggestions)
114 | .slice(0, 5)
115 | .map(([lang]) => lang);
116 | const totalMetrics = Object.values(languageMetrics).reduce((acc, curr) => ({
117 | totalCodeAcceptances: acc.totalCodeAcceptances + curr.total_code_acceptances,
118 | totalCodeSuggestions: acc.totalCodeSuggestions + curr.total_code_suggestions,
119 | totalLinesAccepted: acc.totalLinesAccepted + curr.total_code_lines_accepted
120 | }), { totalCodeAcceptances: 0, totalCodeSuggestions: 0, totalLinesAccepted: 0 });
121 | return summary
122 | .addHeading(`Copilot Usage for ${name}
${dateFormat(data[0].date)} - ${dateFormat(data[data.length - 1].date)}`)
123 | .addRaw(`Metrics for the last ${data.length} days`)
124 | .addHeading('Totals', 2)
125 | .addTable([
126 | ['Code Suggestions', totalMetrics.totalCodeSuggestions.toLocaleString()],
127 | ['Code Acceptances', totalMetrics.totalCodeAcceptances.toLocaleString()],
128 | ['Acceptance Rate', `${((totalMetrics.totalCodeAcceptances / totalMetrics.totalCodeSuggestions) * 100).toFixed(2)}%`],
129 | ['Lines of Code Accepted', totalMetrics.totalLinesAccepted.toLocaleString()],
130 | ['Chat Interactions', chatMetrics.totalChats.toLocaleString()],
131 | ['Chat Copy Events', chatMetrics.totalCopyEvents.toLocaleString()],
132 | ['Chat Insertion Events', chatMetrics.totalInsertEvents.toLocaleString()]
133 | ])
134 | .addHeading('Daily Engaged Users', 3)
135 | .addRaw(createXYChart({
136 | xAxis: {
137 | categories: DEFAULT_CHART_CONFIGS.dailyCategories(data)
138 | },
139 | yAxis: {},
140 | series: [
141 | {
142 | type: 'bar',
143 | values: data.map(day => day.total_active_users || 0)
144 | },
145 | {
146 | type: 'bar',
147 | values: data.map(day => day.total_engaged_users || 0)
148 | },
149 | ],
150 | legend: ['Active', 'Engaged']
151 | }))
152 | .addHeading('Daily Engaged Users by Product', 3)
153 | .addRaw(createXYChart({
154 | xAxis: {
155 | categories: DEFAULT_CHART_CONFIGS.dailyCategories(data)
156 | },
157 | yAxis: {},
158 | series: [
159 | {
160 | type: 'line',
161 | values: data.map(day => sumNestedValue([day], ['copilot_ide_code_completions', 'total_engaged_users']))
162 | },
163 | {
164 | type: 'line',
165 | values: data.map(day => sumNestedValue([day], ['copilot_ide_chat', 'total_engaged_users']))
166 | },
167 | {
168 | type: 'line',
169 | values: data.map(day => sumNestedValue([day], ['copilot_dotcom_chat', 'total_engaged_users']))
170 | },
171 | {
172 | type: 'line',
173 | values: data.map(day => sumNestedValue([day], ['copilot_dotcom_pull_requests', 'total_engaged_users']))
174 | }
175 | ],
176 | legend: ['IDE Code Completions', 'IDE Chat', 'Dotcom Chat', 'Dotcom Pull Requests']
177 | }))
178 | .addHeading('IDE Completion', 2)
179 | .addHeading('Suggestions vs. Acceptances', 3)
180 | .addRaw(createXYChart({
181 | xAxis: { categories: DEFAULT_CHART_CONFIGS.dailyCategories(data) },
182 | yAxis: {},
183 | series: [
184 | {
185 | type: 'bar',
186 | values: dailyTotals.map(day => day.total_code_suggestions || 0)
187 | },
188 | {
189 | type: 'bar',
190 | values: dailyTotals.map(day => day.total_code_acceptances || 0)
191 | },
192 | ],
193 | legend: ['Suggestions', 'Acceptances']
194 | }))
195 | .addHeading('Lines Suggested vs. Accepted', 3)
196 | .addRaw(createXYChart({
197 | xAxis: { categories: DEFAULT_CHART_CONFIGS.dailyCategories(data) },
198 | yAxis: {},
199 | series: [
200 | {
201 | type: 'bar',
202 | values: dailyTotals.map(day => day.total_code_lines_suggested || 0)
203 | },
204 | {
205 | type: 'bar',
206 | values: dailyTotals.map(day => day.total_code_lines_accepted || 0)
207 | },
208 | ],
209 | legend: ['Lines Suggested', 'Lines Accepted']
210 | }))
211 | .addHeading('Acceptance Rate', 3)
212 | .addRaw(createXYChart({
213 | xAxis: {
214 | categories: DEFAULT_CHART_CONFIGS.dailyCategories(data)
215 | },
216 | yAxis: {
217 | min: 0,
218 | max: 100
219 | },
220 | series: [
221 | {
222 | type: 'line',
223 | values: data.map(day => {
224 | const acceptances = sumNestedValue([day], ['copilot_ide_code_completions', 'editors', 'models', 'languages', 'total_code_acceptances']);
225 | const suggestions = sumNestedValue([day], ['copilot_ide_code_completions', 'editors', 'models', 'languages', 'total_code_suggestions']);
226 | return suggestions > 0 ? Math.round((acceptances / suggestions) * 100) : 0;
227 | })
228 | }
229 | ]
230 | }))
231 | .addHeading('Acceptance Rate by Language', 3)
232 | .addRaw(createXYChart({
233 | xAxis: {
234 | categories: DEFAULT_CHART_CONFIGS.dailyCategories(data)
235 | },
236 | yAxis: {
237 | min: 0,
238 | max: 100
239 | },
240 | series: topLanguages.map(language => ({
241 | type: 'line',
242 | values: data.map(day => {
243 | let acceptances = 0;
244 | let suggestions = 0;
245 | day.copilot_ide_code_completions?.editors?.forEach(editor => {
246 | editor.models?.forEach(model => {
247 | model.languages?.forEach(lang => {
248 | if (lang.name === language) {
249 | acceptances += lang.total_code_acceptances || 0;
250 | suggestions += lang.total_code_suggestions || 0;
251 | }
252 | });
253 | });
254 | });
255 | return suggestions > 0 ? Math.round((acceptances / suggestions) * 100) : 0;
256 | })
257 | })),
258 | legend: topLanguages
259 | }))
260 | .addHeading('Language Usage by Engaged Users', 3)
261 | .addRaw(createPieChart(Object.fromEntries(Object.entries(languageMetrics)
262 | .map(([lang, metrics]) => [lang, metrics.total_engaged_users]))))
263 | .addHeading('Editor Usage by Engaged Users', 3)
264 | .addRaw(createPieChart(Object.fromEntries(Object.entries(editorMetrics)
265 | .map(([editor, metrics]) => [editor, metrics.total_engaged_users]))))
266 | .addHeading('IDE Copilot Chat', 2)
267 | .addRaw(createXYChart({
268 | xAxis: {
269 | categories: DEFAULT_CHART_CONFIGS.dailyCategories(data)
270 | },
271 | yAxis: {},
272 | series: [
273 | {
274 | type: 'bar',
275 | values: dailyTotals.map(day => day.total_chats || 0)
276 | },
277 | {
278 | type: 'line',
279 | values: dailyTotals.map(day => day.total_chat_copy_events || 0)
280 | },
281 | {
282 | type: 'line',
283 | values: dailyTotals.map(day => day.total_chat_insert_events || 0)
284 | },
285 | ],
286 | legend: ['Total Chats', 'Copy Events', 'Insert Events']
287 | }))
288 | .addHeading('Copilot .COM Chat', 2)
289 | .addHeading('Total Chats', 3)
290 | .addRaw(createXYChart({
291 | xAxis: {
292 | categories: DEFAULT_CHART_CONFIGS.dailyCategories(data)
293 | },
294 | yAxis: {},
295 | series: [
296 | {
297 | type: 'bar',
298 | values: dailyTotals.map(day => day.total_dotcom_chat_chats || 0)
299 | }
300 | ],
301 | legend: ['Total Chats']
302 | }))
303 | .addHeading('Copilot .COM Pull Request', 2)
304 | .addHeading('Summaries Created', 3)
305 | .addRaw(createXYChart({
306 | xAxis: {
307 | categories: DEFAULT_CHART_CONFIGS.dailyCategories(data)
308 | },
309 | yAxis: {},
310 | series: [
311 | {
312 | type: 'bar',
313 | values: dailyTotals.map(day => day.total_dotcom_pr_summaries_created || 0)
314 | }
315 | ],
316 | legend: ['Total PR Summaries Created']
317 | }));
318 | };
319 | export const createJobSummaryCopilotDetails = (orgCopilotDetails) => {
320 | return summary
321 | .addHeading('Seat Info')
322 | .addHeading('Organization Copilot Details', 3)
323 | .addTable([
324 | ['Plan Type', orgCopilotDetails.plan_type?.toLocaleUpperCase() || 'Unknown'],
325 | ['Seat Management Setting', {
326 | 'assign_all': 'Assign All',
327 | 'assign_selected': 'Assign Selected',
328 | 'disabled': 'Disabled',
329 | 'unconfigured': 'Unconfigured',
330 | }[orgCopilotDetails.seat_management_setting] || 'Unknown'],
331 | ])
332 | .addHeading('Seat Breakdown', 3)
333 | .addTable([
334 | ['Total Seats', (orgCopilotDetails.seat_breakdown.total || 0).toString()],
335 | ['Added this cycle', (orgCopilotDetails.seat_breakdown.added_this_cycle || 0).toString()],
336 | ['Pending invites', (orgCopilotDetails.seat_breakdown.pending_invitation || 0).toString()],
337 | ['Pending cancellations', (orgCopilotDetails.seat_breakdown.pending_cancellation || 0).toString()],
338 | ['Active this cycle', (orgCopilotDetails.seat_breakdown.active_this_cycle || 0).toString()],
339 | ['Inactive this cycle', (orgCopilotDetails.seat_breakdown.inactive_this_cycle || 0).toString()]
340 | ])
341 | .addHeading('Policies', 3)
342 | .addTable([
343 | ['Public Code Suggestions Enabled', {
344 | 'allow': 'Allowed',
345 | 'block': 'Blocked',
346 | 'unconfigured': 'Unconfigured',
347 | }[orgCopilotDetails.public_code_suggestions] || 'Unknown'],
348 | ['IDE Chat Enabled', orgCopilotDetails.ide_chat?.toLocaleUpperCase() || 'Unknown'],
349 | ['Platform Chat Enabled', orgCopilotDetails.platform_chat?.toLocaleUpperCase() || 'Unknown'],
350 | ['CLI Enabled', orgCopilotDetails.cli?.toLocaleUpperCase() || 'Unknown'],
351 | ]);
352 | };
353 | export const createJobSummarySeatAssignments = (data) => {
354 | if (!data)
355 | data = [];
356 | return summary
357 | .addHeading('Seat Assignments')
358 | .addTable([
359 | [
360 | { data: 'Avatar', header: true },
361 | { data: 'Login', header: true },
362 | { data: `Last Activity (${process.env.TZ || 'UTC'})`, header: true },
363 | { data: 'Last Editor Used', header: true },
364 | { data: 'Created At', header: true },
365 | { data: 'Pending Cancellation Date', header: true },
366 | { data: 'Team', header: true },
367 | ],
368 | ...data.map(seat => [
369 | `
`,
370 | seat.assignee?.login,
371 | seat.last_activity_at ? dateFormat(seat.last_activity_at, { month: 'numeric', day: 'numeric', year: 'numeric', hour: 'numeric', minute: 'numeric' }) : 'No Activity',
372 | seat.last_activity_editor || 'N/A',
373 | dateFormat(seat.created_at),
374 | dateFormat(seat.pending_cancellation_date || ''),
375 | String(seat.assigning_team?.name || ' '),
376 | ])
377 | ]);
378 | };
379 | export const setJobSummaryTimeZone = (timeZone) => process.env.TZ = timeZone;
380 | //# sourceMappingURL=job-summary.js.map
--------------------------------------------------------------------------------
/dist/job-summary.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"job-summary.js","sourceRoot":"","sources":["../src/job-summary.ts"],"names":[],"mappings":"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"}
--------------------------------------------------------------------------------
/dist/mermaid.js:
--------------------------------------------------------------------------------
1 | import { dateFormat } from "./utility";
2 | export const createMermaidChart = (type, config, content) => {
3 | const chartConfig = `---
4 | config:
5 | ${type === 'xychart-beta' ? `xyChart:
6 | width: ${config.width || 900}
7 | height: ${config.height || 500}
8 | xAxis:
9 | labelPadding: 20
10 | yAxis:
11 | labelPadding: 20
12 | themeVariables:
13 | xyChart:
14 | backgroundColor: "transparent"` : ''}
15 | ---
16 | ${type}
17 | ${config.title ? ` title "${config.title}"` : ''}
18 | ${content}`;
19 | return `\n\`\`\`mermaid\n${chartConfig}\n\`\`\`\n`;
20 | };
21 | export const createPieChart = (data, limit = 20) => {
22 | const content = Object.entries(data)
23 | .sort((a, b) => b[1] - a[1])
24 | .slice(0, limit)
25 | .map(([label, value]) => `"${label}" : ${value}`)
26 | .join('\n');
27 | return createMermaidChart('pie', {}, content);
28 | };
29 | export const createXYChart = (config) => {
30 | const { min, max } = config.series.reduce((acc, series) => {
31 | series.values.forEach(value => {
32 | if (value < acc.min || acc.min === undefined)
33 | acc.min = value;
34 | if (value > acc.max || acc.max === undefined)
35 | acc.max = value;
36 | });
37 | return acc;
38 | }, { min: config.yAxis.min || 0, max: config.yAxis.max || 100 });
39 | return createMermaidChart('xychart-beta', config, ` x-axis ${config.xAxis.title ? "\"" + config.xAxis.title + "\"" : ''} [${config.xAxis.categories.join(', ')}]\n` +
40 | ` y-axis ${config.yAxis.title ? "\"" + config.yAxis.title + "\"" : ''} ${min || 0} --> ${max || 100}\n` +
41 | config.series.map(series => {
42 | return `${series.type} ${series.category ? "\"" + series.category + "\"" : ''} [${series.values.join(', ')}]`;
43 | }).join('\n')) + (config.legend ? `\n${generateLegend(config.legend)}` : '');
44 | };
45 | export function generateLegend(categories) {
46 | const colors = ["#3498db", "#2ecc71", "#e74c3c", "#f1c40f", "#bdc3c7", "#ffffff", "#34495e", "#9b59b6", "#1abc9c", "#e67e22"];
47 | return categories.map((category, i) => `}/${colors[i % colors.length]
48 | .replace('#', '')}.png) ${category}`)
49 | .join(' ');
50 | }
51 | const DEFAULT_CHART_HEIGHT = 400;
52 | export const DEFAULT_CHART_CONFIGS = {
53 | standardHeight: { height: DEFAULT_CHART_HEIGHT },
54 | dailyCategories: (data) => data.map(day => dateFormat(day.date, { day: 'numeric' })),
55 | };
56 | //# sourceMappingURL=mermaid.js.map
--------------------------------------------------------------------------------
/dist/mermaid.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"mermaid.js","sourceRoot":"","sources":["../src/mermaid.ts"],"names":[],"mappings":"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"}
--------------------------------------------------------------------------------
/dist/run.js:
--------------------------------------------------------------------------------
1 | import { debug, getBooleanInput, getInput, info, setOutput, summary, warning } from "@actions/core";
2 | import { Octokit } from '@octokit/rest';
3 | import { DefaultArtifactClient } from "@actions/artifact";
4 | import { writeFileSync } from "fs";
5 | import { json2csv } from "json-2-csv";
6 | import { toXML } from 'jstoxml';
7 | import { createJobSummaryCopilotDetails, createJobSummarySeatAssignments, createJobSummaryUsage, setJobSummaryTimeZone } from "./job-summary";
8 | const getInputs = () => {
9 | const result = {};
10 | result.token = getInput("github-token").trim();
11 | result.organization = getInput("organization").trim();
12 | result.team = getInput("team").trim();
13 | result.jobSummary = getBooleanInput("job-summary");
14 | result.days = parseInt(getInput("days"));
15 | result.since = getInput("since");
16 | result.until = getInput("until");
17 | result.json = getBooleanInput("json");
18 | result.csv = getBooleanInput("csv");
19 | result.csvOptions = getInput("csv-options") ? JSON.parse(getInput("csv-options")) : undefined;
20 | result.xml = getBooleanInput("xml");
21 | result.xmlOptions = getInput("xml-options") ? JSON.parse(getInput("xml-options")) : {
22 | header: true,
23 | indent: " ",
24 | };
25 | result.timeZone = getInput("time-zone");
26 | result.artifactName = getInput("artifact-name");
27 | if (!result.token) {
28 | throw new Error("github-token is required");
29 | }
30 | return result;
31 | };
32 | const run = async () => {
33 | const input = getInputs();
34 | const octokit = new Octokit({
35 | auth: input.token
36 | });
37 | const params = {};
38 | if (input.days) {
39 | params.since = new Date(new Date().setDate(new Date().getDate() - input.days)).toISOString().split('T')[0];
40 | }
41 | else if (input.since || input.until) {
42 | if (input.since)
43 | params.since = input.since;
44 | if (input.until)
45 | params.until = input.until;
46 | }
47 | let req;
48 | if (input.team) {
49 | if (!input.organization) {
50 | throw new Error("organization is required when team is provided");
51 | }
52 | info(`Fetching Copilot usage for team ${input.team} inside organization ${input.organization}`);
53 | req = octokit.rest.copilot.copilotMetricsForTeam({
54 | org: input.organization,
55 | team_slug: input.team,
56 | ...params
57 | }).then(response => response.data);
58 | }
59 | else if (input.organization) {
60 | info(`Fetching Copilot usage for organization ${input.organization}`);
61 | req = octokit.rest.copilot.copilotMetricsForOrganization({
62 | org: input.organization,
63 | ...params
64 | }).then(response => response.data);
65 | }
66 | else {
67 | throw new Error("organization, enterprise or team input is required");
68 | }
69 | const data = await req;
70 | if (!data || data.length === 0) {
71 | return warning("No Copilot usage data found");
72 | }
73 | debug(JSON.stringify(data, null, 2));
74 | info(`Fetched Copilot usage data for ${data.length} days (${data[0].date} to ${data[data.length - 1].date})`);
75 | if (input.jobSummary) {
76 | setJobSummaryTimeZone(input.timeZone);
77 | const name = (input.team && input.organization) ? `${input.organization} / ${input.team}` : input.organization;
78 | await createJobSummaryUsage(data, name).write();
79 | if (input.organization && !input.team) {
80 | info(`Fetching Copilot details for organization ${input.organization}`);
81 | const orgCopilotDetails = await octokit.rest.copilot.getCopilotOrganizationDetails({
82 | org: input.organization
83 | }).then(response => response.data);
84 | if (orgCopilotDetails) {
85 | await createJobSummaryCopilotDetails(orgCopilotDetails).write();
86 | }
87 | setOutput("result-org-details", JSON.stringify(orgCopilotDetails));
88 | info(`Fetching Copilot seat assignments for organization ${input.organization}`);
89 | const orgSeatAssignments = await octokit.paginate(octokit.rest.copilot.listCopilotSeats, {
90 | org: input.organization
91 | });
92 | const _orgSeatAssignments = {
93 | total_seats: orgSeatAssignments[0]?.total_seats || 0,
94 | seats: (orgSeatAssignments).reduce((acc, rsp) => acc.concat(rsp.seats), [])
95 | };
96 | if (_orgSeatAssignments.total_seats > 0 && _orgSeatAssignments?.seats) {
97 | _orgSeatAssignments.seats = _orgSeatAssignments.seats.sort((a, b) => new Date(b.last_activity_at).getTime() - new Date(a.last_activity_at).getTime());
98 | await createJobSummarySeatAssignments(_orgSeatAssignments?.seats)?.write();
99 | }
100 | setOutput("result-seats", JSON.stringify(_orgSeatAssignments));
101 | }
102 | if (input.organization) {
103 | await summary.addLink(`Manage Access for ${input.organization}`, `https://github.com/organizations/${input.organization}/settings/copilot/seat_management`)
104 | .write();
105 | }
106 | }
107 | if (input.csv || input.xml || input.json) {
108 | const artifact = new DefaultArtifactClient();
109 | const files = [];
110 | if (input.json) {
111 | writeFileSync('copilot-usage.json', JSON.stringify(data, null, 2));
112 | files.push('copilot-usage.json');
113 | }
114 | if (input.csv) {
115 | writeFileSync('copilot-usage.csv', await json2csv(data, input.csvOptions));
116 | files.push('copilot-usage.csv');
117 | }
118 | if (input.xml) {
119 | writeFileSync('copilot-usage.xml', await toXML(data, input.xmlOptions));
120 | files.push('copilot-usage.xml');
121 | }
122 | await artifact.uploadArtifact(input.artifactName, files, '.');
123 | }
124 | setOutput("result", JSON.stringify(data));
125 | setOutput("since", data[0].date);
126 | setOutput("until", data[data.length - 1].date);
127 | setOutput("days", data.length.toString());
128 | };
129 | export default run;
130 | //# sourceMappingURL=run.js.map
--------------------------------------------------------------------------------
/dist/run.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"run.js","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"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"}
--------------------------------------------------------------------------------
/dist/sourcemap-register.js:
--------------------------------------------------------------------------------
1 | (()=>{var e={296:e=>{var r=Object.prototype.toString;var n=typeof Buffer!=="undefined"&&typeof Buffer.alloc==="function"&&typeof Buffer.allocUnsafe==="function"&&typeof Buffer.from==="function";function isArrayBuffer(e){return r.call(e).slice(8,-1)==="ArrayBuffer"}function fromArrayBuffer(e,r,t){r>>>=0;var o=e.byteLength-r;if(o<0){throw new RangeError("'offset' is out of bounds")}if(t===undefined){t=o}else{t>>>=0;if(t>o){throw new RangeError("'length' is out of bounds")}}return n?Buffer.from(e.slice(r,r+t)):new Buffer(new Uint8Array(e.slice(r,r+t)))}function fromString(e,r){if(typeof r!=="string"||r===""){r="utf8"}if(!Buffer.isEncoding(r)){throw new TypeError('"encoding" must be a valid string encoding')}return n?Buffer.from(e,r):new Buffer(e,r)}function bufferFrom(e,r,t){if(typeof e==="number"){throw new TypeError('"value" argument must not be a number')}if(isArrayBuffer(e)){return fromArrayBuffer(e,r,t)}if(typeof e==="string"){return fromString(e,r)}return n?Buffer.from(e):new Buffer(e)}e.exports=bufferFrom},599:(e,r,n)=>{e=n.nmd(e);var t=n(927).SourceMapConsumer;var o=n(928);var i;try{i=n(896);if(!i.existsSync||!i.readFileSync){i=null}}catch(e){}var a=n(296);function dynamicRequire(e,r){return e.require(r)}var u=false;var s=false;var l=false;var c="auto";var p={};var f={};var g=/^data:application\/json[^,]+base64,/;var d=[];var h=[];function isInBrowser(){if(c==="browser")return true;if(c==="node")return false;return typeof window!=="undefined"&&typeof XMLHttpRequest==="function"&&!(window.require&&window.module&&window.process&&window.process.type==="renderer")}function hasGlobalProcessEventEmitter(){return typeof process==="object"&&process!==null&&typeof process.on==="function"}function globalProcessVersion(){if(typeof process==="object"&&process!==null){return process.version}else{return""}}function globalProcessStderr(){if(typeof process==="object"&&process!==null){return process.stderr}}function globalProcessExit(e){if(typeof process==="object"&&process!==null&&typeof process.exit==="function"){return process.exit(e)}}function handlerExec(e){return function(r){for(var n=0;n"}var n=this.getLineNumber();if(n!=null){r+=":"+n;var t=this.getColumnNumber();if(t){r+=":"+t}}}var o="";var i=this.getFunctionName();var a=true;var u=this.isConstructor();var s=!(this.isToplevel()||u);if(s){var l=this.getTypeName();if(l==="[object Object]"){l="null"}var c=this.getMethodName();if(i){if(l&&i.indexOf(l)!=0){o+=l+"."}o+=i;if(c&&i.indexOf("."+c)!=i.length-c.length-1){o+=" [as "+c+"]"}}else{o+=l+"."+(c||"")}}else if(u){o+="new "+(i||"")}else if(i){o+=i}else{o+=r;a=false}if(a){o+=" ("+r+")"}return o}function cloneCallSite(e){var r={};Object.getOwnPropertyNames(Object.getPrototypeOf(e)).forEach((function(n){r[n]=/^(?:is|get)/.test(n)?function(){return e[n].call(e)}:e[n]}));r.toString=CallSiteToString;return r}function wrapCallSite(e,r){if(r===undefined){r={nextPosition:null,curPosition:null}}if(e.isNative()){r.curPosition=null;return e}var n=e.getFileName()||e.getScriptNameOrSourceURL();if(n){var t=e.getLineNumber();var o=e.getColumnNumber()-1;var i=/^v(10\.1[6-9]|10\.[2-9][0-9]|10\.[0-9]{3,}|1[2-9]\d*|[2-9]\d|\d{3,}|11\.11)/;var a=i.test(globalProcessVersion())?0:62;if(t===1&&o>a&&!isInBrowser()&&!e.isEval()){o-=a}var u=mapSourcePosition({source:n,line:t,column:o});r.curPosition=u;e=cloneCallSite(e);var s=e.getFunctionName;e.getFunctionName=function(){if(r.nextPosition==null){return s()}return r.nextPosition.name||s()};e.getFileName=function(){return u.source};e.getLineNumber=function(){return u.line};e.getColumnNumber=function(){return u.column+1};e.getScriptNameOrSourceURL=function(){return u.source};return e}var l=e.isEval()&&e.getEvalOrigin();if(l){l=mapEvalOrigin(l);e=cloneCallSite(e);e.getEvalOrigin=function(){return l};return e}return e}function prepareStackTrace(e,r){if(l){p={};f={}}var n=e.name||"Error";var t=e.message||"";var o=n+": "+t;var i={nextPosition:null,curPosition:null};var a=[];for(var u=r.length-1;u>=0;u--){a.push("\n at "+wrapCallSite(r[u],i));i.nextPosition=i.curPosition}i.curPosition=i.nextPosition=null;return o+a.reverse().join("")}function getErrorSource(e){var r=/\n at [^(]+ \((.*):(\d+):(\d+)\)/.exec(e.stack);if(r){var n=r[1];var t=+r[2];var o=+r[3];var a=p[n];if(!a&&i&&i.existsSync(n)){try{a=i.readFileSync(n,"utf8")}catch(e){a=""}}if(a){var u=a.split(/(?:\r\n|\r|\n)/)[t-1];if(u){return n+":"+t+"\n"+u+"\n"+new Array(o).join(" ")+"^"}}}return null}function printErrorAndExit(e){var r=getErrorSource(e);var n=globalProcessStderr();if(n&&n._handle&&n._handle.setBlocking){n._handle.setBlocking(true)}if(r){console.error();console.error(r)}console.error(e.stack);globalProcessExit(1)}function shimEmitUncaughtException(){var e=process.emit;process.emit=function(r){if(r==="uncaughtException"){var n=arguments[1]&&arguments[1].stack;var t=this.listeners(r).length>0;if(n&&!t){return printErrorAndExit(arguments[1])}}return e.apply(this,arguments)}}var S=d.slice(0);var _=h.slice(0);r.wrapCallSite=wrapCallSite;r.getErrorSource=getErrorSource;r.mapSourcePosition=mapSourcePosition;r.retrieveSourceMap=v;r.install=function(r){r=r||{};if(r.environment){c=r.environment;if(["node","browser","auto"].indexOf(c)===-1){throw new Error("environment "+c+" was unknown. Available options are {auto, browser, node}")}}if(r.retrieveFile){if(r.overrideRetrieveFile){d.length=0}d.unshift(r.retrieveFile)}if(r.retrieveSourceMap){if(r.overrideRetrieveSourceMap){h.length=0}h.unshift(r.retrieveSourceMap)}if(r.hookRequire&&!isInBrowser()){var n=dynamicRequire(e,"module");var t=n.prototype._compile;if(!t.__sourceMapSupport){n.prototype._compile=function(e,r){p[r]=e;f[r]=undefined;return t.call(this,e,r)};n.prototype._compile.__sourceMapSupport=true}}if(!l){l="emptyCacheBetweenOperations"in r?r.emptyCacheBetweenOperations:false}if(!u){u=true;Error.prepareStackTrace=prepareStackTrace}if(!s){var o="handleUncaughtExceptions"in r?r.handleUncaughtExceptions:true;try{var i=dynamicRequire(e,"worker_threads");if(i.isMainThread===false){o=false}}catch(e){}if(o&&hasGlobalProcessEventEmitter()){s=true;shimEmitUncaughtException()}}};r.resetRetrieveHandlers=function(){d.length=0;h.length=0;d=S.slice(0);h=_.slice(0);v=handlerExec(h);m=handlerExec(d)}},517:(e,r,n)=>{var t=n(297);var o=Object.prototype.hasOwnProperty;var i=typeof Map!=="undefined";function ArraySet(){this._array=[];this._set=i?new Map:Object.create(null)}ArraySet.fromArray=function ArraySet_fromArray(e,r){var n=new ArraySet;for(var t=0,o=e.length;t=0){return r}}else{var n=t.toSetString(e);if(o.call(this._set,n)){return this._set[n]}}throw new Error('"'+e+'" is not in the set.')};ArraySet.prototype.at=function ArraySet_at(e){if(e>=0&&e{var t=n(158);var o=5;var i=1<>1;return r?-n:n}r.encode=function base64VLQ_encode(e){var r="";var n;var i=toVLQSigned(e);do{n=i&a;i>>>=o;if(i>0){n|=u}r+=t.encode(n)}while(i>0);return r};r.decode=function base64VLQ_decode(e,r,n){var i=e.length;var s=0;var l=0;var c,p;do{if(r>=i){throw new Error("Expected more digits in base 64 VLQ value.")}p=t.decode(e.charCodeAt(r++));if(p===-1){throw new Error("Invalid base64 digit: "+e.charAt(r-1))}c=!!(p&u);p&=a;s=s+(p<{var n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".split("");r.encode=function(e){if(0<=e&&e{r.GREATEST_LOWER_BOUND=1;r.LEAST_UPPER_BOUND=2;function recursiveSearch(e,n,t,o,i,a){var u=Math.floor((n-e)/2)+e;var s=i(t,o[u],true);if(s===0){return u}else if(s>0){if(n-u>1){return recursiveSearch(u,n,t,o,i,a)}if(a==r.LEAST_UPPER_BOUND){return n1){return recursiveSearch(e,u,t,o,i,a)}if(a==r.LEAST_UPPER_BOUND){return u}else{return e<0?-1:e}}}r.search=function search(e,n,t,o){if(n.length===0){return-1}var i=recursiveSearch(-1,n.length,e,n,t,o||r.GREATEST_LOWER_BOUND);if(i<0){return-1}while(i-1>=0){if(t(n[i],n[i-1],true)!==0){break}--i}return i}},24:(e,r,n)=>{var t=n(297);function generatedPositionAfter(e,r){var n=e.generatedLine;var o=r.generatedLine;var i=e.generatedColumn;var a=r.generatedColumn;return o>n||o==n&&a>=i||t.compareByGeneratedPositionsInflated(e,r)<=0}function MappingList(){this._array=[];this._sorted=true;this._last={generatedLine:-1,generatedColumn:0}}MappingList.prototype.unsortedForEach=function MappingList_forEach(e,r){this._array.forEach(e,r)};MappingList.prototype.add=function MappingList_add(e){if(generatedPositionAfter(this._last,e)){this._last=e;this._array.push(e)}else{this._sorted=false;this._array.push(e)}};MappingList.prototype.toArray=function MappingList_toArray(){if(!this._sorted){this._array.sort(t.compareByGeneratedPositionsInflated);this._sorted=true}return this._array};r.P=MappingList},299:(e,r)=>{function swap(e,r,n){var t=e[r];e[r]=e[n];e[n]=t}function randomIntInRange(e,r){return Math.round(e+Math.random()*(r-e))}function doQuickSort(e,r,n,t){if(n{var t;var o=n(297);var i=n(197);var a=n(517).C;var u=n(818);var s=n(299).g;function SourceMapConsumer(e,r){var n=e;if(typeof e==="string"){n=o.parseSourceMapInput(e)}return n.sections!=null?new IndexedSourceMapConsumer(n,r):new BasicSourceMapConsumer(n,r)}SourceMapConsumer.fromSourceMap=function(e,r){return BasicSourceMapConsumer.fromSourceMap(e,r)};SourceMapConsumer.prototype._version=3;SourceMapConsumer.prototype.__generatedMappings=null;Object.defineProperty(SourceMapConsumer.prototype,"_generatedMappings",{configurable:true,enumerable:true,get:function(){if(!this.__generatedMappings){this._parseMappings(this._mappings,this.sourceRoot)}return this.__generatedMappings}});SourceMapConsumer.prototype.__originalMappings=null;Object.defineProperty(SourceMapConsumer.prototype,"_originalMappings",{configurable:true,enumerable:true,get:function(){if(!this.__originalMappings){this._parseMappings(this._mappings,this.sourceRoot)}return this.__originalMappings}});SourceMapConsumer.prototype._charIsMappingSeparator=function SourceMapConsumer_charIsMappingSeparator(e,r){var n=e.charAt(r);return n===";"||n===","};SourceMapConsumer.prototype._parseMappings=function SourceMapConsumer_parseMappings(e,r){throw new Error("Subclasses must implement _parseMappings")};SourceMapConsumer.GENERATED_ORDER=1;SourceMapConsumer.ORIGINAL_ORDER=2;SourceMapConsumer.GREATEST_LOWER_BOUND=1;SourceMapConsumer.LEAST_UPPER_BOUND=2;SourceMapConsumer.prototype.eachMapping=function SourceMapConsumer_eachMapping(e,r,n){var t=r||null;var i=n||SourceMapConsumer.GENERATED_ORDER;var a;switch(i){case SourceMapConsumer.GENERATED_ORDER:a=this._generatedMappings;break;case SourceMapConsumer.ORIGINAL_ORDER:a=this._originalMappings;break;default:throw new Error("Unknown order of iteration.")}var u=this.sourceRoot;a.map((function(e){var r=e.source===null?null:this._sources.at(e.source);r=o.computeSourceURL(u,r,this._sourceMapURL);return{source:r,generatedLine:e.generatedLine,generatedColumn:e.generatedColumn,originalLine:e.originalLine,originalColumn:e.originalColumn,name:e.name===null?null:this._names.at(e.name)}}),this).forEach(e,t)};SourceMapConsumer.prototype.allGeneratedPositionsFor=function SourceMapConsumer_allGeneratedPositionsFor(e){var r=o.getArg(e,"line");var n={source:o.getArg(e,"source"),originalLine:r,originalColumn:o.getArg(e,"column",0)};n.source=this._findSourceIndex(n.source);if(n.source<0){return[]}var t=[];var a=this._findMapping(n,this._originalMappings,"originalLine","originalColumn",o.compareByOriginalPositions,i.LEAST_UPPER_BOUND);if(a>=0){var u=this._originalMappings[a];if(e.column===undefined){var s=u.originalLine;while(u&&u.originalLine===s){t.push({line:o.getArg(u,"generatedLine",null),column:o.getArg(u,"generatedColumn",null),lastColumn:o.getArg(u,"lastGeneratedColumn",null)});u=this._originalMappings[++a]}}else{var l=u.originalColumn;while(u&&u.originalLine===r&&u.originalColumn==l){t.push({line:o.getArg(u,"generatedLine",null),column:o.getArg(u,"generatedColumn",null),lastColumn:o.getArg(u,"lastGeneratedColumn",null)});u=this._originalMappings[++a]}}}return t};r.SourceMapConsumer=SourceMapConsumer;function BasicSourceMapConsumer(e,r){var n=e;if(typeof e==="string"){n=o.parseSourceMapInput(e)}var t=o.getArg(n,"version");var i=o.getArg(n,"sources");var u=o.getArg(n,"names",[]);var s=o.getArg(n,"sourceRoot",null);var l=o.getArg(n,"sourcesContent",null);var c=o.getArg(n,"mappings");var p=o.getArg(n,"file",null);if(t!=this._version){throw new Error("Unsupported version: "+t)}if(s){s=o.normalize(s)}i=i.map(String).map(o.normalize).map((function(e){return s&&o.isAbsolute(s)&&o.isAbsolute(e)?o.relative(s,e):e}));this._names=a.fromArray(u.map(String),true);this._sources=a.fromArray(i,true);this._absoluteSources=this._sources.toArray().map((function(e){return o.computeSourceURL(s,e,r)}));this.sourceRoot=s;this.sourcesContent=l;this._mappings=c;this._sourceMapURL=r;this.file=p}BasicSourceMapConsumer.prototype=Object.create(SourceMapConsumer.prototype);BasicSourceMapConsumer.prototype.consumer=SourceMapConsumer;BasicSourceMapConsumer.prototype._findSourceIndex=function(e){var r=e;if(this.sourceRoot!=null){r=o.relative(this.sourceRoot,r)}if(this._sources.has(r)){return this._sources.indexOf(r)}var n;for(n=0;n1){v.source=l+_[1];l+=_[1];v.originalLine=i+_[2];i=v.originalLine;v.originalLine+=1;v.originalColumn=a+_[3];a=v.originalColumn;if(_.length>4){v.name=c+_[4];c+=_[4]}}m.push(v);if(typeof v.originalLine==="number"){h.push(v)}}}s(m,o.compareByGeneratedPositionsDeflated);this.__generatedMappings=m;s(h,o.compareByOriginalPositions);this.__originalMappings=h};BasicSourceMapConsumer.prototype._findMapping=function SourceMapConsumer_findMapping(e,r,n,t,o,a){if(e[n]<=0){throw new TypeError("Line must be greater than or equal to 1, got "+e[n])}if(e[t]<0){throw new TypeError("Column must be greater than or equal to 0, got "+e[t])}return i.search(e,r,o,a)};BasicSourceMapConsumer.prototype.computeColumnSpans=function SourceMapConsumer_computeColumnSpans(){for(var e=0;e=0){var t=this._generatedMappings[n];if(t.generatedLine===r.generatedLine){var i=o.getArg(t,"source",null);if(i!==null){i=this._sources.at(i);i=o.computeSourceURL(this.sourceRoot,i,this._sourceMapURL)}var a=o.getArg(t,"name",null);if(a!==null){a=this._names.at(a)}return{source:i,line:o.getArg(t,"originalLine",null),column:o.getArg(t,"originalColumn",null),name:a}}}return{source:null,line:null,column:null,name:null}};BasicSourceMapConsumer.prototype.hasContentsOfAllSources=function BasicSourceMapConsumer_hasContentsOfAllSources(){if(!this.sourcesContent){return false}return this.sourcesContent.length>=this._sources.size()&&!this.sourcesContent.some((function(e){return e==null}))};BasicSourceMapConsumer.prototype.sourceContentFor=function SourceMapConsumer_sourceContentFor(e,r){if(!this.sourcesContent){return null}var n=this._findSourceIndex(e);if(n>=0){return this.sourcesContent[n]}var t=e;if(this.sourceRoot!=null){t=o.relative(this.sourceRoot,t)}var i;if(this.sourceRoot!=null&&(i=o.urlParse(this.sourceRoot))){var a=t.replace(/^file:\/\//,"");if(i.scheme=="file"&&this._sources.has(a)){return this.sourcesContent[this._sources.indexOf(a)]}if((!i.path||i.path=="/")&&this._sources.has("/"+t)){return this.sourcesContent[this._sources.indexOf("/"+t)]}}if(r){return null}else{throw new Error('"'+t+'" is not in the SourceMap.')}};BasicSourceMapConsumer.prototype.generatedPositionFor=function SourceMapConsumer_generatedPositionFor(e){var r=o.getArg(e,"source");r=this._findSourceIndex(r);if(r<0){return{line:null,column:null,lastColumn:null}}var n={source:r,originalLine:o.getArg(e,"line"),originalColumn:o.getArg(e,"column")};var t=this._findMapping(n,this._originalMappings,"originalLine","originalColumn",o.compareByOriginalPositions,o.getArg(e,"bias",SourceMapConsumer.GREATEST_LOWER_BOUND));if(t>=0){var i=this._originalMappings[t];if(i.source===n.source){return{line:o.getArg(i,"generatedLine",null),column:o.getArg(i,"generatedColumn",null),lastColumn:o.getArg(i,"lastGeneratedColumn",null)}}}return{line:null,column:null,lastColumn:null}};t=BasicSourceMapConsumer;function IndexedSourceMapConsumer(e,r){var n=e;if(typeof e==="string"){n=o.parseSourceMapInput(e)}var t=o.getArg(n,"version");var i=o.getArg(n,"sections");if(t!=this._version){throw new Error("Unsupported version: "+t)}this._sources=new a;this._names=new a;var u={line:-1,column:0};this._sections=i.map((function(e){if(e.url){throw new Error("Support for url field in sections not implemented.")}var n=o.getArg(e,"offset");var t=o.getArg(n,"line");var i=o.getArg(n,"column");if(t{var t=n(818);var o=n(297);var i=n(517).C;var a=n(24).P;function SourceMapGenerator(e){if(!e){e={}}this._file=o.getArg(e,"file",null);this._sourceRoot=o.getArg(e,"sourceRoot",null);this._skipValidation=o.getArg(e,"skipValidation",false);this._sources=new i;this._names=new i;this._mappings=new a;this._sourcesContents=null}SourceMapGenerator.prototype._version=3;SourceMapGenerator.fromSourceMap=function SourceMapGenerator_fromSourceMap(e){var r=e.sourceRoot;var n=new SourceMapGenerator({file:e.file,sourceRoot:r});e.eachMapping((function(e){var t={generated:{line:e.generatedLine,column:e.generatedColumn}};if(e.source!=null){t.source=e.source;if(r!=null){t.source=o.relative(r,t.source)}t.original={line:e.originalLine,column:e.originalColumn};if(e.name!=null){t.name=e.name}}n.addMapping(t)}));e.sources.forEach((function(t){var i=t;if(r!==null){i=o.relative(r,t)}if(!n._sources.has(i)){n._sources.add(i)}var a=e.sourceContentFor(t);if(a!=null){n.setSourceContent(t,a)}}));return n};SourceMapGenerator.prototype.addMapping=function SourceMapGenerator_addMapping(e){var r=o.getArg(e,"generated");var n=o.getArg(e,"original",null);var t=o.getArg(e,"source",null);var i=o.getArg(e,"name",null);if(!this._skipValidation){this._validateMapping(r,n,t,i)}if(t!=null){t=String(t);if(!this._sources.has(t)){this._sources.add(t)}}if(i!=null){i=String(i);if(!this._names.has(i)){this._names.add(i)}}this._mappings.add({generatedLine:r.line,generatedColumn:r.column,originalLine:n!=null&&n.line,originalColumn:n!=null&&n.column,source:t,name:i})};SourceMapGenerator.prototype.setSourceContent=function SourceMapGenerator_setSourceContent(e,r){var n=e;if(this._sourceRoot!=null){n=o.relative(this._sourceRoot,n)}if(r!=null){if(!this._sourcesContents){this._sourcesContents=Object.create(null)}this._sourcesContents[o.toSetString(n)]=r}else if(this._sourcesContents){delete this._sourcesContents[o.toSetString(n)];if(Object.keys(this._sourcesContents).length===0){this._sourcesContents=null}}};SourceMapGenerator.prototype.applySourceMap=function SourceMapGenerator_applySourceMap(e,r,n){var t=r;if(r==null){if(e.file==null){throw new Error("SourceMapGenerator.prototype.applySourceMap requires either an explicit source file, "+'or the source map\'s "file" property. Both were omitted.')}t=e.file}var a=this._sourceRoot;if(a!=null){t=o.relative(a,t)}var u=new i;var s=new i;this._mappings.unsortedForEach((function(r){if(r.source===t&&r.originalLine!=null){var i=e.originalPositionFor({line:r.originalLine,column:r.originalColumn});if(i.source!=null){r.source=i.source;if(n!=null){r.source=o.join(n,r.source)}if(a!=null){r.source=o.relative(a,r.source)}r.originalLine=i.line;r.originalColumn=i.column;if(i.name!=null){r.name=i.name}}}var l=r.source;if(l!=null&&!u.has(l)){u.add(l)}var c=r.name;if(c!=null&&!s.has(c)){s.add(c)}}),this);this._sources=u;this._names=s;e.sources.forEach((function(r){var t=e.sourceContentFor(r);if(t!=null){if(n!=null){r=o.join(n,r)}if(a!=null){r=o.relative(a,r)}this.setSourceContent(r,t)}}),this)};SourceMapGenerator.prototype._validateMapping=function SourceMapGenerator_validateMapping(e,r,n,t){if(r&&typeof r.line!=="number"&&typeof r.column!=="number"){throw new Error("original.line and original.column are not numbers -- you probably meant to omit "+"the original mapping entirely and only map the generated position. If so, pass "+"null for the original mapping instead of an object with empty or null values.")}if(e&&"line"in e&&"column"in e&&e.line>0&&e.column>=0&&!r&&!n&&!t){return}else if(e&&"line"in e&&"column"in e&&r&&"line"in r&&"column"in r&&e.line>0&&e.column>=0&&r.line>0&&r.column>=0&&n){return}else{throw new Error("Invalid mapping: "+JSON.stringify({generated:e,source:n,original:r,name:t}))}};SourceMapGenerator.prototype._serializeMappings=function SourceMapGenerator_serializeMappings(){var e=0;var r=1;var n=0;var i=0;var a=0;var u=0;var s="";var l;var c;var p;var f;var g=this._mappings.toArray();for(var d=0,h=g.length;d0){if(!o.compareByGeneratedPositionsInflated(c,g[d-1])){continue}l+=","}}l+=t.encode(c.generatedColumn-e);e=c.generatedColumn;if(c.source!=null){f=this._sources.indexOf(c.source);l+=t.encode(f-u);u=f;l+=t.encode(c.originalLine-1-i);i=c.originalLine-1;l+=t.encode(c.originalColumn-n);n=c.originalColumn;if(c.name!=null){p=this._names.indexOf(c.name);l+=t.encode(p-a);a=p}}s+=l}return s};SourceMapGenerator.prototype._generateSourcesContent=function SourceMapGenerator_generateSourcesContent(e,r){return e.map((function(e){if(!this._sourcesContents){return null}if(r!=null){e=o.relative(r,e)}var n=o.toSetString(e);return Object.prototype.hasOwnProperty.call(this._sourcesContents,n)?this._sourcesContents[n]:null}),this)};SourceMapGenerator.prototype.toJSON=function SourceMapGenerator_toJSON(){var e={version:this._version,sources:this._sources.toArray(),names:this._names.toArray(),mappings:this._serializeMappings()};if(this._file!=null){e.file=this._file}if(this._sourceRoot!=null){e.sourceRoot=this._sourceRoot}if(this._sourcesContents){e.sourcesContent=this._generateSourcesContent(e.sources,e.sourceRoot)}return e};SourceMapGenerator.prototype.toString=function SourceMapGenerator_toString(){return JSON.stringify(this.toJSON())};r.x=SourceMapGenerator},565:(e,r,n)=>{var t;var o=n(163).x;var i=n(297);var a=/(\r?\n)/;var u=10;var s="$$$isSourceNode$$$";function SourceNode(e,r,n,t,o){this.children=[];this.sourceContents={};this.line=e==null?null:e;this.column=r==null?null:r;this.source=n==null?null:n;this.name=o==null?null:o;this[s]=true;if(t!=null)this.add(t)}SourceNode.fromStringWithSourceMap=function SourceNode_fromStringWithSourceMap(e,r,n){var t=new SourceNode;var o=e.split(a);var u=0;var shiftNextLine=function(){var e=getNextLine();var r=getNextLine()||"";return e+r;function getNextLine(){return u=0;r--){this.prepend(e[r])}}else if(e[s]||typeof e==="string"){this.children.unshift(e)}else{throw new TypeError("Expected a SourceNode, string, or an array of SourceNodes and strings. Got "+e)}return this};SourceNode.prototype.walk=function SourceNode_walk(e){var r;for(var n=0,t=this.children.length;n0){r=[];for(n=0;n{function getArg(e,r,n){if(r in e){return e[r]}else if(arguments.length===3){return n}else{throw new Error('"'+r+'" is a required argument.')}}r.getArg=getArg;var n=/^(?:([\w+\-.]+):)?\/\/(?:(\w+:\w+)@)?([\w.-]*)(?::(\d+))?(.*)$/;var t=/^data:.+\,.+$/;function urlParse(e){var r=e.match(n);if(!r){return null}return{scheme:r[1],auth:r[2],host:r[3],port:r[4],path:r[5]}}r.urlParse=urlParse;function urlGenerate(e){var r="";if(e.scheme){r+=e.scheme+":"}r+="//";if(e.auth){r+=e.auth+"@"}if(e.host){r+=e.host}if(e.port){r+=":"+e.port}if(e.path){r+=e.path}return r}r.urlGenerate=urlGenerate;function normalize(e){var n=e;var t=urlParse(e);if(t){if(!t.path){return e}n=t.path}var o=r.isAbsolute(n);var i=n.split(/\/+/);for(var a,u=0,s=i.length-1;s>=0;s--){a=i[s];if(a==="."){i.splice(s,1)}else if(a===".."){u++}else if(u>0){if(a===""){i.splice(s+1,u);u=0}else{i.splice(s,2);u--}}}n=i.join("/");if(n===""){n=o?"/":"."}if(t){t.path=n;return urlGenerate(t)}return n}r.normalize=normalize;function join(e,r){if(e===""){e="."}if(r===""){r="."}var n=urlParse(r);var o=urlParse(e);if(o){e=o.path||"/"}if(n&&!n.scheme){if(o){n.scheme=o.scheme}return urlGenerate(n)}if(n||r.match(t)){return r}if(o&&!o.host&&!o.path){o.host=r;return urlGenerate(o)}var i=r.charAt(0)==="/"?r:normalize(e.replace(/\/+$/,"")+"/"+r);if(o){o.path=i;return urlGenerate(o)}return i}r.join=join;r.isAbsolute=function(e){return e.charAt(0)==="/"||n.test(e)};function relative(e,r){if(e===""){e="."}e=e.replace(/\/$/,"");var n=0;while(r.indexOf(e+"/")!==0){var t=e.lastIndexOf("/");if(t<0){return r}e=e.slice(0,t);if(e.match(/^([^\/]+:\/)?\/*$/)){return r}++n}return Array(n+1).join("../")+r.substr(e.length+1)}r.relative=relative;var o=function(){var e=Object.create(null);return!("__proto__"in e)}();function identity(e){return e}function toSetString(e){if(isProtoString(e)){return"$"+e}return e}r.toSetString=o?identity:toSetString;function fromSetString(e){if(isProtoString(e)){return e.slice(1)}return e}r.fromSetString=o?identity:fromSetString;function isProtoString(e){if(!e){return false}var r=e.length;if(r<9){return false}if(e.charCodeAt(r-1)!==95||e.charCodeAt(r-2)!==95||e.charCodeAt(r-3)!==111||e.charCodeAt(r-4)!==116||e.charCodeAt(r-5)!==111||e.charCodeAt(r-6)!==114||e.charCodeAt(r-7)!==112||e.charCodeAt(r-8)!==95||e.charCodeAt(r-9)!==95){return false}for(var n=r-10;n>=0;n--){if(e.charCodeAt(n)!==36){return false}}return true}function compareByOriginalPositions(e,r,n){var t=strcmp(e.source,r.source);if(t!==0){return t}t=e.originalLine-r.originalLine;if(t!==0){return t}t=e.originalColumn-r.originalColumn;if(t!==0||n){return t}t=e.generatedColumn-r.generatedColumn;if(t!==0){return t}t=e.generatedLine-r.generatedLine;if(t!==0){return t}return strcmp(e.name,r.name)}r.compareByOriginalPositions=compareByOriginalPositions;function compareByGeneratedPositionsDeflated(e,r,n){var t=e.generatedLine-r.generatedLine;if(t!==0){return t}t=e.generatedColumn-r.generatedColumn;if(t!==0||n){return t}t=strcmp(e.source,r.source);if(t!==0){return t}t=e.originalLine-r.originalLine;if(t!==0){return t}t=e.originalColumn-r.originalColumn;if(t!==0){return t}return strcmp(e.name,r.name)}r.compareByGeneratedPositionsDeflated=compareByGeneratedPositionsDeflated;function strcmp(e,r){if(e===r){return 0}if(e===null){return 1}if(r===null){return-1}if(e>r){return 1}return-1}function compareByGeneratedPositionsInflated(e,r){var n=e.generatedLine-r.generatedLine;if(n!==0){return n}n=e.generatedColumn-r.generatedColumn;if(n!==0){return n}n=strcmp(e.source,r.source);if(n!==0){return n}n=e.originalLine-r.originalLine;if(n!==0){return n}n=e.originalColumn-r.originalColumn;if(n!==0){return n}return strcmp(e.name,r.name)}r.compareByGeneratedPositionsInflated=compareByGeneratedPositionsInflated;function parseSourceMapInput(e){return JSON.parse(e.replace(/^\)]}'[^\n]*\n/,""))}r.parseSourceMapInput=parseSourceMapInput;function computeSourceURL(e,r,n){r=r||"";if(e){if(e[e.length-1]!=="/"&&r[0]!=="/"){e+="/"}r=e+r}if(n){var t=urlParse(n);if(!t){throw new Error("sourceMapURL could not be parsed")}if(t.path){var o=t.path.lastIndexOf("/");if(o>=0){t.path=t.path.substring(0,o+1)}}r=join(urlGenerate(t),r)}return normalize(r)}r.computeSourceURL=computeSourceURL},927:(e,r,n)=>{n(163).x;r.SourceMapConsumer=n(684).SourceMapConsumer;n(565)},896:e=>{"use strict";e.exports=require("fs")},928:e=>{"use strict";e.exports=require("path")}};var r={};function __webpack_require__(n){var t=r[n];if(t!==undefined){return t.exports}var o=r[n]={id:n,loaded:false,exports:{}};var i=true;try{e[n](o,o.exports,__webpack_require__);i=false}finally{if(i)delete r[n]}o.loaded=true;return o.exports}(()=>{__webpack_require__.nmd=e=>{e.paths=[];if(!e.children)e.children=[];return e}})();if(typeof __webpack_require__!=="undefined")__webpack_require__.ab=__dirname+"/";var n={};__webpack_require__(599).install();module.exports=n})();
--------------------------------------------------------------------------------
/dist/utility.js:
--------------------------------------------------------------------------------
1 | export const dateFormat = (date, options = {
2 | month: 'numeric', day: 'numeric'
3 | }) => {
4 | options.timeZone = process.env.TZ || 'UTC';
5 | return new Date(date).toLocaleDateString('en-US', options);
6 | };
7 | //# sourceMappingURL=utility.js.map
--------------------------------------------------------------------------------
/dist/utility.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"utility.js","sourceRoot":"","sources":["../src/utility.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,IAAY,EAAE,UAAsC;IAC7E,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,SAAS;CACjC,EAAU,EAAE;IACX,OAAO,CAAC,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,EAAE,IAAI,KAAK,CAAC;IAC3C,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,kBAAkB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;AAC7D,CAAC,CAAC"}
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import js from "@eslint/js";
2 | import globals from "globals";
3 | import tseslint from "typescript-eslint";
4 | import { defineConfig } from "eslint/config";
5 |
6 |
7 | export default defineConfig([
8 | { files: ["**/*.{js,mjs,cjs,ts,mts,cts}"], plugins: { js }, extends: ["js/recommended"] },
9 | { files: ["**/*.{js,mjs,cjs,ts,mts,cts}"], languageOptions: { globals: globals.browser } },
10 | tseslint.configs.recommended,
11 | ]);
12 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "copilot-usage",
3 | "version": "5.2.0",
4 | "description": "A GitHub Action to get copilot usage",
5 | "private": false,
6 | "scripts": {
7 | "build": "tsc && ncc build src/index.ts -o dist --source-map --license LICENSE",
8 | "test": "vitest run",
9 | "lint": "eslint src/**/*.ts"
10 | },
11 | "keywords": [
12 | "actions",
13 | "node",
14 | "github",
15 | "typescript"
16 | ],
17 | "author": "Austen Stone",
18 | "readme": "README.md",
19 | "repository": {
20 | "type": "git",
21 | "url": "https://github.com/austenstone/copilot-usage.git"
22 | },
23 | "license": "MIT",
24 | "dependencies": {
25 | "@actions/artifact": "^2.3.2",
26 | "@actions/core": "^1.11.1",
27 | "@actions/github": "^6.0.1",
28 | "@octokit/action": "^8.0.2",
29 | "@octokit/rest": "^22.0.0",
30 | "json-2-csv": "^5.5.9",
31 | "jstoxml": "^7.0.1",
32 | "octokit": "^5.0.3"
33 | },
34 | "devDependencies": {
35 | "@eslint/js": "^9.28.0",
36 | "@types/jest": "^29.5.14",
37 | "@types/node": "^22.15.29",
38 | "@vercel/ncc": "^0.38.3",
39 | "dotenv": "^16.5.0",
40 | "eslint": "^9.28.0",
41 | "globals": "^16.2.0",
42 | "ts-jest": "^29.3.4",
43 | "ts-loader": "^9.5.2",
44 | "typescript": "^5.8.3",
45 | "typescript-eslint": "^8.33.1",
46 | "vitest": "^3.2.0"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import run from "./run";
2 | import { RequestError } from "@octokit/request-error";
3 | import { setFailed } from "@actions/core";
4 |
5 | try {
6 | run();
7 | } catch (err) {
8 | if (err instanceof RequestError) {
9 | setFailed(`Request failed: (${err.status}) ${err.message}`);
10 | } else if (err instanceof Error) {
11 | setFailed(err);
12 | } else {
13 | setFailed(JSON.stringify(err, null, 2))
14 | }
15 | throw err;
16 | }
17 |
--------------------------------------------------------------------------------
/src/job-summary.ts:
--------------------------------------------------------------------------------
1 | import { summary } from "@actions/core";
2 | import { Endpoints } from "@octokit/types";
3 | import { createPieChart, createXYChart, DEFAULT_CHART_CONFIGS } from "./mermaid";
4 | import { dateFormat } from "./utility";
5 |
6 | type CopilotUsageResponse = Endpoints["GET /orgs/{org}/copilot/metrics"]["response"]["data"];
7 |
8 | interface BaseMetrics {
9 | total_engaged_users: number;
10 | total_code_acceptances: number;
11 | total_code_suggestions: number;
12 | total_code_lines_accepted: number;
13 | total_code_lines_suggested: number;
14 | }
15 |
16 |
17 | const getEmptyBaseMetrics = (): BaseMetrics => ({
18 | total_engaged_users: 0,
19 | total_code_acceptances: 0,
20 | total_code_suggestions: 0,
21 | total_code_lines_accepted: 0,
22 | total_code_lines_suggested: 0
23 | });
24 |
25 | export const sumNestedValue = (data: T[], path: string[]): number => {
26 | return data.reduce((sum, obj) => {
27 | let result = 0;
28 |
29 | // Helper function to recursively traverse the object/array
30 | const traverse = (current: unknown, pathIndex: number) => {
31 | // Return if we've reached an invalid path
32 | if (current === undefined || current === null) return;
33 |
34 | if (pathIndex >= path.length) {
35 | // We've reached the end of the path, add the value if it's a number
36 | if (typeof current === 'number') {
37 | result += current;
38 | }
39 | return;
40 | }
41 |
42 | const key = path[pathIndex];
43 |
44 | if (Array.isArray(current)) {
45 | // If current is an array, traverse each element
46 | current.forEach(item => traverse(item, pathIndex));
47 | } else if (typeof current === 'object') {
48 | // If current has the key, traverse deeper
49 | if (key in current) {
50 | traverse(current[key], pathIndex + 1);
51 | }
52 | }
53 | };
54 |
55 | traverse(obj, 0);
56 | return sum + result;
57 | }, 0);
58 | };
59 |
60 | const aggregateMetricsBy = (data: CopilotUsageResponse, groupFn: (day: CopilotUsageResponse[0]) => Record): Record => {
61 | return data.reduce((acc, day) => {
62 | const dayMetrics = groupFn(day);
63 | Object.entries(dayMetrics).forEach(([key, metrics]) => {
64 | acc[key] = acc[key] || getEmptyBaseMetrics();
65 | Object.entries(metrics).forEach(([metric, value]) => {
66 | if (metric === 'total_engaged_users') {
67 | acc[key][metric] = Math.max(acc[key][metric], value);
68 | } else {
69 | acc[key][metric] += value;
70 | }
71 | });
72 | });
73 | return acc;
74 | }, {} as Record);
75 | };
76 |
77 | const groupLanguageMetrics = (day: CopilotUsageResponse[0]): Record => {
78 | const metrics: Record = {};
79 |
80 | day.copilot_ide_code_completions?.editors?.forEach(editor => {
81 | editor.models?.forEach(model => {
82 | model.languages?.forEach(lang => {
83 | const language = lang.name || 'unknown';
84 | metrics[language] = metrics[language] || getEmptyBaseMetrics();
85 | Object.entries(lang).forEach(([key, value]) => {
86 | if (key in metrics[language] && typeof value === 'number') {
87 | metrics[language][key as keyof BaseMetrics] += value;
88 | }
89 | });
90 | });
91 | });
92 | });
93 |
94 | return metrics;
95 | };
96 |
97 | const groupEditorMetrics = (day: CopilotUsageResponse[0]): Record => {
98 | const metrics: Record = {};
99 |
100 | day.copilot_ide_code_completions?.editors?.forEach(editor => {
101 | const editorName = editor.name || 'unknown';
102 | metrics[editorName] = metrics[editorName] || getEmptyBaseMetrics();
103 | metrics[editorName].total_engaged_users = editor.total_engaged_users || 0;
104 |
105 | editor.models?.forEach(model => {
106 | model.languages?.forEach(lang => {
107 | Object.entries(lang).forEach(([key, value]) => {
108 | if (key in metrics[editorName] && typeof value === 'number') {
109 | metrics[editorName][key as keyof BaseMetrics] += value;
110 | }
111 | });
112 | });
113 | });
114 | });
115 |
116 | return metrics;
117 | };
118 |
119 | const getChatMetrics = (dailyTotals: Array<{total_chats?: number, total_chat_copy_events?: number, total_chat_insert_events?: number}>) => ({
120 | totalChats: dailyTotals.reduce((sum, day) => sum + (day.total_chats || 0), 0),
121 | totalCopyEvents: dailyTotals.reduce((sum, day) => sum + (day.total_chat_copy_events || 0), 0),
122 | totalInsertEvents: dailyTotals.reduce((sum, day) => sum + (day.total_chat_insert_events || 0), 0)
123 | });
124 |
125 | export const createJobSummaryUsage = (data: CopilotUsageResponse, name: string) => {
126 | const languageMetrics = aggregateMetricsBy(data, groupLanguageMetrics);
127 | const editorMetrics = aggregateMetricsBy(data, groupEditorMetrics);
128 | // const modelMetrics = aggregateMetricsBy(data, groupModelMetrics);
129 | const dailyTotals = data.map(day => ({
130 | date: day.date,
131 | total_active_users: day.total_active_users || 0,
132 | total_engaged_users: day.total_engaged_users || 0,
133 | total_code_acceptances: sumNestedValue([day], ['copilot_ide_code_completions', 'editors', 'models', 'languages', 'total_code_acceptances']),
134 | total_code_suggestions: sumNestedValue([day], ['copilot_ide_code_completions', 'editors', 'models', 'languages', 'total_code_suggestions']),
135 | total_code_lines_accepted: sumNestedValue([day], ['copilot_ide_code_completions', 'editors', 'models', 'languages', 'total_code_lines_accepted']),
136 | total_code_lines_suggested: sumNestedValue([day], ['copilot_ide_code_completions', 'editors', 'models', 'languages', 'total_code_lines_suggested']),
137 | total_chats: sumNestedValue([day], ['copilot_ide_chat', 'editors', 'models', 'total_chats']),
138 | total_chat_copy_events: sumNestedValue([day], ['copilot_ide_chat', 'editors', 'models', 'total_chat_copy_events']),
139 | total_chat_insert_events: sumNestedValue([day], ['copilot_ide_chat', 'editors', 'models', 'total_chat_insertion_events']),
140 | total_dotcom_chat_chats: sumNestedValue([day], ['copilot_dotcom_chat', 'models', 'total_chats']),
141 | total_dotcom_pr_summaries_created: sumNestedValue([day], ['copilot_dotcom_pull_requests', 'repositories', 'models', 'total_pr_summaries_created']),
142 | }));
143 | const chatMetrics = getChatMetrics(dailyTotals);
144 |
145 | const topLanguages = Object.entries(languageMetrics)
146 | .sort((a, b) => b[1].total_code_suggestions - a[1].total_code_suggestions)
147 | .slice(0, 5)
148 | .map(([lang]) => lang);
149 |
150 | const totalMetrics = Object.values(languageMetrics).reduce((acc, curr) => ({
151 | totalCodeAcceptances: acc.totalCodeAcceptances + curr.total_code_acceptances,
152 | totalCodeSuggestions: acc.totalCodeSuggestions + curr.total_code_suggestions,
153 | totalLinesAccepted: acc.totalLinesAccepted + curr.total_code_lines_accepted
154 | }), { totalCodeAcceptances: 0, totalCodeSuggestions: 0, totalLinesAccepted: 0 });
155 |
156 | return summary
157 | .addHeading(`Copilot Usage for ${name}
${dateFormat(data[0].date)} - ${dateFormat(data[data.length - 1].date)}`)
158 | .addRaw(`Metrics for the last ${data.length} days`)
159 | .addHeading('Totals', 2)
160 | .addTable([
161 | ['Code Suggestions', totalMetrics.totalCodeSuggestions.toLocaleString()],
162 | ['Code Acceptances', totalMetrics.totalCodeAcceptances.toLocaleString()],
163 | ['Acceptance Rate', `${((totalMetrics.totalCodeAcceptances / totalMetrics.totalCodeSuggestions) * 100).toFixed(2)}%`],
164 | ['Lines of Code Accepted', totalMetrics.totalLinesAccepted.toLocaleString()],
165 | ['Chat Interactions', chatMetrics.totalChats.toLocaleString()],
166 | ['Chat Copy Events', chatMetrics.totalCopyEvents.toLocaleString()],
167 | ['Chat Insertion Events', chatMetrics.totalInsertEvents.toLocaleString()]
168 | ])
169 | .addHeading('Daily Engaged Users', 3)
170 | .addRaw(createXYChart({
171 | xAxis: {
172 | categories: DEFAULT_CHART_CONFIGS.dailyCategories(data)
173 | },
174 | yAxis: {
175 | },
176 | series: [
177 | {
178 | type: 'bar',
179 | values: data.map(day => day.total_active_users || 0)
180 | },
181 | {
182 | type: 'bar',
183 | values: data.map(day => day.total_engaged_users || 0)
184 | },
185 | ],
186 | legend: ['Active', 'Engaged']
187 | }))
188 | .addHeading('Daily Engaged Users by Product', 3)
189 | .addRaw(createXYChart({
190 | xAxis: {
191 | categories: DEFAULT_CHART_CONFIGS.dailyCategories(data)
192 | },
193 | yAxis: {
194 | },
195 | series: [
196 | {
197 | type: 'line',
198 | values: data.map(day => sumNestedValue([day], ['copilot_ide_code_completions', 'total_engaged_users']))
199 | },
200 | {
201 | type: 'line',
202 | values: data.map(day => sumNestedValue([day], ['copilot_ide_chat', 'total_engaged_users']))
203 | },
204 | {
205 | type: 'line',
206 | values: data.map(day => sumNestedValue([day], ['copilot_dotcom_chat', 'total_engaged_users']))
207 | },
208 | {
209 | type: 'line',
210 | values: data.map(day => sumNestedValue([day], ['copilot_dotcom_pull_requests', 'total_engaged_users']))
211 | }
212 | ],
213 | legend: ['IDE Code Completions', 'IDE Chat', 'Dotcom Chat', 'Dotcom Pull Requests']
214 | }))
215 | .addHeading('IDE Completion', 2)
216 | .addHeading('Suggestions vs. Acceptances', 3)
217 | .addRaw(createXYChart({
218 | xAxis: { categories: DEFAULT_CHART_CONFIGS.dailyCategories(data) },
219 | yAxis: {},
220 | series: [
221 | {
222 | type: 'bar',
223 | values: dailyTotals.map(day => day.total_code_suggestions || 0)
224 | },
225 | {
226 | type: 'bar',
227 | values: dailyTotals.map(day => day.total_code_acceptances || 0)
228 | },
229 | ],
230 | legend: ['Suggestions', 'Acceptances']
231 | }))
232 | .addHeading('Lines Suggested vs. Accepted', 3)
233 | .addRaw(createXYChart({
234 | xAxis: { categories: DEFAULT_CHART_CONFIGS.dailyCategories(data) },
235 | yAxis: {},
236 | series: [
237 | {
238 | type: 'bar',
239 | values: dailyTotals.map(day => day.total_code_lines_suggested || 0)
240 | },
241 | {
242 | type: 'bar',
243 | values: dailyTotals.map(day => day.total_code_lines_accepted || 0)
244 | },
245 | ],
246 | legend: ['Lines Suggested', 'Lines Accepted']
247 | }))
248 | .addHeading('Acceptance Rate', 3)
249 | .addRaw(createXYChart({
250 | xAxis: {
251 | categories: DEFAULT_CHART_CONFIGS.dailyCategories(data)
252 | },
253 | yAxis: {
254 | min: 0,
255 | max: 100
256 | },
257 | series: [
258 | {
259 | type: 'line',
260 | values: data.map(day => {
261 | const acceptances = sumNestedValue([day], ['copilot_ide_code_completions', 'editors', 'models', 'languages', 'total_code_acceptances']);
262 | const suggestions = sumNestedValue([day], ['copilot_ide_code_completions', 'editors', 'models', 'languages', 'total_code_suggestions']);
263 | return suggestions > 0 ? Math.round((acceptances / suggestions) * 100) : 0;
264 | })
265 | }
266 | ]
267 | }))
268 | .addHeading('Acceptance Rate by Language', 3)
269 | .addRaw(createXYChart({
270 | xAxis: {
271 | categories: DEFAULT_CHART_CONFIGS.dailyCategories(data)
272 | },
273 | yAxis: {
274 | min: 0,
275 | max: 100
276 | },
277 | series: topLanguages.map(language => ({
278 | type: 'line',
279 | values: data.map(day => {
280 | let acceptances = 0;
281 | let suggestions = 0;
282 |
283 | day.copilot_ide_code_completions?.editors?.forEach(editor => {
284 | editor.models?.forEach(model => {
285 | model.languages?.forEach(lang => {
286 | if (lang.name === language) {
287 | acceptances += lang.total_code_acceptances || 0;
288 | suggestions += lang.total_code_suggestions || 0;
289 | }
290 | });
291 | });
292 | });
293 |
294 | return suggestions > 0 ? Math.round((acceptances / suggestions) * 100) : 0;
295 | })
296 | })),
297 | legend: topLanguages
298 | }))
299 | .addHeading('Language Usage by Engaged Users', 3)
300 | .addRaw(createPieChart(Object.fromEntries(Object.entries(languageMetrics)
301 | .map(([lang, metrics]) => [lang, metrics.total_engaged_users]))
302 | ))
303 | .addHeading('Editor Usage by Engaged Users', 3)
304 | .addRaw(createPieChart(Object.fromEntries(Object.entries(editorMetrics)
305 | .map(([editor, metrics]) => [editor, metrics.total_engaged_users]))
306 | ))
307 | // .addHeading('Model Usage')
308 | // .addRaw(createPieChart(Object.fromEntries(Object.entries(modelMetrics)
309 | // .map(([model, metrics]) => [model, metrics.total_engaged_users]))
310 | // ))
311 | .addHeading('IDE Copilot Chat', 2)
312 | .addRaw(createXYChart({
313 | xAxis: {
314 | categories: DEFAULT_CHART_CONFIGS.dailyCategories(data)
315 | },
316 | yAxis: {
317 | },
318 | series: [
319 | {
320 | type: 'bar',
321 | values: dailyTotals.map(day => day.total_chats || 0)
322 | },
323 | {
324 | type: 'line',
325 | values: dailyTotals.map(day => day.total_chat_copy_events || 0)
326 | },
327 | {
328 | type: 'line',
329 | values: dailyTotals.map(day => day.total_chat_insert_events || 0)
330 | },
331 | ],
332 | legend: ['Total Chats', 'Copy Events', 'Insert Events']
333 | }))
334 | .addHeading('Copilot .COM Chat', 2)
335 | .addHeading('Total Chats', 3)
336 | .addRaw(createXYChart({
337 | xAxis: {
338 | categories: DEFAULT_CHART_CONFIGS.dailyCategories(data)
339 | },
340 | yAxis: {
341 | },
342 | series: [
343 | {
344 | type: 'bar',
345 | values: dailyTotals.map(day => day.total_dotcom_chat_chats || 0)
346 | }
347 | ],
348 | legend: ['Total Chats']
349 | }))
350 | .addHeading('Copilot .COM Pull Request', 2)
351 | .addHeading('Summaries Created', 3)
352 | .addRaw(createXYChart({
353 | xAxis: {
354 | categories: DEFAULT_CHART_CONFIGS.dailyCategories(data)
355 | },
356 | yAxis: {
357 | },
358 | series: [
359 | {
360 | type: 'bar',
361 | values: dailyTotals.map(day => day.total_dotcom_pr_summaries_created || 0)
362 | }
363 | ],
364 | legend: ['Total PR Summaries Created']
365 | }))
366 | };
367 |
368 | export const createJobSummaryCopilotDetails = (orgCopilotDetails: Endpoints["GET /orgs/{org}/copilot/billing"]["response"]["data"]) => {
369 | return summary
370 | .addHeading('Seat Info')
371 | .addHeading('Organization Copilot Details', 3)
372 | .addTable([
373 | ['Plan Type', orgCopilotDetails.plan_type?.toLocaleUpperCase() || 'Unknown'],
374 | ['Seat Management Setting', {
375 | 'assign_all': 'Assign All',
376 | 'assign_selected': 'Assign Selected',
377 | 'disabled': 'Disabled',
378 | 'unconfigured': 'Unconfigured',
379 | }[orgCopilotDetails.seat_management_setting] || 'Unknown'],
380 | ])
381 | .addHeading('Seat Breakdown', 3)
382 | .addTable([
383 | ['Total Seats', (orgCopilotDetails.seat_breakdown.total || 0).toString()],
384 | ['Added this cycle', (orgCopilotDetails.seat_breakdown.added_this_cycle || 0).toString()],
385 | ['Pending invites', (orgCopilotDetails.seat_breakdown.pending_invitation || 0).toString()],
386 | ['Pending cancellations', (orgCopilotDetails.seat_breakdown.pending_cancellation || 0).toString()],
387 | ['Active this cycle', (orgCopilotDetails.seat_breakdown.active_this_cycle || 0).toString()],
388 | ['Inactive this cycle', (orgCopilotDetails.seat_breakdown.inactive_this_cycle || 0).toString()]
389 | ])
390 | .addHeading('Policies', 3)
391 | .addTable([
392 | ['Public Code Suggestions Enabled', {
393 | 'allow': 'Allowed',
394 | 'block': 'Blocked',
395 | 'unconfigured': 'Unconfigured',
396 | }[orgCopilotDetails.public_code_suggestions] || 'Unknown'],
397 | ['IDE Chat Enabled', orgCopilotDetails.ide_chat?.toLocaleUpperCase() || 'Unknown'],
398 | ['Platform Chat Enabled', orgCopilotDetails.platform_chat?.toLocaleUpperCase() || 'Unknown'],
399 | ['CLI Enabled', orgCopilotDetails.cli?.toLocaleUpperCase() || 'Unknown'],
400 | ])
401 | };
402 |
403 | export const createJobSummarySeatAssignments = (data: Endpoints["GET /orgs/{org}/copilot/billing/seats"]["response"]["data"]["seats"]) => {
404 | if (!data) data = [];
405 | return summary
406 | .addHeading('Seat Assignments')
407 | .addTable([
408 | [
409 | { data: 'Avatar', header: true },
410 | { data: 'Login', header: true },
411 | { data: `Last Activity (${process.env.TZ || 'UTC'})`, header: true },
412 | { data: 'Last Editor Used', header: true },
413 | { data: 'Created At', header: true },
414 | { data: 'Pending Cancellation Date', header: true },
415 | { data: 'Team', header: true },
416 | ],
417 | ...data.map(seat => [
418 | `
`,
419 | seat.assignee?.login,
420 | seat.last_activity_at ? dateFormat(seat.last_activity_at, { month: 'numeric', day: 'numeric', year: 'numeric', hour: 'numeric', minute: 'numeric' }) : 'No Activity',
421 | seat.last_activity_editor || 'N/A',
422 | dateFormat(seat.created_at),
423 | dateFormat(seat.pending_cancellation_date || ''),
424 | String(seat.assigning_team?.name || ' '),
425 | ] as string[])
426 | ])
427 | }
428 |
429 | export const setJobSummaryTimeZone = (timeZone: string) => process.env.TZ = timeZone;
430 |
--------------------------------------------------------------------------------
/src/mermaid.ts:
--------------------------------------------------------------------------------
1 | import { Endpoints } from "@octokit/types";
2 | import { dateFormat } from "./utility";
3 |
4 | interface ChartConfig {
5 | width?: number;
6 | height?: number;
7 | title?: string;
8 | }
9 |
10 | export const createMermaidChart = (type: 'pie' | 'xychart-beta', config: ChartConfig, content: string) => {
11 | const chartConfig = `---
12 | config:
13 | ${type === 'xychart-beta' ? `xyChart:
14 | width: ${config.width || 900}
15 | height: ${config.height || 500}
16 | xAxis:
17 | labelPadding: 20
18 | yAxis:
19 | labelPadding: 20
20 | themeVariables:
21 | xyChart:
22 | backgroundColor: "transparent"` : ''}
23 | ---
24 | ${type}
25 | ${config.title ? ` title "${config.title}"` : ''}
26 | ${content}`;
27 |
28 | return `\n\`\`\`mermaid\n${chartConfig}\n\`\`\`\n`;
29 | };
30 |
31 | export const createPieChart = (data: Record, limit = 20) => {
32 | const content = Object.entries(data)
33 | .sort((a, b) => b[1] - a[1])
34 | .slice(0, limit)
35 | .map(([label, value]) => `"${label}" : ${value}`)
36 | .join('\n');
37 |
38 | return createMermaidChart('pie', {}, content);
39 | };
40 |
41 | export const createXYChart = (config: ChartConfig & {
42 | series: {
43 | type: 'bar' | 'line',
44 | category?: string,
45 | values: number[],
46 | }[],
47 | xAxis: {
48 | title?: string,
49 | categories: string[],
50 | },
51 | yAxis: {
52 | title?: string,
53 | min?: number,
54 | max?: number,
55 | },
56 | legend?: string[],
57 | }) => {
58 |
59 | const {
60 | min, max
61 | } = config.series.reduce((acc, series) => {
62 | series.values.forEach(value => {
63 | if (value < acc.min || acc.min === undefined) acc.min = value;
64 | if (value > acc.max || acc.max === undefined) acc.max = value;
65 | });
66 | return acc;
67 | }, { min: config.yAxis.min || 0, max: config.yAxis.max || 100 });
68 |
69 | return createMermaidChart('xychart-beta', config,
70 | ` x-axis ${config.xAxis.title ? "\"" + config.xAxis.title + "\"" : ''} [${config.xAxis.categories.join(', ')}]\n` +
71 | ` y-axis ${config.yAxis.title ? "\"" + config.yAxis.title + "\"" : ''} ${min || 0} --> ${max || 100}\n` +
72 | config.series.map(series => {
73 | return `${series.type} ${series.category ? "\"" + series.category + "\"" : ''} [${series.values.join(', ')}]`;
74 | }).join('\n')) + (config.legend ? `\n${generateLegend(config.legend)}` : '');
75 | };
76 |
77 | export function generateLegend(categories: string[]): string {
78 | const colors = ["#3498db", "#2ecc71", "#e74c3c", "#f1c40f", "#bdc3c7", "#ffffff", "#34495e", "#9b59b6", "#1abc9c", "#e67e22"];
79 | return categories.map((category, i) =>
80 | `}/${colors[i % colors.length]
81 | .replace('#', '')}.png) ${category}`)
82 | .join(' ');
83 | }
84 |
85 | const DEFAULT_CHART_HEIGHT = 400;
86 | export const DEFAULT_CHART_CONFIGS = {
87 | standardHeight: { height: DEFAULT_CHART_HEIGHT },
88 | dailyCategories: (data: Endpoints["GET /orgs/{org}/copilot/metrics"]["response"]["data"]) =>
89 | data.map(day => dateFormat(day.date, { day: 'numeric' })),
90 | } as const;
91 |
--------------------------------------------------------------------------------
/src/run.ts:
--------------------------------------------------------------------------------
1 | import { debug, getBooleanInput, getInput, info, setOutput, summary, warning } from "@actions/core";
2 | import { Octokit, RestEndpointMethodTypes } from '@octokit/rest'
3 | import { DefaultArtifactClient } from "@actions/artifact";
4 | import { writeFileSync } from "fs";
5 | import { Json2CsvOptions, json2csv } from "json-2-csv";
6 | import { toXML } from 'jstoxml';
7 | import { createJobSummaryCopilotDetails, createJobSummarySeatAssignments, createJobSummaryUsage, setJobSummaryTimeZone } from "./job-summary";
8 |
9 | export type CopilotUsageBreakdown = {
10 | language: string;
11 | editor: string;
12 | suggestions_count: number;
13 | acceptances_count: number;
14 | lines_suggested: number;
15 | lines_accepted: number;
16 | active_users: number;
17 | };
18 |
19 | export type CopilotUsageResponseData = {
20 | day: string;
21 | total_suggestions_count: number;
22 | total_acceptances_count: number;
23 | total_lines_suggested: number;
24 | total_lines_accepted: number;
25 | total_active_users: number;
26 | total_chat_acceptances: number;
27 | total_chat_turns: number;
28 | total_active_chat_users: number;
29 | breakdown: CopilotUsageBreakdown[];
30 | };
31 |
32 | export type CopilotUsageResponse = CopilotUsageResponseData[];
33 |
34 | interface Input {
35 | token: string;
36 | organization: string;
37 | team?: string;
38 | days?: number;
39 | since?: string;
40 | until?: string;
41 | jobSummary: boolean;
42 | json: boolean;
43 | csv: boolean;
44 | csvOptions?: Json2CsvOptions;
45 | xml: boolean;
46 | xmlOptions?: {
47 | header: boolean;
48 | indent: string;
49 | attributeExplicitTrue: boolean;
50 | selfCloseTags: boolean;
51 | };
52 | timeZone: string;
53 | artifactName: string;
54 | }
55 |
56 | const getInputs = (): Input => {
57 | const result = {} as Input;
58 | result.token = getInput("github-token").trim();
59 | result.organization = getInput("organization").trim();
60 | result.team = getInput("team").trim();
61 | result.jobSummary = getBooleanInput("job-summary");
62 | result.days = parseInt(getInput("days"));
63 | result.since = getInput("since");
64 | result.until = getInput("until");
65 | result.json = getBooleanInput("json");
66 | result.csv = getBooleanInput("csv");
67 | result.csvOptions = getInput("csv-options") ? JSON.parse(getInput("csv-options")) : undefined;
68 | result.xml = getBooleanInput("xml");
69 | result.xmlOptions = getInput("xml-options") ? JSON.parse(getInput("xml-options")) : {
70 | header: true,
71 | indent: " ",
72 | };
73 | result.timeZone = getInput("time-zone");
74 | result.artifactName = getInput("artifact-name");
75 | if (!result.token) {
76 | throw new Error("github-token is required");
77 | }
78 | return result;
79 | };
80 |
81 | const run = async (): Promise => {
82 | const input = getInputs();
83 | const octokit = new Octokit({
84 | auth: input.token
85 | });
86 |
87 | const params = {} as Record;
88 | if (input.days) {
89 | params.since = new Date(new Date().setDate(new Date().getDate() - input.days)).toISOString().split('T')[0];
90 | } else if (input.since || input.until) {
91 | if (input.since) params.since = input.since;
92 | if (input.until) params.until = input.until;
93 | }
94 | let req: Promise;
95 |
96 | if (input.team) {
97 | if (!input.organization) {
98 | throw new Error("organization is required when team is provided");
99 | }
100 | info(`Fetching Copilot usage for team ${input.team} inside organization ${input.organization}`);
101 | req = octokit.rest.copilot.copilotMetricsForTeam({
102 | org: input.organization,
103 | team_slug: input.team,
104 | ...params
105 | }).then(response => response.data);
106 | } else if (input.organization) {
107 | info(`Fetching Copilot usage for organization ${input.organization}`);
108 | req = octokit.rest.copilot.copilotMetricsForOrganization({
109 | org: input.organization,
110 | ...params
111 | }).then(response => response.data);
112 | } else {
113 | throw new Error("organization, enterprise or team input is required");
114 | }
115 |
116 | const data = await req;
117 | if (!data || data.length === 0) {
118 | return warning("No Copilot usage data found");
119 | }
120 | debug(JSON.stringify(data, null, 2));
121 | info(`Fetched Copilot usage data for ${data.length} days (${data[0].date} to ${data[data.length - 1].date})`);
122 |
123 | if (input.jobSummary) {
124 | setJobSummaryTimeZone(input.timeZone);
125 | const name = (input.team && input.organization) ? `${input.organization} / ${input.team}` : input.organization;
126 | await createJobSummaryUsage(data, name).write();
127 |
128 | if (input.organization && !input.team) { // refuse to fetch organization seat info if looking for team usage
129 | info(`Fetching Copilot details for organization ${input.organization}`);
130 | const orgCopilotDetails = await octokit.rest.copilot.getCopilotOrganizationDetails({
131 | org: input.organization
132 | }).then(response => response.data);
133 | if (orgCopilotDetails) {
134 | await createJobSummaryCopilotDetails(orgCopilotDetails).write();
135 | }
136 | setOutput("result-org-details", JSON.stringify(orgCopilotDetails));
137 |
138 | info(`Fetching Copilot seat assignments for organization ${input.organization}`);
139 | const orgSeatAssignments = await octokit.paginate(octokit.rest.copilot.listCopilotSeats, {
140 | org: input.organization
141 | }) as { total_seats: number, seats: object[] }[];
142 | const _orgSeatAssignments = {
143 | total_seats: orgSeatAssignments[0]?.total_seats || 0,
144 | // octokit paginate returns an array of objects (bug)
145 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
146 | seats: (orgSeatAssignments).reduce((acc, rsp) => acc.concat(rsp.seats), [] as any[])
147 | };
148 | if (_orgSeatAssignments.total_seats > 0 && _orgSeatAssignments?.seats) {
149 | _orgSeatAssignments.seats = _orgSeatAssignments.seats.sort((a, b) => new Date(b.last_activity_at).getTime() - new Date(a.last_activity_at).getTime());
150 | await createJobSummarySeatAssignments(_orgSeatAssignments?.seats)?.write();
151 | }
152 | setOutput("result-seats", JSON.stringify(_orgSeatAssignments));
153 | }
154 |
155 | if (input.organization) {
156 | await summary.addLink(`Manage Access for ${input.organization}`, `https://github.com/organizations/${input.organization}/settings/copilot/seat_management`)
157 | .write();
158 | }
159 | }
160 |
161 | if (input.csv || input.xml || input.json) {
162 | const artifact = new DefaultArtifactClient();
163 | const files = [] as string[];
164 | if (input.json) {
165 | writeFileSync('copilot-usage.json', JSON.stringify(data, null, 2));
166 | files.push('copilot-usage.json');
167 | }
168 | if (input.csv) {
169 | writeFileSync('copilot-usage.csv', await json2csv(data, input.csvOptions));
170 | files.push('copilot-usage.csv');
171 | }
172 | if (input.xml) {
173 | writeFileSync('copilot-usage.xml', await toXML(data, input.xmlOptions));
174 | files.push('copilot-usage.xml');
175 | }
176 | await artifact.uploadArtifact(input.artifactName, files, '.');
177 | }
178 |
179 | setOutput("result", JSON.stringify(data));
180 | setOutput("since", data[0].date);
181 | setOutput("until", data[data.length - 1].date);
182 | setOutput("days", data.length.toString());
183 | };
184 |
185 | export default run;
--------------------------------------------------------------------------------
/src/utility.ts:
--------------------------------------------------------------------------------
1 | export const dateFormat = (date: string, options: Intl.DateTimeFormatOptions = {
2 | month: 'numeric', day: 'numeric'
3 | }): string => {
4 | options.timeZone = process.env.TZ || 'UTC';
5 | return new Date(date).toLocaleDateString('en-US', options);
6 | };
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Basic Options */
4 | // "incremental": true, /* Enable incremental compilation */
5 | "target": "ES2020", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
6 | "module": "esnext", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
7 | // "allowJs": true, /* Allow javascript files to be compiled. */
8 | // "checkJs": true, /* Report errors in .js files. */
9 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
10 | // "declaration": true, /* Generates corresponding '.d.ts' file. */
11 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
12 | "sourceMap": true, /* Generates corresponding '.map' file. */
13 | // "outFile": "./", /* Concatenate and emit output to single file. */
14 | "outDir": "./dist", /* Redirect output structure to the directory. */
15 | "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
16 | // "composite": true, /* Enable project compilation */
17 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
18 | "removeComments": true, /* Do not emit comments to output. */
19 | // "noEmit": true, /* Do not emit outputs. */
20 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */
21 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
22 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
23 | /* Strict Type-Checking Options */
24 | "strict": true, /* Enable all strict type-checking options. */
25 | "noImplicitAny": false, /* Raise error on expressions and declarations with an implied 'any' type. */
26 | "strictNullChecks": true, /* Enable strict null checks. */
27 | "strictFunctionTypes": true, /* Enable strict checking of function types. */
28 | "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
29 | "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
30 | "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
31 | "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
32 | /* Additional Checks */
33 | "noUnusedLocals": false, /* Report errors on unused locals. */
34 | "noUnusedParameters": true, /* Report errors on unused parameters. */
35 | "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
36 | "noFallthroughCasesInSwitch": true, /* Report errors for fallthnpm i -g @vercel/ncc
37 | rough cases in switch statement. */
38 | /* Module Resolution Options */
39 | "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
40 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
41 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
42 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
43 | // "typeRoots": [], /* List of folders to include type definitions from. */
44 | // "types": [], /* Type declaration files to be included in compilation. */
45 | "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
46 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
47 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
48 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
49 | /* Source Map Options */
50 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
51 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
52 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
53 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
54 | /* Experimental Options */
55 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
56 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
57 | },
58 | "exclude": [
59 | "node_modules",
60 | "__tests__",
61 | "__mocks__",
62 | "jest.config.ts",
63 | "dist",
64 | ]
65 | }
--------------------------------------------------------------------------------