From 80dc4ee1b82c94e844fddd2a1d82ff359a99a6cb Mon Sep 17 00:00:00 2001 From: nicholas-codecov Date: Mon, 9 Sep 2024 07:35:53 -0300 Subject: [PATCH] feat: Add ability to use GitHub OIDC (#161) Add GH OIDC functionality to the plugin core, enabling it in all of the other plugins. --- .changeset/lucky-seals-admire.md | 13 ++ .github/workflows/ci.yml | 7 + examples/oidc/.eslintrc.cjs | 18 ++ examples/oidc/.gitignore | 24 +++ examples/oidc/README.md | 27 +++ examples/oidc/index.html | 13 ++ examples/oidc/package.json | 36 ++++ examples/oidc/public/vite.svg | 1 + examples/oidc/src/App.css | 42 +++++ examples/oidc/src/App.tsx | 44 +++++ .../IndexedLazyComponent.tsx | 7 + .../oidc/src/IndexedLazyComponent/index.ts | 1 + .../oidc/src/LazyComponent/LazyComponent.tsx | 14 ++ examples/oidc/src/assets/react.svg | 1 + examples/oidc/src/index.css | 69 +++++++ examples/oidc/src/main.tsx | 10 + examples/oidc/src/vite-env.d.ts | 1 + examples/oidc/tsconfig.json | 25 +++ examples/oidc/tsconfig.node.json | 10 + examples/oidc/vite.config.ts | 28 +++ packages/bundler-plugin-core/package.json | 1 + .../src/errors/BadOIDCServiceError.ts | 5 + .../src/errors/BadResponseError.ts | 4 +- .../src/errors/FailedFetchError.ts | 4 +- .../src/errors/FailedOIDCFetchError.ts | 5 + .../src/errors/FailedUploadError.ts | 4 +- .../src/errors/InvalidSlugError.ts | 4 +- .../src/errors/NoUploadTokenError.ts | 4 +- .../src/errors/UndefinedGitServiceError.ts | 4 +- .../src/errors/UploadLimitReachedError.ts | 4 +- packages/bundler-plugin-core/src/types.ts | 32 +++- .../bundler-plugin-core/src/utils/Output.ts | 14 +- .../utils/__tests__/getPreSignedURL.test.ts | 175 +++++++++++++++--- .../utils/__tests__/normalizeOptions.test.ts | 16 ++ .../src/utils/fetchWithRetry.ts | 6 +- .../src/utils/getPreSignedURL.ts | 51 ++++- .../src/utils/normalizeOptions.ts | 23 +++ .../utils/providers/__tests__/index.test.ts | 9 +- .../src/utils/uploadStats.ts | 3 +- packages/nextjs-webpack-plugin/README.md | 26 +++ packages/nuxt-plugin/README.md | 28 +++ packages/remix-vite-plugin/README.md | 27 +++ packages/rollup-plugin/README.md | 23 +++ packages/solidstart-plugin/README.md | 27 +++ packages/sveltekit-plugin/README.md | 25 +++ packages/vite-plugin/README.md | 25 +++ packages/webpack-plugin/README.md | 29 +++ pnpm-lock.yaml | 63 +++++++ tsconfig.json | 2 +- 49 files changed, 969 insertions(+), 65 deletions(-) create mode 100644 .changeset/lucky-seals-admire.md create mode 100644 examples/oidc/.eslintrc.cjs create mode 100644 examples/oidc/.gitignore create mode 100644 examples/oidc/README.md create mode 100644 examples/oidc/index.html create mode 100644 examples/oidc/package.json create mode 100644 examples/oidc/public/vite.svg create mode 100644 examples/oidc/src/App.css create mode 100644 examples/oidc/src/App.tsx create mode 100644 examples/oidc/src/IndexedLazyComponent/IndexedLazyComponent.tsx create mode 100644 examples/oidc/src/IndexedLazyComponent/index.ts create mode 100644 examples/oidc/src/LazyComponent/LazyComponent.tsx create mode 100644 examples/oidc/src/assets/react.svg create mode 100644 examples/oidc/src/index.css create mode 100644 examples/oidc/src/main.tsx create mode 100644 examples/oidc/src/vite-env.d.ts create mode 100644 examples/oidc/tsconfig.json create mode 100644 examples/oidc/tsconfig.node.json create mode 100644 examples/oidc/vite.config.ts create mode 100644 packages/bundler-plugin-core/src/errors/BadOIDCServiceError.ts create mode 100644 packages/bundler-plugin-core/src/errors/FailedOIDCFetchError.ts diff --git a/.changeset/lucky-seals-admire.md b/.changeset/lucky-seals-admire.md new file mode 100644 index 00000000..38f10eec --- /dev/null +++ b/.changeset/lucky-seals-admire.md @@ -0,0 +1,13 @@ +--- +"@codecov/nextjs-webpack-plugin": minor +"@codecov/bundler-plugin-core": minor +"@codecov/remix-vite-plugin": minor +"@codecov/solidstart-plugin": minor +"@codecov/sveltekit-plugin": minor +"@codecov/webpack-plugin": minor +"@codecov/rollup-plugin": minor +"@codecov/nuxt-plugin": minor +"@codecov/vite-plugin": minor +--- + +Add the ability for users to use GH OIDC instead of explicit upload tokens diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f21fc3b4..35b1f423 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,6 +11,9 @@ concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true +permissions: + id-token: write + jobs: install: name: Install deps @@ -284,6 +287,7 @@ jobs: "next-js", "next-js-15", "nuxt", + "oidc", "remix", "rollup", "sveltekit", @@ -343,6 +347,7 @@ jobs: NEXT_API_URL: ${{ secrets.CODECOV_API_URL }} NUXT_UPLOAD_TOKEN: ${{ secrets.CODECOV_ORG_TOKEN }} NUXT_API_URL: ${{ secrets.CODECOV_API_URL }} + OIDC_API_URL: ${{ secrets.CODECOV_API_URL }} REMIX_UPLOAD_TOKEN: ${{ secrets.CODECOV_ORG_TOKEN }} REMIX_API_URL: ${{ secrets.CODECOV_API_URL }} ROLLUP_UPLOAD_TOKEN: ${{ secrets.CODECOV_ORG_TOKEN }} @@ -371,6 +376,7 @@ jobs: "next-js", "next-js-15", "nuxt", + "oidc", "remix", "rollup", "sveltekit", @@ -430,6 +436,7 @@ jobs: NEXT_API_URL: ${{ secrets.CODECOV_STAGING_API_URL }} NUXT_UPLOAD_TOKEN: ${{ secrets.CODECOV_ORG_TOKEN }} NUXT_API_URL: ${{ secrets.CODECOV_API_URL }} + OIDC_API_URL: ${{ secrets.CODECOV_API_URL }} REMIX_UPLOAD_TOKEN: ${{ secrets.CODECOV_ORG_TOKEN }} REMIX_API_URL: ${{ secrets.CODECOV_API_URL }} ROLLUP_UPLOAD_TOKEN: ${{ secrets.CODECOV_ORG_TOKEN_STAGING }} diff --git a/examples/oidc/.eslintrc.cjs b/examples/oidc/.eslintrc.cjs new file mode 100644 index 00000000..d6c95379 --- /dev/null +++ b/examples/oidc/.eslintrc.cjs @@ -0,0 +1,18 @@ +module.exports = { + root: true, + env: { browser: true, es2020: true }, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:react-hooks/recommended', + ], + ignorePatterns: ['dist', '.eslintrc.cjs'], + parser: '@typescript-eslint/parser', + plugins: ['react-refresh'], + rules: { + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, +} diff --git a/examples/oidc/.gitignore b/examples/oidc/.gitignore new file mode 100644 index 00000000..a547bf36 --- /dev/null +++ b/examples/oidc/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/examples/oidc/README.md b/examples/oidc/README.md new file mode 100644 index 00000000..1ebe379f --- /dev/null +++ b/examples/oidc/README.md @@ -0,0 +1,27 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: + +- Configure the top-level `parserOptions` property like this: + +```js + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + project: ['./tsconfig.json', './tsconfig.node.json'], + tsconfigRootDir: __dirname, + }, +``` + +- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` +- Optionally add `plugin:@typescript-eslint/stylistic-type-checked` +- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list diff --git a/examples/oidc/index.html b/examples/oidc/index.html new file mode 100644 index 00000000..e4b78eae --- /dev/null +++ b/examples/oidc/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + React + TS + + +
+ + + diff --git a/examples/oidc/package.json b/examples/oidc/package.json new file mode 100644 index 00000000..73989417 --- /dev/null +++ b/examples/oidc/package.json @@ -0,0 +1,36 @@ +{ + "name": "@codecov/example-oidc-app", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@codecov/vite-plugin": "workspace:^", + "@types/react": "^18.2.51", + "@types/react-dom": "^18.2.18", + "@typescript-eslint/eslint-plugin": "^6.20.0", + "@typescript-eslint/parser": "^6.20.0", + "@vitejs/plugin-react": "^4.2.1", + "eslint": "^8.56.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.5", + "rollup": "^4.9.6", + "typescript": "^5.3.3", + "vite": "^5.2.10" + }, + "volta": { + "extends": "../../package.json" + }, + "engines": { + "node": ">=18.0.0" + } +} diff --git a/examples/oidc/public/vite.svg b/examples/oidc/public/vite.svg new file mode 100644 index 00000000..e7b8dfb1 --- /dev/null +++ b/examples/oidc/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/oidc/src/App.css b/examples/oidc/src/App.css new file mode 100644 index 00000000..b9d355df --- /dev/null +++ b/examples/oidc/src/App.css @@ -0,0 +1,42 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/examples/oidc/src/App.tsx b/examples/oidc/src/App.tsx new file mode 100644 index 00000000..345e0828 --- /dev/null +++ b/examples/oidc/src/App.tsx @@ -0,0 +1,44 @@ +import { useState, lazy, Suspense } from "react"; +import reactLogo from "./assets/react.svg"; +import viteLogo from "/vite.svg"; +import "./App.css"; + +const IndexedLazyComponent = lazy(() => import("./IndexedLazyComponent")); +const LazyComponent = lazy(() => import("./LazyComponent/LazyComponent")); + +function App() { + const [count, setCount] = useState(0); + + return ( + <> +
+ + Vite logo + + + React logo + +
+

Vite + React

+
+ +

+ Edit src/App.tsx and save to test HMR +

+
+

+ Click on the Vite and React logos to learn more +

+ + + + + + + + ); +} + +export default App; diff --git a/examples/oidc/src/IndexedLazyComponent/IndexedLazyComponent.tsx b/examples/oidc/src/IndexedLazyComponent/IndexedLazyComponent.tsx new file mode 100644 index 00000000..6686dc58 --- /dev/null +++ b/examples/oidc/src/IndexedLazyComponent/IndexedLazyComponent.tsx @@ -0,0 +1,7 @@ +export default function IndexedLazyComponent() { + return ( +
+

Indexed Lazy Component

+
+ ); +} diff --git a/examples/oidc/src/IndexedLazyComponent/index.ts b/examples/oidc/src/IndexedLazyComponent/index.ts new file mode 100644 index 00000000..1dd706b5 --- /dev/null +++ b/examples/oidc/src/IndexedLazyComponent/index.ts @@ -0,0 +1 @@ +export { default } from "./IndexedLazyComponent"; diff --git a/examples/oidc/src/LazyComponent/LazyComponent.tsx b/examples/oidc/src/LazyComponent/LazyComponent.tsx new file mode 100644 index 00000000..2a56c928 --- /dev/null +++ b/examples/oidc/src/LazyComponent/LazyComponent.tsx @@ -0,0 +1,14 @@ +import { lazy, Suspense } from "react"; + +const IndexedLazyComponent = lazy(() => import("../IndexedLazyComponent")); + +export default function LazyComponent() { + return ( +
+

Lazy Component

+ + + +
+ ); +} diff --git a/examples/oidc/src/assets/react.svg b/examples/oidc/src/assets/react.svg new file mode 100644 index 00000000..6c87de9b --- /dev/null +++ b/examples/oidc/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/oidc/src/index.css b/examples/oidc/src/index.css new file mode 100644 index 00000000..2c3fac68 --- /dev/null +++ b/examples/oidc/src/index.css @@ -0,0 +1,69 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/examples/oidc/src/main.tsx b/examples/oidc/src/main.tsx new file mode 100644 index 00000000..f25366e5 --- /dev/null +++ b/examples/oidc/src/main.tsx @@ -0,0 +1,10 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./App.tsx"; +import "./index.css"; + +ReactDOM.createRoot(document.getElementById("root")!).render( + + + , +); diff --git a/examples/oidc/src/vite-env.d.ts b/examples/oidc/src/vite-env.d.ts new file mode 100644 index 00000000..11f02fe2 --- /dev/null +++ b/examples/oidc/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/oidc/tsconfig.json b/examples/oidc/tsconfig.json new file mode 100644 index 00000000..de3bc503 --- /dev/null +++ b/examples/oidc/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }], +} diff --git a/examples/oidc/tsconfig.node.json b/examples/oidc/tsconfig.node.json new file mode 100644 index 00000000..42872c59 --- /dev/null +++ b/examples/oidc/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/examples/oidc/vite.config.ts b/examples/oidc/vite.config.ts new file mode 100644 index 00000000..c28c0df7 --- /dev/null +++ b/examples/oidc/vite.config.ts @@ -0,0 +1,28 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import { codecovVitePlugin } from "@codecov/vite-plugin"; + +// https://vitejs.dev/config/ +export default defineConfig({ + build: { + rollupOptions: { + output: { + assetFileNames: "[name].[hash].js", + chunkFileNames: "[name]-[hash].js", + }, + }, + }, + plugins: [ + react(), + codecovVitePlugin({ + enableBundleAnalysis: true, + bundleName: "@codecov/example-oidc-app", + apiUrl: process.env.OIDC_API_URL, + gitService: "github", + oidc: { + useGitHubOIDC: true, + }, + debug: true, + }), + ], +}); diff --git a/packages/bundler-plugin-core/package.json b/packages/bundler-plugin-core/package.json index b4c5a73a..6c31bfbf 100644 --- a/packages/bundler-plugin-core/package.json +++ b/packages/bundler-plugin-core/package.json @@ -39,6 +39,7 @@ "generate:typedoc": "typedoc --options ./typedoc.json" }, "dependencies": { + "@actions/core": "^1.10.1", "@actions/github": "^6.0.0", "chalk": "4.1.2", "semver": "^7.5.4", diff --git a/packages/bundler-plugin-core/src/errors/BadOIDCServiceError.ts b/packages/bundler-plugin-core/src/errors/BadOIDCServiceError.ts new file mode 100644 index 00000000..14538660 --- /dev/null +++ b/packages/bundler-plugin-core/src/errors/BadOIDCServiceError.ts @@ -0,0 +1,5 @@ +export class BadOIDCServiceError extends Error { + constructor(msg: string, options?: ErrorOptions) { + super(msg, options); + } +} diff --git a/packages/bundler-plugin-core/src/errors/BadResponseError.ts b/packages/bundler-plugin-core/src/errors/BadResponseError.ts index c218a7ab..f5f69cc9 100644 --- a/packages/bundler-plugin-core/src/errors/BadResponseError.ts +++ b/packages/bundler-plugin-core/src/errors/BadResponseError.ts @@ -1,5 +1,5 @@ export class BadResponseError extends Error { - constructor(msg: string) { - super(msg); + constructor(msg: string, options?: ErrorOptions) { + super(msg, options); } } diff --git a/packages/bundler-plugin-core/src/errors/FailedFetchError.ts b/packages/bundler-plugin-core/src/errors/FailedFetchError.ts index 07b5b001..b602da42 100644 --- a/packages/bundler-plugin-core/src/errors/FailedFetchError.ts +++ b/packages/bundler-plugin-core/src/errors/FailedFetchError.ts @@ -1,5 +1,5 @@ export class FailedFetchError extends Error { - constructor(msg: string) { - super(msg); + constructor(msg: string, options?: ErrorOptions) { + super(msg, options); } } diff --git a/packages/bundler-plugin-core/src/errors/FailedOIDCFetchError.ts b/packages/bundler-plugin-core/src/errors/FailedOIDCFetchError.ts new file mode 100644 index 00000000..e8a35158 --- /dev/null +++ b/packages/bundler-plugin-core/src/errors/FailedOIDCFetchError.ts @@ -0,0 +1,5 @@ +export class FailedOIDCFetchError extends Error { + constructor(msg: string, options?: ErrorOptions) { + super(msg, options); + } +} diff --git a/packages/bundler-plugin-core/src/errors/FailedUploadError.ts b/packages/bundler-plugin-core/src/errors/FailedUploadError.ts index c228c35d..35d6088a 100644 --- a/packages/bundler-plugin-core/src/errors/FailedUploadError.ts +++ b/packages/bundler-plugin-core/src/errors/FailedUploadError.ts @@ -1,5 +1,5 @@ export class FailedUploadError extends Error { - constructor(msg: string) { - super(msg); + constructor(msg: string, options?: ErrorOptions) { + super(msg, options); } } diff --git a/packages/bundler-plugin-core/src/errors/InvalidSlugError.ts b/packages/bundler-plugin-core/src/errors/InvalidSlugError.ts index c33ac778..1b1a7f47 100644 --- a/packages/bundler-plugin-core/src/errors/InvalidSlugError.ts +++ b/packages/bundler-plugin-core/src/errors/InvalidSlugError.ts @@ -1,5 +1,5 @@ export class InvalidSlugError extends Error { - constructor(msg: string) { - super(msg); + constructor(msg: string, options?: ErrorOptions) { + super(msg, options); } } diff --git a/packages/bundler-plugin-core/src/errors/NoUploadTokenError.ts b/packages/bundler-plugin-core/src/errors/NoUploadTokenError.ts index 4b342015..e627ab2f 100644 --- a/packages/bundler-plugin-core/src/errors/NoUploadTokenError.ts +++ b/packages/bundler-plugin-core/src/errors/NoUploadTokenError.ts @@ -1,5 +1,5 @@ export class NoUploadTokenError extends Error { - constructor(msg: string) { - super(msg); + constructor(msg: string, options?: ErrorOptions) { + super(msg, options); } } diff --git a/packages/bundler-plugin-core/src/errors/UndefinedGitServiceError.ts b/packages/bundler-plugin-core/src/errors/UndefinedGitServiceError.ts index baf3f8a7..aa5c7ad4 100644 --- a/packages/bundler-plugin-core/src/errors/UndefinedGitServiceError.ts +++ b/packages/bundler-plugin-core/src/errors/UndefinedGitServiceError.ts @@ -1,5 +1,5 @@ export class UndefinedGitServiceError extends Error { - constructor(msg: string) { - super(msg); + constructor(msg: string, options?: ErrorOptions) { + super(msg, options); } } diff --git a/packages/bundler-plugin-core/src/errors/UploadLimitReachedError.ts b/packages/bundler-plugin-core/src/errors/UploadLimitReachedError.ts index b4cb205b..387b5efa 100644 --- a/packages/bundler-plugin-core/src/errors/UploadLimitReachedError.ts +++ b/packages/bundler-plugin-core/src/errors/UploadLimitReachedError.ts @@ -1,5 +1,5 @@ export class UploadLimitReachedError extends Error { - constructor(msg: string) { - super(msg); + constructor(msg: string, options?: ErrorOptions) { + super(msg, options); } } diff --git a/packages/bundler-plugin-core/src/types.ts b/packages/bundler-plugin-core/src/types.ts index 76cdc736..c509731d 100644 --- a/packages/bundler-plugin-core/src/types.ts +++ b/packages/bundler-plugin-core/src/types.ts @@ -89,8 +89,11 @@ export interface Options { gitService?: ValidGitService; /** - * The upload token to use for uploading the bundle analysis information. This field is - * **required** for uploading bundle analysis information in private repositories. + * The upload token to use for uploading the bundle analysis information. + * + * This field is **required** for uploading bundle analysis information in private repositories. + * Alternatively if you're using GitHub Actions and have configured OIDC authentication you can + * omit this field, and enable the `oidc.useGitHubOIDC` option. * * This value can either be a global upload token or a repo token. * - The global upload token can be found under the organization settings page. @@ -139,6 +142,31 @@ export interface Options { * Defaults to `false` */ dryRun?: boolean; + + /** Options for OIDC authentication. */ + oidc?: { + /** + * When using GitHub Actions this option can be enabled to use OIDC authentication, which + * removes the requirement for an upload token. + * + * [OpenID Connect + * (OIDC)](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect) + * is **required** to be configured in order to use GitHub OIDC. + * + * Defaults to `false` + */ + useGitHubOIDC: boolean; + + /** + * The OIDC audience to use for authentication. + * + * If you're using a self hosted version of Codecov, you will need to provide the audience for + * the OIDC token. + * + * Defaults to `https://codecov.io` + */ + gitHubOIDCTokenAudience?: string; + }; } export type BundleAnalysisUploadPlugin = ( diff --git a/packages/bundler-plugin-core/src/utils/Output.ts b/packages/bundler-plugin-core/src/utils/Output.ts index 2ed99fed..7584f80f 100644 --- a/packages/bundler-plugin-core/src/utils/Output.ts +++ b/packages/bundler-plugin-core/src/utils/Output.ts @@ -19,6 +19,10 @@ class Output { retryCount: number; enableBundleAnalysis: boolean; uploadToken?: string; + oidc?: { + useGitHubOIDC: boolean; + gitHubOIDCTokenAudience: string; + }; debug: boolean; gitService?: ValidGitService; originalBundleName: string; @@ -61,6 +65,7 @@ class Output { this.debug = userOptions.debug; this.gitService = userOptions.gitService; this.originalBundleName = userOptions.bundleName; + this.oidc = userOptions.oidc; if (userOptions.uploadOverrides) { this.branch = userOptions.uploadOverrides.branch; @@ -143,11 +148,12 @@ class Output { let url = ""; try { url = await getPreSignedURL({ - apiURL: this?.apiUrl ?? "https://api.codecov.io", - uploadToken: this?.uploadToken, + apiUrl: this.apiUrl, + uploadToken: this.uploadToken, + gitService: this.gitService, + oidc: this.oidc, + retryCount: this.retryCount, serviceParams: provider, - retryCount: this?.retryCount, - gitService: this?.gitService, }); } catch (error) { return; diff --git a/packages/bundler-plugin-core/src/utils/__tests__/getPreSignedURL.test.ts b/packages/bundler-plugin-core/src/utils/__tests__/getPreSignedURL.test.ts index 0cae4a12..43258ef6 100644 --- a/packages/bundler-plugin-core/src/utils/__tests__/getPreSignedURL.test.ts +++ b/packages/bundler-plugin-core/src/utils/__tests__/getPreSignedURL.test.ts @@ -19,6 +19,21 @@ import { FailedFetchError } from "../../errors/FailedFetchError.ts"; import { NoUploadTokenError } from "../../errors/NoUploadTokenError.ts"; import { UploadLimitReachedError } from "../../errors/UploadLimitReachedError.ts"; import { UndefinedGitServiceError } from "../../errors/UndefinedGitServiceError.ts"; +import { BadOIDCServiceError } from "src/errors/BadOIDCServiceError.ts"; +import { FailedOIDCFetchError } from "src/errors/FailedOIDCFetchError.ts"; + +const mocks = vi.hoisted(() => ({ + getIDToken: vi.fn().mockReturnValue(""), +})); + +vi.mock("@actions/core", async (importOriginal) => { + // eslint-disable-next-line @typescript-eslint/consistent-type-imports + const original = await importOriginal(); + return { + ...original, + getIDToken: mocks.getIDToken, + }; +}); const server = setupServer(); @@ -40,16 +55,30 @@ interface SetupArgs { data?: object; retryCount?: number; sendError?: boolean; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + getIDTokenValue?: any; } describe("getPreSignedURL", () => { let consoleSpy: MockInstance; const requestBodyMock = vi.fn(); + const requestTokenMock = vi.fn(); const spawnSync = td.replace(childProcess, "spawnSync"); - function setup({ status = 200, data = {}, sendError = false }: SetupArgs) { + function setup({ + status = 200, + data = {}, + sendError = false, + getIDTokenValue, + }: SetupArgs) { consoleSpy = vi.spyOn(console, "log").mockImplementation(() => null); + if (getIDTokenValue instanceof Error) { + mocks.getIDToken.mockRejectedValue(getIDTokenValue); + } else { + mocks.getIDToken.mockReturnValue(getIDTokenValue); + } + server.use( http.post( "http://localhost/upload/bundle_analysis/v1", @@ -57,8 +86,14 @@ describe("getPreSignedURL", () => { if (sendError) { return HttpResponse.error(); } + const requestBody = await request.json(); requestBodyMock(requestBody); + + if (request.headers.get("Authorization")) { + requestTokenMock(request.headers.get("Authorization")); + } + return HttpResponse.json(data, { status }); }, ), @@ -82,12 +117,12 @@ describe("getPreSignedURL", () => { }); const url = await getPreSignedURL({ - apiURL: "http://localhost", - uploadToken: "super-cool-token", + apiUrl: "http://localhost", + uploadToken: "cool-upload-token", + retryCount: 0, serviceParams: { commit: "123", }, - retryCount: 0, }); expect(url).toEqual("http://example.com"); @@ -120,12 +155,12 @@ describe("getPreSignedURL", () => { }); const url = await getPreSignedURL({ - apiURL: "http://localhost", + apiUrl: "http://localhost", + retryCount: 0, serviceParams: { commit: "123", branch: "owner:branch", }, - retryCount: 0, }); expect(url).toEqual("http://example.com"); @@ -144,13 +179,13 @@ describe("getPreSignedURL", () => { }); const url = await getPreSignedURL({ - apiURL: "http://localhost", + apiUrl: "http://localhost", + retryCount: 0, + gitService: "github_enterprise", serviceParams: { commit: "123", branch: "owner:branch", }, - retryCount: 0, - gitService: "github_enterprise", }); expect(url).toEqual("http://example.com"); @@ -162,6 +197,31 @@ describe("getPreSignedURL", () => { }); }); }); + + describe("using oidc and github actions", () => { + it("returns the pre-signed URL", async () => { + setup({ + data: { url: "http://example.com" }, + getIDTokenValue: "cool-token", + }); + + const url = await getPreSignedURL({ + apiUrl: "http://localhost", + retryCount: 0, + oidc: { + gitHubOIDCTokenAudience: "http://localhost", + useGitHubOIDC: true, + }, + serviceParams: { + commit: "123", + service: "github-actions", + }, + }); + + expect(url).toEqual("http://example.com"); + expect(requestTokenMock).toHaveBeenCalledWith("token cool-token"); + }); + }); }); }); @@ -176,12 +236,13 @@ describe("getPreSignedURL", () => { let error; try { await getPreSignedURL({ - apiURL: "http://localhost", + apiUrl: "http://localhost", + retryCount: 0, + gitService: "github", serviceParams: { commit: "123", branch: "main", }, - retryCount: 0, }); } catch (e) { error = e; @@ -224,12 +285,12 @@ describe("getPreSignedURL", () => { let error; try { await getPreSignedURL({ - apiURL: "http://localhost", + apiUrl: "http://localhost", + retryCount: 0, serviceParams: { commit: "123", branch: "owner:branch", }, - retryCount: 0, }); } catch (e) { error = e; @@ -255,12 +316,12 @@ describe("getPreSignedURL", () => { let error; try { await getPreSignedURL({ - apiURL: "http://localhost", + apiUrl: "http://localhost", uploadToken: "cool-upload-token", + retryCount: 0, serviceParams: { commit: "123", }, - retryCount: 0, }); } catch (e) { error = e; @@ -281,12 +342,12 @@ describe("getPreSignedURL", () => { let error; try { await getPreSignedURL({ - apiURL: "http://localhost", - uploadToken: "super-cool-token", + apiUrl: "http://localhost", + uploadToken: "cool-upload-token", + retryCount: 0, serviceParams: { commit: "123", }, - retryCount: 0, }); } catch (e) { error = e; @@ -317,12 +378,12 @@ describe("getPreSignedURL", () => { let error; try { await getPreSignedURL({ - apiURL: "http://localhost", - uploadToken: "super-cool-token", + apiUrl: "http://localhost", + uploadToken: "cool-upload-token", + retryCount: 0, serviceParams: { commit: "123", }, - retryCount: 0, }); } catch (e) { error = e; @@ -343,12 +404,12 @@ describe("getPreSignedURL", () => { let error; try { await getPreSignedURL({ - apiURL: "http://localhost", - uploadToken: "super-cool-token", + apiUrl: "http://localhost", + uploadToken: "cool-upload-token", + retryCount: 0, serviceParams: { commit: "123", }, - retryCount: 0, }); } catch (e) { error = e; @@ -379,12 +440,12 @@ describe("getPreSignedURL", () => { let error; try { await getPreSignedURL({ - apiURL: "http://localhost", - uploadToken: "super-cool-token", + apiUrl: "http://localhost", + uploadToken: "cool-upload-token", + retryCount: 0, serviceParams: { commit: "123", }, - retryCount: 0, }); } catch (e) { error = e; @@ -394,5 +455,65 @@ describe("getPreSignedURL", () => { expect(error).toBeInstanceOf(FailedFetchError); }); }); + + describe("using oidc and not github actions", () => { + it("throws an error", async () => { + setup({ + data: { url: "http://example.com" }, + getIDTokenValue: "cool-token", + }); + + let error; + try { + await getPreSignedURL({ + apiUrl: "http://localhost", + retryCount: 0, + oidc: { + gitHubOIDCTokenAudience: "http://localhost", + useGitHubOIDC: true, + }, + serviceParams: { + commit: "123", + service: "local", + }, + }); + } catch (e) { + error = e; + } + + expect(consoleSpy).toHaveBeenCalled(); + expect(error).toBeInstanceOf(BadOIDCServiceError); + }); + }); + + describe("using oidc and getIDToken fails", () => { + it("throws an error", async () => { + setup({ + data: { url: "http://example.com" }, + getIDTokenValue: new Error("Failed to get token"), + }); + + let error; + try { + await getPreSignedURL({ + apiUrl: "http://localhost", + retryCount: 0, + oidc: { + gitHubOIDCTokenAudience: "http://localhost", + useGitHubOIDC: true, + }, + serviceParams: { + commit: "123", + service: "github-actions", + }, + }); + } catch (e) { + error = e; + } + + expect(consoleSpy).toHaveBeenCalled(); + expect(error).toBeInstanceOf(FailedOIDCFetchError); + }); + }); }); }); diff --git a/packages/bundler-plugin-core/src/utils/__tests__/normalizeOptions.test.ts b/packages/bundler-plugin-core/src/utils/__tests__/normalizeOptions.test.ts index ec4dd48d..a5a02c40 100644 --- a/packages/bundler-plugin-core/src/utils/__tests__/normalizeOptions.test.ts +++ b/packages/bundler-plugin-core/src/utils/__tests__/normalizeOptions.test.ts @@ -62,6 +62,10 @@ const tests: Test[] = [ }, debug: true, gitService: "bitbucket", + oidc: { + useGitHubOIDC: true, + gitHubOIDCTokenAudience: "https://codecov.io", + }, }, }, expected: { @@ -83,6 +87,10 @@ const tests: Test[] = [ }, debug: true, gitService: "bitbucket", + oidc: { + useGitHubOIDC: true, + gitHubOIDCTokenAudience: "https://codecov.io", + }, }, }, }, @@ -120,6 +128,12 @@ const tests: Test[] = [ debug: "true", // @ts-expect-error - testing invalid input gitService: 123, + oidc: { + // @ts-expect-error - testing invalid input + useGitHubOIDC: "true", + // @ts-expect-error - testing invalid input + gitHubOIDCTokenAudience: 123, + }, }, }, expected: { @@ -139,6 +153,8 @@ const tests: Test[] = [ "`slug` must be a string.", "`debug` must be a boolean.", "`gitService` must be a valid git service.", + "`useGitHubOIDC` must be a boolean.", + "`gitHubOIDCTokenAudience` must be a string.", ], }, }, diff --git a/packages/bundler-plugin-core/src/utils/fetchWithRetry.ts b/packages/bundler-plugin-core/src/utils/fetchWithRetry.ts index 68f9bb89..6467be66 100644 --- a/packages/bundler-plugin-core/src/utils/fetchWithRetry.ts +++ b/packages/bundler-plugin-core/src/utils/fetchWithRetry.ts @@ -1,18 +1,18 @@ import { BadResponseError } from "../errors/BadResponseError"; -import { DEFAULT_RETRY_DELAY } from "./constants"; +import { DEFAULT_RETRY_DELAY, DEFAULT_RETRY_COUNT } from "./constants"; import { delay } from "./delay"; import { debug, red } from "./logging"; interface FetchWithRetryArgs { url: string; - retryCount: number; + retryCount?: number; requestData: RequestInit; name?: string; } export const fetchWithRetry = async ({ url, - retryCount, + retryCount = DEFAULT_RETRY_COUNT, requestData, name, }: FetchWithRetryArgs) => { diff --git a/packages/bundler-plugin-core/src/utils/getPreSignedURL.ts b/packages/bundler-plugin-core/src/utils/getPreSignedURL.ts index 6a092d5b..41e7200a 100644 --- a/packages/bundler-plugin-core/src/utils/getPreSignedURL.ts +++ b/packages/bundler-plugin-core/src/utils/getPreSignedURL.ts @@ -1,21 +1,27 @@ +import * as Core from "@actions/core"; import { z } from "zod"; import { FailedFetchError } from "../errors/FailedFetchError.ts"; import { UploadLimitReachedError } from "../errors/UploadLimitReachedError.ts"; import { type ProviderServiceParams } from "../types.ts"; -import { DEFAULT_RETRY_COUNT } from "./constants.ts"; import { fetchWithRetry } from "./fetchWithRetry.ts"; import { green, red } from "./logging.ts"; import { preProcessBody } from "./preProcessBody.ts"; import { NoUploadTokenError } from "../errors/NoUploadTokenError.ts"; import { findGitService } from "./findGitService.ts"; import { UndefinedGitServiceError } from "../errors/UndefinedGitServiceError.ts"; +import { FailedOIDCFetchError } from "../errors/FailedOIDCFetchError.ts"; +import { BadOIDCServiceError } from "../errors/BadOIDCServiceError.ts"; interface GetPreSignedURLArgs { - apiURL: string; + apiUrl: string; uploadToken?: string; serviceParams: Partial; retryCount?: number; gitService?: string; + oidc?: { + useGitHubOIDC: boolean; + gitHubOIDCTokenAudience: string; + }; } type RequestBody = Record; @@ -24,15 +30,16 @@ const PreSignedURLSchema = z.object({ url: z.string(), }); +const API_ENDPOINT = "/upload/bundle_analysis/v1"; + export const getPreSignedURL = async ({ - apiURL, + apiUrl, uploadToken, serviceParams, - retryCount = DEFAULT_RETRY_COUNT, + retryCount, gitService, + oidc, }: GetPreSignedURLArgs) => { - const url = `${apiURL}/upload/bundle_analysis/v1`; - const headers = new Headers({ "Content-Type": "application/json", }); @@ -55,6 +62,30 @@ export const getPreSignedURL = async ({ requestBody.git_service = foundGitService; } + } else if (oidc?.useGitHubOIDC && Core) { + if (serviceParams?.service !== "github-actions") { + red("OIDC is only supported for GitHub Actions"); + throw new BadOIDCServiceError( + "OIDC is only supported for GitHub Actions", + ); + } + + let token = ""; + try { + token = await Core.getIDToken(oidc.gitHubOIDCTokenAudience); + } catch (err: unknown) { + if (err instanceof Error) { + red( + `Failed to get OIDC token with url:\`${oidc.gitHubOIDCTokenAudience}\`. ${err.message}`, + ); + throw new FailedOIDCFetchError( + `Failed to get OIDC token with url: \`${oidc.gitHubOIDCTokenAudience}\`. ${err.message}`, + { cause: err }, + ); + } + } + + headers.set("Authorization", `token ${token}`); } else if (uploadToken) { headers.set("Authorization", `token ${uploadToken}`); } else { @@ -65,8 +96,8 @@ export const getPreSignedURL = async ({ let response: Response; try { response = await fetchWithRetry({ - url, retryCount, + url: `${apiUrl}${API_ENDPOINT}`, name: "`get-pre-signed-url`", requestData: { method: "POST", @@ -76,7 +107,7 @@ export const getPreSignedURL = async ({ }); } catch (e) { red("Failed to fetch pre-signed URL"); - throw new FailedFetchError("Failed to fetch pre-signed URL"); + throw new FailedFetchError("Failed to fetch pre-signed URL", { cause: e }); } if (response.status === 429) { @@ -94,7 +125,9 @@ export const getPreSignedURL = async ({ data = await response.json(); } catch (e) { red("Failed to parse pre-signed URL body"); - throw new FailedFetchError("Failed to parse pre-signed URL body"); + throw new FailedFetchError("Failed to parse pre-signed URL body", { + cause: e, + }); } const parsedData = PreSignedURLSchema.safeParse(data); diff --git a/packages/bundler-plugin-core/src/utils/normalizeOptions.ts b/packages/bundler-plugin-core/src/utils/normalizeOptions.ts index 89cf7e30..6dbedd88 100644 --- a/packages/bundler-plugin-core/src/utils/normalizeOptions.ts +++ b/packages/bundler-plugin-core/src/utils/normalizeOptions.ts @@ -56,6 +56,28 @@ const UploadOverridesSchema = z.object({ .optional(), }); +const OIDCSchema = z.object( + { + useGitHubOIDC: z + .boolean({ + invalid_type_error: "`useGitHubOIDC` must be a boolean.", + }) + .default(false), + /** + * Following along with how we handle this in our GH Action. + * + * See: https://github.com/codecov/codecov-action/blob/main/src/buildExec.ts#L53-L58 + */ + gitHubOIDCTokenAudience: z + .string({ + invalid_type_error: "`gitHubOIDCTokenAudience` must be a string.", + }) + .optional() + .default("https://codecov.io"), + }, + { invalid_type_error: "`oidc` must be an object." }, +); + const optionsSchemaFactory = (options: Options) => z.object({ apiUrl: z @@ -123,6 +145,7 @@ const optionsSchemaFactory = (options: Options) => { invalid_type_error: "`gitService` must be a valid git service." }, ) .optional(), + oidc: OIDCSchema.optional(), }); interface NormalizedOptionsFailure { diff --git a/packages/bundler-plugin-core/src/utils/providers/__tests__/index.test.ts b/packages/bundler-plugin-core/src/utils/providers/__tests__/index.test.ts index 40cb90e2..ccb89f0f 100644 --- a/packages/bundler-plugin-core/src/utils/providers/__tests__/index.test.ts +++ b/packages/bundler-plugin-core/src/utils/providers/__tests__/index.test.ts @@ -9,9 +9,12 @@ import { Output } from "../../Output.ts"; import { providerList } from "../index.ts"; const server = setupServer( - http.get("https://api.github.com/repos/:org/:repo/actions/runs//jobs", () => { - return HttpResponse.json({}, { status: 200 }); - }), + http.get( + "https://api.github.com/repos/:org/:repo/actions/runs/:id/jobs", + () => { + return HttpResponse.json({}, { status: 200 }); + }, + ), ); beforeAll(() => { diff --git a/packages/bundler-plugin-core/src/utils/uploadStats.ts b/packages/bundler-plugin-core/src/utils/uploadStats.ts index 3067ba4d..a1ab59bf 100644 --- a/packages/bundler-plugin-core/src/utils/uploadStats.ts +++ b/packages/bundler-plugin-core/src/utils/uploadStats.ts @@ -3,7 +3,6 @@ import { ReadableStream, TextEncoderStream } from "node:stream/web"; import { FailedUploadError } from "../errors/FailedUploadError"; import { green, red } from "./logging"; import { fetchWithRetry } from "./fetchWithRetry"; -import { DEFAULT_RETRY_COUNT } from "./constants"; import { UploadLimitReachedError } from "../errors/UploadLimitReachedError"; import { FailedFetchError } from "../errors/FailedFetchError"; @@ -18,7 +17,7 @@ export async function uploadStats({ message, bundleName, preSignedUrl, - retryCount = DEFAULT_RETRY_COUNT, + retryCount, }: UploadStatsArgs) { const iterator = message[Symbol.iterator](); const stream = new ReadableStream({ diff --git a/packages/nextjs-webpack-plugin/README.md b/packages/nextjs-webpack-plugin/README.md index 2f49b4aa..f74a2f89 100644 --- a/packages/nextjs-webpack-plugin/README.md +++ b/packages/nextjs-webpack-plugin/README.md @@ -110,6 +110,32 @@ export default { }; ``` +## OIDC Configuration Example + +For users with [OpenID Connect (OIDC) enabled](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect), setting the `uploadToken` is not necessary. You can use OIDC with the `oidc` configuration as following. + +```typescript +// next.config.mjs +import { codecovNextJSWebpackPlugin } from "@codecov/nextjs-webpack-plugin"; + +export default { + webpack: (config, options) => { + config.plugins.push( + codecovNextJSWebpackPlugin({ + enableBundleAnalysis: true, + bundleName: "example-nextjs-webpack-bundle", + webpack: options.webpack, + oidc: { + useGitHubOIDC: true, + }, + }), + ); + + return config; + }, +}; +``` + ## More information - [NextJS Config Docs](https://nextjs.org/docs/app/api-reference/next-config-js) diff --git a/packages/nuxt-plugin/README.md b/packages/nuxt-plugin/README.md index 908b4111..c00e1b3a 100644 --- a/packages/nuxt-plugin/README.md +++ b/packages/nuxt-plugin/README.md @@ -119,6 +119,34 @@ export default defineNuxtConfig({ }); ``` +## OIDC Configuration Example + +For users with [OpenID Connect (OIDC) enabled](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect), setting the `uploadToken` is not necessary. You can use OIDC with the `oidc` configuration as following. + +```typescript +// nuxt.config.ts +import { defineNuxtConfig } from "nuxt/config"; + +export default defineNuxtConfig({ + devtools: { enabled: true }, + // Ensure that the builder is set to "vite" + builder: "vite", + // Ensure that the plugin is added to the modules array + modules: [ + [ + "@codecov/nuxt-plugin", + { + enableBundleAnalysis: true, + bundleName: "nuxt-bundle-analysis", + oidc: { + useGitHubOIDC: true, + }, + }, + ], + ], +}); +``` + ## More information - [Codecov Documentation](https://docs.codecov.com/docs) diff --git a/packages/remix-vite-plugin/README.md b/packages/remix-vite-plugin/README.md index f4b5f791..c2adb3a3 100644 --- a/packages/remix-vite-plugin/README.md +++ b/packages/remix-vite-plugin/README.md @@ -116,6 +116,33 @@ export default defineConfig({ }); ``` +## OIDC Configuration Example + +For users with [OpenID Connect (OIDC) enabled](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect), setting the `uploadToken` is not necessary. You can use OIDC with the `oidc` configuration as following. + +```ts +// vite.config.ts +import { vitePlugin as remix } from "@remix-run/dev"; +import { defineConfig } from "vite"; +import tsconfigPaths from "vite-tsconfig-paths"; +import { codecovRemixPlugin } from "@codecov/remix-vite-plugin"; + +export default defineConfig({ + plugins: [ + remix(), + tsconfigPaths() + // Put the Codecov Remix plugin after all other plugins + codecovRemixPlugin({ + enableBundleAnalysis: true, + bundleName: "example-remix-bundle", + oidc: { + useGitHubOIDC: true, + }, + }), + ], +}); +``` + ## More information - [Codecov Documentation](https://docs.codecov.com/docs) diff --git a/packages/rollup-plugin/README.md b/packages/rollup-plugin/README.md index d709299f..3bcdc2ce 100644 --- a/packages/rollup-plugin/README.md +++ b/packages/rollup-plugin/README.md @@ -101,6 +101,29 @@ export default defineConfig({ }); ``` +## OIDC Configuration Example + +For users with [OpenID Connect (OIDC) enabled](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect), setting the `uploadToken` is not necessary. You can use OIDC with the `oidc` configuration as following. + +```js +// rollup.config.js +import { defineConfig } from "rollup"; +import { codecovRollupPlugin } from "@codecov/rollup-plugin"; + +export default defineConfig({ + plugins: [ + // Put the Codecov rollup plugin after all other plugins + codecovRollupPlugin({ + enableBundleAnalysis: true, + bundleName: "example-rollup-bundle", + oidc: { + useGitHubOIDC: true, + }, + }), + ], +}); +``` + ## More information - [Rollup Config Docs](https://codecov.github.io/codecov-javascript-bundler-plugins/modules/_codecov_rollup_plugin.html) diff --git a/packages/solidstart-plugin/README.md b/packages/solidstart-plugin/README.md index 2549ea32..008a63da 100644 --- a/packages/solidstart-plugin/README.md +++ b/packages/solidstart-plugin/README.md @@ -116,6 +116,33 @@ export default defineConfig({ }); ``` +## OIDC Configuration Example + +For users with [OpenID Connect (OIDC) enabled](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect), setting the `uploadToken` is not necessary. You can use OIDC with the `oidc` configuration as following. + +```ts +// app.config.ts +import { defineConfig } from "@solidjs/start/config"; +import solidPlugin from "vite-plugin-solid"; +import { codecovSolidStartPlugin } from "@codecov/solidstart-plugin"; + +export default defineConfig({ + vite: { + plugins: [ + // Put the Codecov SolidStart plugin after all other plugins + solidPlugin(), + codecovSolidStartPlugin({ + enableBundleAnalysis: true, + bundleName: "example-solidstart-bundle", + oidc: { + useGitHubOIDC: true, + }, + }), + ], + }, +}); +``` + ## More information - [Codecov Documentation](https://docs.codecov.com/docs) diff --git a/packages/sveltekit-plugin/README.md b/packages/sveltekit-plugin/README.md index 21a616bd..a7af4c26 100644 --- a/packages/sveltekit-plugin/README.md +++ b/packages/sveltekit-plugin/README.md @@ -110,6 +110,31 @@ export default defineConfig({ }); ``` +## OIDC Configuration Example + +For users with [OpenID Connect (OIDC) enabled](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect), setting the `uploadToken` is not necessary. You can use OIDC with the `oidc` configuration as following. + +```ts +// vite.config.ts +import { sveltekit } from "@sveltejs/kit/vite"; +import { defineConfig } from "vite"; +import { codecovSvelteKitPlugin } from "@codecov/sveltekit-plugin"; + +export default defineConfig({ + plugins: [ + sveltekit(), + // Put the Codecov SvelteKit plugin after all other plugins + codecovSvelteKitPlugin({ + enableBundleAnalysis: true, + bundleName: "example-sveltekit-bundle", + oidc: { + useGitHubOIDC: true, + }, + }), + ], +}); +``` + ## More information - [Codecov Documentation](https://docs.codecov.com/docs) diff --git a/packages/vite-plugin/README.md b/packages/vite-plugin/README.md index 2d8df00d..d1e3e3f2 100644 --- a/packages/vite-plugin/README.md +++ b/packages/vite-plugin/README.md @@ -84,6 +84,8 @@ export default defineConfig({ This is the required way to use the plugin for private repositories. This configuration will automatically upload the bundle analysis to Codecov. +This configuration will automatically upload the bundle analysis to Codecov for public repositories. When an internal PR is created it will use the Codecov token set in your secrets, and if running from a forked PR, it will use the tokenless setting automatically. For setups not using GitHub Actions see the following [example](#public-repo-example---non-github-actions). For private repositories see the following [example](#private-repo-example). + ```js // vite.config.js import { defineConfig } from "vite"; @@ -101,6 +103,29 @@ export default defineConfig({ }); ``` +## OIDC Configuration Example + +For users with [OpenID Connect (OIDC) enabled](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect), setting the `uploadToken` is not necessary. You can use OIDC with the `oidc` configuration as following. + +```js +// vite.config.js +import { defineConfig } from "vite"; +import { codecovVitePlugin } from "@codecov/vite-plugin"; + +export default defineConfig({ + plugins: [ + // Put the Codecov vite plugin after all other plugins + codecovVitePlugin({ + enableBundleAnalysis: true, + bundleName: "example-vite-bundle", + oidc: { + useGitHubOIDC: true, + }, + }), + ], +}); +``` + ## More information - [Vite Config Docs](https://codecov.github.io/codecov-javascript-bundler-plugins/modules/_codecov_vite_plugin.html) diff --git a/packages/webpack-plugin/README.md b/packages/webpack-plugin/README.md index 7998994f..754399cf 100644 --- a/packages/webpack-plugin/README.md +++ b/packages/webpack-plugin/README.md @@ -119,6 +119,35 @@ module.exports = { }; ``` +## OIDC Configuration Example + +For users with [OpenID Connect (OIDC) enabled](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect), setting the `uploadToken` is not necessary. You can use OIDC with the `oidc` configuration as following. + +```js +// webpack.config.js +const path = require("path"); +const { codecovWebpackPlugin } = require("@codecov/webpack-plugin"); + +module.exports = { + entry: "./src/index.js", + mode: "production", + output: { + filename: "main.js", + path: path.resolve(__dirname, "dist"), + }, + plugins: [ + // Put the Codecov vite plugin after all other plugins + codecovWebpackPlugin({ + enableBundleAnalysis: true, + bundleName: "example-webpack-bundle", + oidc: { + useGitHubOIDC: true, + }, + }), + ], +}; +``` + ## More information - [Webpack Config Docs](https://codecov.github.io/codecov-javascript-bundler-plugins/modules/_codecov_webpack_plugin.html) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3a136ba1..77839ce1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -153,6 +153,52 @@ importers: specifier: workspace:^ version: link:../../packages/nuxt-plugin + examples/oidc: + dependencies: + react: + specifier: ^18.2.0 + version: 18.3.1 + react-dom: + specifier: ^18.2.0 + version: 18.3.1(react@18.3.1) + devDependencies: + '@codecov/vite-plugin': + specifier: workspace:^ + version: link:../../packages/vite-plugin + '@types/react': + specifier: ^18.2.51 + version: 18.3.3 + '@types/react-dom': + specifier: ^18.2.18 + version: 18.3.0 + '@typescript-eslint/eslint-plugin': + specifier: ^6.20.0 + version: 6.20.0(@typescript-eslint/parser@6.20.0(eslint@8.56.0)(typescript@5.4.5))(eslint@8.56.0)(typescript@5.4.5) + '@typescript-eslint/parser': + specifier: ^6.20.0 + version: 6.20.0(eslint@8.56.0)(typescript@5.4.5) + '@vitejs/plugin-react': + specifier: ^4.2.1 + version: 4.2.1(vite@5.2.10(@types/node@20.12.12)(terser@5.27.0)) + eslint: + specifier: ^8.56.0 + version: 8.56.0 + eslint-plugin-react-hooks: + specifier: ^4.6.0 + version: 4.6.0(eslint@8.56.0) + eslint-plugin-react-refresh: + specifier: ^0.4.5 + version: 0.4.5(eslint@8.56.0) + rollup: + specifier: ^4.9.6 + version: 4.9.6 + typescript: + specifier: ^5.3.3 + version: 5.4.5 + vite: + specifier: ^5.2.10 + version: 5.2.10(@types/node@20.12.12)(terser@5.27.0) + examples/remix: dependencies: '@remix-run/node': @@ -689,6 +735,9 @@ importers: packages/bundler-plugin-core: dependencies: + '@actions/core': + specifier: ^1.10.1 + version: 1.10.1 '@actions/github': specifier: ^6.0.0 version: 6.0.0 @@ -1164,6 +1213,9 @@ packages: resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} engines: {node: '>=0.10.0'} + '@actions/core@1.10.1': + resolution: {integrity: sha512-3lBR9EDAY+iYIpTnTIXmWcNbX3T2kCkAEQGIQx4NVQ0575nk2k3GRZDTPQG+vVtS2izSLmINlxXf0uLtnrTP+g==} + '@actions/github@6.0.0': resolution: {integrity: sha512-alScpSVnYmjNEXboZjarjukQEzgCRmjMv6Xj47fsdnqGS73bjJNDpiiXmp8jr0UZLdUB6d9jW63IcmddUP+l0g==} @@ -10844,6 +10896,10 @@ packages: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} + uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + uvu@0.5.6: resolution: {integrity: sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==} engines: {node: '>=8'} @@ -11506,6 +11562,11 @@ snapshots: '@aashutoshrathi/word-wrap@1.2.6': {} + '@actions/core@1.10.1': + dependencies: + '@actions/http-client': 2.2.1 + uuid: 8.3.2 + '@actions/github@6.0.0': dependencies: '@actions/http-client': 2.2.1 @@ -25010,6 +25071,8 @@ snapshots: utils-merge@1.0.1: {} + uuid@8.3.2: {} + uvu@0.5.6: dependencies: dequal: 2.0.3 diff --git a/tsconfig.json b/tsconfig.json index 9dfbd48c..834660a0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,7 +8,7 @@ "compilerOptions": { /* LANGUAGE COMPILATION OPTIONS */ "target": "ESNext", - "lib": ["DOM", "DOM.Iterable", "ES2021"], + "lib": ["DOM", "DOM.Iterable", "ES2022"], "module": "ESNext", "moduleResolution": "Bundler", "resolveJsonModule": true,