Skip to content

feat(providers): add providerMeta (#59)#198

Open
theoephraim wants to merge 14 commits into
unjs:mainfrom
theoephraim:feat/provider-metadata
Open

feat(providers): add providerMeta (#59)#198
theoephraim wants to merge 14 commits into
unjs:mainfrom
theoephraim:feat/provider-metadata

Conversation

@theoephraim

@theoephraim theoephraim commented Jul 8, 2026

Copy link
Copy Markdown

Closes #59 (partially — the git/build metadata half).

Obviously feel free to adapt as necessary. The important parts (all the provider specific detection and vars) should be easy to reuse however you see fit.

This comes from well worn code in varlock - although I highly doubt we've had users on all the more obscure providers.


What

Adds detectProviderMetadata() / providerMetadata, exposing normalized git and build metadata for the detected CI/CD or deployment provider:

import { providerMetadata } from "std-env";
// {
//   name: "github_actions",
//   repo: { owner: "unjs", name: "std-env" },
//   repoSlug: "unjs/std-env",
//   branch: "main",
//   commitSha: "abcdef1234567890...",
//   commitShaShort: "abcdef1",
//   isPR: false,
//   runId: "42",
//   buildUrl: "https://github.com/unjs/std-env/actions/runs/42",
//   actor: "octocat",
//   eventName: "push",
// }

Fields: name, repo {owner,name}, repoSlug, branch, commitSha, commitShaShort, isPR, prNumber, environment, buildUrl, runId, actor, eventName, workflowName. All optional except name — availability depends on the provider and event.

The extraction logic and per-provider env var mappings are adapted from @varlock/ci-env-info. It shows no npm downloads because while it is usable on its own, it's an internal package that ships inside Varlock, where this metadata powers builtin variables and is used to automatically infer the current environment (production / preview / development) from the CI/deploy context. So the mappings here are already battle-tested across real CI providers, not net-new.

Design notes

  • Separate module (src/provider-metadata.ts), out of the detection hot path. providers.ts stays free of per-provider extractor closures, so importing only isCI/provider doesn't pull in the metadata engine. Verified with esbuild — a { isCI, provider } bundle contains zero metadata extractors; the module is side-effect-free (Side effects: 0 B).
  • Single source of truth for detection. detectProviderMetadata() calls the existing detectProvider() for the provider name, then looks up extractors in a Partial<Record<ProviderName, ProviderExtractors>> keyed by the same lowercase names. No parallel detection list.
  • Honors project conventions: env proxy (no bare process), lazy /* #__PURE__ */ singleton, .ts imports.
  • ~20 providers with git/build metadata included (all the ones std-env already detects that expose it: GitHub Actions, GitLab, Vercel, Netlify, Cloudflare Pages/Workers, CircleCI, Travis, Buildkite, Bitbucket, Azure Pipelines, CodeBuild, Drone, Render, Semaphore, AppVeyor, Bitrise, Cirrus, Codefresh, Jenkins). Providers without metadata just return { name }.

Open questions for review

  • Naming. Went with providerMetadata/detectProviderMetadata alongside providerInfo. Happy to rename or reshape — e.g. folding into providerInfo, though that reintroduces the tree-shaking cost the split avoids.
  • Shape. Dropped docsUrl/raw/isCI from the upstream varlock shape (std-env already has isCI; the others felt out of scope). Easy to add back.

Checklist

  • pnpm lint / pnpm typecheck / pnpm test pass (8 new tests)
  • pnpm build succeeds; new exports present in dist
  • README + AGENTS.md updated

Summary by CodeRabbit

  • New Features
    • Added CI/CD “Provider Metadata” detection with a normalized metadata object (repo, branch, PR details, commit SHA, environment, actor, event, workflow, and build/run info).
    • Exposed new metadata exports, including providerMetadata and detectProviderMetadata.
  • Documentation
    • Documented Provider Metadata in the README and updated AGENTS guidance with structured Source Structure and metadata module details.
  • Tests
    • Added/expanded Vitest coverage for provider metadata extraction across multiple CI/CD platforms and scenarios.

Add detectProviderMetadata() / providerMetadata exposing normalized git
and build metadata (repo, branch, commit SHA, PR number, deployment
environment, build URL, run id, actor, event, workflow) for the detected
provider.

Extractors live in a separate module (src/provider-metadata.ts) and reuse
detectProvider() as the single source of truth for the provider name, so
importing only isCI/provider tree-shakes the metadata engine out.

Adapted from @varlock/ci-env-info.
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds a new provider metadata module, exports it from the package entrypoint, documents the API, and adds tests for provider-specific metadata extraction.

Changes

Provider Metadata Feature

Layer / File(s) Summary
Type declarations and extractor registry
src/provider-metadata.ts
Declares the normalized metadata types and the provider-keyed extractor registry for metadata fields.
Core detectProviderMetadata implementation
src/provider-metadata.ts
Builds normalized metadata from detectProvider() and provider-specific extractors, and exports the eager providerMetadata value.
Parsing and normalization helpers
src/provider-metadata.ts
Adds helpers for environment resolution, branch cleanup, repo slug parsing, PR number parsing, and environment mapping.
Public exports and documentation
src/index.ts, README.md, AGENTS.md
Re-exports the new API and documents the metadata shape plus source-structure guidance.
Provider metadata test suite
test/provider-metadata.test.ts
Covers detection and extraction behavior for the supported CI providers and the default case.

Estimated code review effort: 3 (Moderate) | ~25 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR adds normalized metadata extraction and exports for the requested CI/CD providers and fields in #59.
Out of Scope Changes check ✅ Passed The changes stay focused on metadata extraction, exports, tests, and docs with no clear unrelated additions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and points to the new provider metadata API, even though it uses a shortened name.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@theoephraim theoephraim marked this pull request as ready for review July 8, 2026 06:28

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
test/provider-metadata.test.ts (1)

62-85: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider adding a workflowName assertion to the GitHub Actions push test.

GITHUB_WORKFLOW is listed in envKeys (line 17) and workflowName is a documented metadata field, but no test asserts it. Adding a stub and assertion to the existing GitHub Actions push test would close this coverage gap with minimal effort.

🧪 Suggested addition
     vi.stubEnv("GITHUB_ACTOR", "octocat");
+    vi.stubEnv("GITHUB_WORKFLOW", "CI");
 
     expect(detectProviderMetadata()).toMatchObject({
       name: "github_actions",
       repo: { owner: "unjs", name: "std-env" },
       repoSlug: "unjs/std-env",
       branch: "main",
       commitSha: "abcdef1234567890",
       commitShaShort: "abcdef1",
       isPR: false,
       runId: "42",
       buildUrl: "https://github.com/unjs/std-env/actions/runs/42",
       actor: "octocat",
       eventName: "push",
+      workflowName: "CI",
     });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/provider-metadata.test.ts` around lines 62 - 85, The GitHub Actions push
metadata test in detectProviderMetadata is missing coverage for the documented
workflowName field even though GITHUB_WORKFLOW is included in envKeys. Update
the existing GitHub Actions push case to stub GITHUB_WORKFLOW and assert the
returned metadata includes workflowName, using detectProviderMetadata and the
same toMatchObject block so the coverage stays aligned with the other GitHub
Actions fields.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/provider-metadata.ts`:
- Line 162: The buildUrl mapping in the provider metadata is double-prefixing
Netlify deploy URLs with https://, which produces invalid URLs. Update the
buildUrl handler in provider-metadata to use env.DEPLOY_URL directly when it is
present, and keep the undefined fallback unchanged so Netlify’s already
fully-qualified URL is returned as-is.
- Around line 142-143: The GitLab merge-request number mapping in provider
metadata is using the instance-wide ID instead of the user-facing MR IID. Update
the `provider-metadata` mapping for `prNumber` to use `CI_MERGE_REQUEST_IID`
while keeping `isPR` unchanged unless you intentionally want to align it too;
the relevant symbols to adjust are the merge-request fields in the provider
metadata object.
- Line 128: The prNumber resolver in provider metadata is reading the wrong
GitHub Actions variable, so PR builds never resolve a number. Update the
prNumber mapping in the provider-metadata logic to use env.GITHUB_REF with
parsePrNumber, since that helper already understands the pull ref format; keep
the change localized to the prNumber callback and ensure no other callers still
rely on GITHUB_EVENT_NUMBER.

In `@test/provider-metadata.test.ts`:
- Around line 116-130: Handle Netlify DEPLOY_URL values without double-prefixing
the scheme. In detectProviderMetadata, update the Netlify URL normalization so
the buildUrl logic checks whether DEPLOY_URL already starts with https:// before
prepending it; only add the scheme when it is missing. Keep the behavior covered
by the existing provider metadata test for the Netlify deploy-preview case.

---

Nitpick comments:
In `@test/provider-metadata.test.ts`:
- Around line 62-85: The GitHub Actions push metadata test in
detectProviderMetadata is missing coverage for the documented workflowName field
even though GITHUB_WORKFLOW is included in envKeys. Update the existing GitHub
Actions push case to stub GITHUB_WORKFLOW and assert the returned metadata
includes workflowName, using detectProviderMetadata and the same toMatchObject
block so the coverage stays aligned with the other GitHub Actions fields.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: aba0544a-432e-43b4-a65f-1011a240b56a

📥 Commits

Reviewing files that changed from the base of the PR and between 7b6b8b1 and d11beb6.

📒 Files selected for processing (5)
  • AGENTS.md
  • README.md
  • src/index.ts
  • src/provider-metadata.ts
  • test/provider-metadata.test.ts

Comment thread src/provider-metadata.ts Outdated
Comment thread src/provider-metadata.ts Outdated
Comment thread src/provider-metadata.ts Outdated
Comment thread test/provider-meta.test.ts
Address CodeRabbit review and an independent audit of every provider's
env vars against official docs:

- github: parse prNumber from GITHUB_REF (refs/pull/<n>), only on pull
  refs; prefer GITHUB_REF_NAME for the branch fallback
- gitlab: use CI_MERGE_REQUEST_IID (user-facing MR number) not the
  instance-wide CI_MERGE_REQUEST_ID
- netlify: DEPLOY_URL is already fully-qualified — don't re-prefix https://
- azure: prefer SYSTEM_PULLREQUEST_PULLREQUESTNUMBER over the internal
  PULLREQUESTID for GitHub-based PRs
- bitbucket: BITBUCKET_REPO_OWNER is deprecated — use BITBUCKET_WORKSPACE
- semaphore: PULL_REQUEST_NUMBER does not exist — use SEMAPHORE_GIT_PR_NUMBER
- bitrise: add repo via BITRISEIO_GIT_REPOSITORY_OWNER/SLUG
- note target-vs-source branch on PR builds (travis, appveyor, semaphore)

Add Semaphore test and workflowName coverage; fix Netlify test URL.
@theoephraim

Copy link
Copy Markdown
Author

Pushed fixes for the review, plus an independent audit of every provider's env vars against official docs.

From the CodeRabbit review

  • GitHub prNumberGITHUB_EVENT_NUMBER doesn't exist; parse from GITHUB_REF (refs/pull/<n>/merge), but gated on refs/pull/ so branch names ending in digits aren't mistaken for PR numbers.
  • GitLab prNumber — switched to CI_MERGE_REQUEST_IID (user-facing MR number) instead of the instance-wide CI_MERGE_REQUEST_ID.
  • Netlify buildUrlDEPLOY_URL is already fully-qualified https://, so the https://${…} prefix produced https://https://…. Now used verbatim.
  • Added workflowName test coverage.

From the docs audit (beyond the bot's findings)

  • Azure prNumberSYSTEM_PULLREQUEST_PULLREQUESTID is Azure's internal id for GitHub-based PRs; prefer SYSTEM_PULLREQUEST_PULLREQUESTNUMBER, falling back to the id (they coincide for Azure Repos).
  • Bitbucket repo ownerBITBUCKET_REPO_OWNER is deprecated → BITBUCKET_WORKSPACE.
  • Semaphore prNumberPULL_REQUEST_NUMBER doesn't exist on Semaphore 2.0; corrected to SEMAPHORE_GIT_PR_NUMBER.
  • GitHub branch — prefer the documented short GITHUB_REF_NAME before hand-parsing the ref.
  • Bitrise — added repo extraction via BITRISEIO_GIT_REPOSITORY_OWNER/_SLUG.

Known, documented caveat

For Travis / AppVeyor / Semaphore, on PR builds the branch field is the target branch (what the PR merges into), not the source — noted inline. Happy to add a sourceBranch/baseBranch split if you'd prefer; left it out to keep the shape lean for now.

Confirmed correct and unchanged: Vercel (VERCEL_URL has no scheme, so its https:// prefix is right), Cloudflare Pages/Workers, CircleCI, Buildkite, Drone, Codefresh, Cirrus (CIRRUS_CHANGE_IN_REPO is the commit SHA), CodeBuild, Render.

All green: 33 tests, typecheck, lint, build. Still a draft pending direction on naming/shape.

pi0 and others added 5 commits July 8, 2026 09:23
…mmitShaShort

- Derive `isPR` from the parsed PR number when a provider signals a PR via the
  same env var it uses for the number (9 providers), instead of repeating that
  var in both the `isPR` and `prNumber` tuple slots. Semantics unchanged,
  including the `isPR: false` case on non-PR builds.
- Drop the `commitShaShort` field and its `shortSha` helper: it duplicated data
  already in `commitSha` and is a one-liner (`commitSha.slice(0, 7)`) for
  consumers that want it.

Full-bundle dist: 9653 -> 9368 B raw, 3898 -> 3858 B gzip. Metadata engine
still tree-shakes to zero for consumers that don't import it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…eld name

Add a `/* branch */`, `/* commitSha */`, ... comment in front of every element
of each provider's extractor tuple — values and elided `,` holes alike — so the
positional table reads clearly without cross-referencing the tuple order.

oxfmt strips comments off bare sparse-array holes, so the table is frozen with a
statement-level `// oxfmt-ignore` and hand-aligned. Comments are dropped in
minification: dist is byte-identical (9368 B raw / 3858 B gzip).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Shorter, consistent naming across the whole module:
- src/provider-metadata.ts -> src/provider-meta.ts (+ test file)
- ProviderMetadata -> ProviderMeta
- providerMetadata -> providerMeta
- detectProviderMetadata -> detectProviderMeta
- README + AGENTS docs updated to match

Public API rename (exports change), all tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@pi0 pi0 changed the title feat(providers): add git/build metadata extraction (#59) feat(providers): add providerMeta (#59) Jul 8, 2026
pi0 and others added 7 commits July 8, 2026 10:20
`repoSlug` was unconditionally `${repo.owner}/${repo.name}` — fully derived
from `repo` and never divergent, so it duplicated state the caller already has.
Consumers that want the slug can join it in one expression. Mirrors the earlier
`commitShaShort` removal. The `parseRepoSlug` normalization helper stays.

dist: 9360 -> 9326 B raw, 3854 -> 3836 B gzip.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`mapEnvironment` had exactly one caller (`runEnvironment`'s EnvironmentMap
branch); fold it in. Terser already inlined it at minify time, so dist is
unchanged (9326 B raw); the win is one fewer module-local helper to follow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
detectProviderMeta() called detectProvider() unconditionally, so a bundle
retaining both `provider*` and `providerMeta` scanned the provider table twice
at module init. Split the body into `extractProviderMeta(name)`:
- detectProviderMeta() still passes a fresh detectProvider().name (re-run
  semantics for callers).
- the eager `providerMeta` singleton reuses the already-computed
  `providerInfo.name`, so detection runs once at init.

The singleton is a `#__PURE__` IIFE (same pattern as `provider`) so the
`providerInfo.name` property access stays inside the closure — a bare
property-access argument would defeat the annotation and re-pin the provider
table into every consumer (caught by test/bundle.test.ts). All 13 tree-shaking
guards + 46 unit tests pass. dist: 9326 -> 9362 B raw.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…lit-var providers

Vercel, Bitbucket, and Drone all expose canonical split owner/name env vars, so
their inline `parseRepoSlug(...)` fallbacks were dead or duplicative:
- vercel: the fallback parsed VERCEL_GIT_REPO_SLUG, which is only the repo name
  (no `/`), so parseRepoSlug always returned undefined there — dead code.
- bitbucket: use BITBUCKET_WORKSPACE + BITBUCKET_REPO_SLUG directly instead of
  splitting BITBUCKET_REPO_FULL_NAME.
- drone: use DRONE_REPO_OWNER + DRONE_REPO_NAME instead of parsing DRONE_REPO.

parseRepoSlug stays: it's the default parser for the 10 string-valued `repo`
slots, four of which pass full git/https URLs (netlify, codebuild, buildkite,
circle) that need its URL handling.

dist: 9362 -> 9192 B raw, 3831 -> 3784 B gzip. All tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ad ref branch

- Add `// Docs: <url>` comments citing each provider's official env-var
  reference, verified against vendor docs.
- Netlify: extract `prNumber` from `REVIEW_ID` (documented to match the
  deploy-preview number).
- CodeBuild: recognize `PULL_REQUEST_MERGED`/`PULL_REQUEST_CLOSED` webhook
  events and extract `prNumber` from `CODEBUILD_WEBHOOK_TRIGGER`.
- Jenkins: prefer `CHANGE_ID` (maintained GitHub Branch Source plugin) over
  `ghprbPullId` (deprecated PR builder plugin) when both are set.
- refToBranch: remove the `refs/head/` (singular) branch — not a real git ref
  format, so it was unreachable dead code.
- Note Azure's branch-name leaf-truncation and CircleCI's PR-var deprecation
  caveats inline for future reference.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… edge cases

Adds dedicated cases for previously-untested providers (Cloudflare Workers,
Azure Pipelines, Bitbucket, Buildkite, Jenkins, Render, Travis, AppVeyor,
Bitrise, Cirrus, Codefresh, Drone) plus edge cases surfaced while verifying
provider docs: case-insensitive environment mapping, malformed/`.git`-suffix
repo URLs, GitLab single-segment project paths, and the new Netlify/CodeBuild
prNumber extraction.

Raises provider-meta.ts coverage from ~80%/64% to ~97%/90% (statements/branches).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…Url (live app)

`buildUrl` conflated two different concepts: a link to the CI run's logs
(GitHub Actions, GitLab pipeline, CircleCI, Buildkite) vs. the live,
publicly-reachable deployment URL (Vercel, Netlify, Cloudflare Pages,
Render). Splitting them fixes two real gaps found while auditing against
official docs: Render exposed no URL at all despite having
`RENDER_EXTERNAL_URL`, and GitLab only captured the pipeline log URL, never
its opt-in `CI_ENVIRONMENT_URL` deploy URL.

- Add `deployUrl` as a new (appended, non-reordering) tuple slot.
- Move Netlify/Vercel/Cloudflare Pages' existing URL extraction to `deployUrl`.
- Add Render (`RENDER_EXTERNAL_URL`) and GitLab (`CI_ENVIRONMENT_URL`)
  `deployUrl` extraction.
- Update README/AGENTS.md and add test coverage for the new field.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support standard build envs for CI / Git integrations

2 participants