diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d0b27b7b2..f6cb44565 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -136,10 +136,8 @@ jobs:
id-token: write
env:
API_IMAGE_ID_BASE: us-central1-docker.pkg.dev/deno-registry3-infra/registry/api
- FRONTEND_IMAGE_ID_BASE: us-central1-docker.pkg.dev/deno-registry3-infra/registry/frontend
outputs:
api_image_id: ${{ steps.api_image_id.outputs.image_id }}
- frontend_image_id: ${{ steps.frontend_image_id.outputs.image_id }}
steps:
- name: Clone repository
uses: actions/checkout@v6
@@ -165,8 +163,7 @@ jobs:
if: github.event_name == 'push'
id: check_existing
run: |
- if docker manifest inspect ${{ env.API_IMAGE_ID_BASE }}:${{ github.sha }} > /dev/null 2>&1 &&
- docker manifest inspect ${{ env.FRONTEND_IMAGE_ID_BASE }}:${{ github.sha }} > /dev/null 2>&1; then
+ if docker manifest inspect ${{ env.API_IMAGE_ID_BASE }}:${{ github.sha }} > /dev/null 2>&1 then
echo "exists=true" >> $GITHUB_OUTPUT
else
echo "exists=false" >> $GITHUB_OUTPUT
@@ -191,20 +188,6 @@ jobs:
cache-from: type=gha,scope=docker-api
cache-to: type=gha,mode=max,scope=docker-api
- # The legacy Cloud Run frontend lives alongside the new Cloudflare
- # Worker frontend while traffic is cut over; tear this and the
- # corresponding terraform resources down in a follow-up.
- - name: Build and push frontend docker image
- if: github.event_name != 'push' || steps.check_existing.outputs.exists != 'true'
- uses: docker/build-push-action@v5
- id: frontend_push
- with:
- context: frontend
- push: true
- tags: ${{ env.FRONTEND_IMAGE_ID_BASE }}:${{ github.sha }}
- cache-from: type=gha,scope=docker-frontend
- cache-to: type=gha,mode=max,scope=docker-frontend
-
- name: Set api_image_id output
id: api_image_id
run: |
@@ -214,15 +197,6 @@ jobs:
echo "image_id=${{ env.API_IMAGE_ID_BASE }}:${{ github.sha }}" >> $GITHUB_OUTPUT
fi
- - name: Set frontend_image_id output
- id: frontend_image_id
- run: |
- if [ -n "${{ steps.frontend_push.outputs.imageid }}" ]; then
- echo "image_id=${{ env.FRONTEND_IMAGE_ID_BASE }}@${{ steps.frontend_push.outputs.imageid }}" >> $GITHUB_OUTPUT
- else
- echo "image_id=${{ env.FRONTEND_IMAGE_ID_BASE }}:${{ github.sha }}" >> $GITHUB_OUTPUT
- fi
-
staging:
if: github.event_name == 'merge_group' || (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'test-on-staging'))
runs-on: ubuntu-22.04
@@ -269,7 +243,6 @@ jobs:
deno task tf:staging:plan
env:
API_IMAGE_ID: ${{ needs.docker-images.outputs.api_image_id }}
- FRONTEND_IMAGE_ID: ${{ needs.docker-images.outputs.frontend_image_id }}
TF_VAR_github_client_secret: ${{ secrets.GH_CLIENT_SECRET }}
TF_VAR_gitlab_client_secret: ${{ secrets.GITLAB_CLIENT_SECRET }}
TF_VAR_postmark_token: ${{ secrets.POSTMARK_TOKEN }}
@@ -281,6 +254,7 @@ jobs:
TF_VAR_orama_symbols_data_source: ${{ vars.ORAMA_SYMBOLS_DATA_SOURCE }}
TF_VAR_orama_docs_project_id: ${{ vars.ORAMA_DOCS_PROJECT_ID }}
TF_VAR_cloudflare_api_token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
+ TF_VAR_otlp_headers: ${{ secrets.OTLP_HEADERS }}
- name: terraform apply
run: deno task tf:staging:apply
@@ -341,7 +315,6 @@ jobs:
deno task tf:prod:plan
env:
API_IMAGE_ID: ${{ needs.docker-images.outputs.api_image_id }}
- FRONTEND_IMAGE_ID: ${{ needs.docker-images.outputs.frontend_image_id }}
TF_VAR_github_client_secret: ${{ secrets.GH_CLIENT_SECRET }}
TF_VAR_gitlab_client_secret: ${{ secrets.GITLAB_CLIENT_SECRET }}
TF_VAR_postmark_token: ${{ secrets.POSTMARK_TOKEN }}
@@ -353,6 +326,7 @@ jobs:
TF_VAR_orama_symbols_data_source: ${{ vars.ORAMA_SYMBOLS_DATA_SOURCE }}
TF_VAR_orama_docs_project_id: ${{ vars.ORAMA_DOCS_PROJECT_ID }}
TF_VAR_cloudflare_api_token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
+ TF_VAR_otlp_headers: ${{ secrets.OTLP_HEADERS }}
- name: terraform apply
run: deno task tf:prod:apply
diff --git a/Cargo.lock b/Cargo.lock
index 49a7d771d..f5edf0851 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -371,7 +371,7 @@ dependencies = [
"async-std",
"filetime",
"libc",
- "pin-project 1.1.5",
+ "pin-project",
"redox_syscall 0.2.16",
"xattr 0.2.3",
]
@@ -591,7 +591,7 @@ dependencies = [
"bitflags 2.6.0",
"cexpr",
"clang-sys",
- "itertools 0.10.5",
+ "itertools",
"lazy_static",
"lazycell",
"log",
@@ -1800,49 +1800,6 @@ dependencies = [
"slab",
]
-[[package]]
-name = "gcemeta"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "47d460327b24cc34c86d53d60a90e9e6044817f7906ebd9baa5c3d0ee13e1ecf"
-dependencies = [
- "bytes",
- "hyper 0.14.30",
- "serde",
- "serde_json",
- "thiserror 1.0.63",
- "tokio",
- "tracing",
-]
-
-[[package]]
-name = "gcloud-sdk"
-version = "0.20.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3a24376e7850e7864bb326debc5765a1dda4fc47603c22e2bc0ebf30ff59141b"
-dependencies = [
- "async-trait",
- "chrono",
- "futures",
- "gcemeta",
- "hyper 0.14.30",
- "jsonwebtoken",
- "once_cell",
- "prost 0.11.9",
- "prost-types 0.11.9",
- "reqwest 0.11.27",
- "secret-vault-value",
- "serde",
- "serde_json",
- "tokio",
- "tonic 0.9.2",
- "tower 0.4.13",
- "tower-layer",
- "tower-util",
- "tracing",
- "url",
-]
-
[[package]]
name = "generator"
version = "0.8.8"
@@ -2528,15 +2485,6 @@ dependencies = [
"either",
]
-[[package]]
-name = "itertools"
-version = "0.13.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
-dependencies = [
- "either",
-]
-
[[package]]
name = "itoa"
version = "1.0.14"
@@ -3119,24 +3067,16 @@ dependencies = [
]
[[package]]
-name = "opentelemetry-gcloud-trace"
-version = "0.5.0"
+name = "opentelemetry-http"
+version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f67eb7da990bcd55159c3300c8d431d503892cf4f4d6a802fd30021b8233dec3"
+checksum = "a819b71d6530c4297b49b3cae2939ab3a8cc1b9f382826a1bc29dd0ca3864906"
dependencies = [
"async-trait",
- "futures",
- "futures-util",
- "gcloud-sdk",
- "opentelemetry",
- "opentelemetry-semantic-conventions",
- "prost-types 0.11.9",
- "rsb_derive",
- "rvstruct",
- "tokio",
- "tokio-stream",
- "tonic 0.9.2",
- "tracing",
+ "bytes",
+ "http 0.2.12",
+ "opentelemetry_api",
+ "reqwest 0.11.27",
]
[[package]]
@@ -3150,11 +3090,11 @@ dependencies = [
"futures-util",
"http 0.2.12",
"opentelemetry",
+ "opentelemetry-http",
"opentelemetry-proto",
- "prost 0.11.9",
+ "prost",
+ "reqwest 0.11.27",
"thiserror 1.0.63",
- "tokio",
- "tonic 0.8.3",
]
[[package]]
@@ -3166,17 +3106,8 @@ dependencies = [
"futures",
"futures-util",
"opentelemetry",
- "prost 0.11.9",
- "tonic 0.8.3",
-]
-
-[[package]]
-name = "opentelemetry-semantic-conventions"
-version = "0.11.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "24e33428e6bf08c6f7fcea4ddb8e358fab0fe48ab877a87c70c6ebe20f673ce5"
-dependencies = [
- "opentelemetry",
+ "prost",
+ "tonic",
]
[[package]]
@@ -3452,33 +3383,13 @@ dependencies = [
"siphasher",
]
-[[package]]
-name = "pin-project"
-version = "0.4.30"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3ef0f924a5ee7ea9cbcea77529dba45f8a9ba9f622419fe3386ca581a3ae9d5a"
-dependencies = [
- "pin-project-internal 0.4.30",
-]
-
[[package]]
name = "pin-project"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3"
dependencies = [
- "pin-project-internal 1.1.5",
-]
-
-[[package]]
-name = "pin-project-internal"
-version = "0.4.30"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "851c8d0ce9bebe43790dedfc86614c23494ac9f423dd618d3a61fc693eafe61e"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 1.0.109",
+ "pin-project-internal",
]
[[package]]
@@ -3637,17 +3548,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd"
dependencies = [
"bytes",
- "prost-derive 0.11.9",
-]
-
-[[package]]
-name = "prost"
-version = "0.13.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3b2ecbe40f08db5c006b5764a2645f7f3f141ce756412ac9e1dd6087e6d32995"
-dependencies = [
- "bytes",
- "prost-derive 0.13.2",
+ "prost-derive",
]
[[package]]
@@ -3657,43 +3558,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4"
dependencies = [
"anyhow",
- "itertools 0.10.5",
+ "itertools",
"proc-macro2",
"quote",
"syn 1.0.109",
]
-[[package]]
-name = "prost-derive"
-version = "0.13.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "acf0c195eebb4af52c752bec4f52f645da98b6e92077a04110c7f349477ae5ac"
-dependencies = [
- "anyhow",
- "itertools 0.13.0",
- "proc-macro2",
- "quote",
- "syn 2.0.90",
-]
-
-[[package]]
-name = "prost-types"
-version = "0.11.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13"
-dependencies = [
- "prost 0.11.9",
-]
-
-[[package]]
-name = "prost-types"
-version = "0.13.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "60caa6738c7369b940c3d49246a8d1749323674c65cb13010134f5c9bad5b519"
-dependencies = [
- "prost 0.13.2",
-]
-
[[package]]
name = "psm"
version = "0.1.23"
@@ -3929,11 +3799,10 @@ dependencies = [
"oauth2",
"once_cell",
"opentelemetry",
- "opentelemetry-gcloud-trace",
"opentelemetry-otlp",
"oramacore-client",
"percent-encoding",
- "pin-project 1.1.5",
+ "pin-project",
"postmark",
"pretty_assertions",
"rand",
@@ -4191,17 +4060,6 @@ dependencies = [
"zeroize",
]
-[[package]]
-name = "rsb_derive"
-version = "0.5.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2c53e42fccdc5f1172e099785fe78f89bc0c1e657d0c2ef591efbfac427e9a4"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 1.0.109",
-]
-
[[package]]
name = "rust-ini"
version = "0.21.3"
@@ -4323,18 +4181,6 @@ dependencies = [
"zeroize",
]
-[[package]]
-name = "rustls-native-certs"
-version = "0.6.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00"
-dependencies = [
- "openssl-probe",
- "rustls-pemfile 1.0.4",
- "schannel",
- "security-framework",
-]
-
[[package]]
name = "rustls-pemfile"
version = "1.0.4"
@@ -4391,26 +4237,6 @@ version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6"
-[[package]]
-name = "rvs_derive"
-version = "0.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e1fa12378eb54f3d4f2db8dcdbe33af610b7e7d001961c1055858282ecef2a5"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 1.0.109",
-]
-
-[[package]]
-name = "rvstruct"
-version = "0.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5107860ec34506b64cf3680458074eac5c2c564f7ccc140918bbcd1714fd8d5d"
-dependencies = [
- "rvs_derive",
-]
-
[[package]]
name = "ryu"
version = "1.0.18"
@@ -4454,19 +4280,6 @@ dependencies = [
"untrusted 0.9.0",
]
-[[package]]
-name = "secret-vault-value"
-version = "0.3.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc32a777b53b3433b974c9c26b6d502a50037f8da94e46cb8ce2ced2cfdfaea0"
-dependencies = [
- "prost 0.13.2",
- "prost-types 0.13.2",
- "serde",
- "serde_json",
- "zeroize",
-]
-
[[package]]
name = "security-framework"
version = "2.11.1"
@@ -5872,9 +5685,9 @@ dependencies = [
"hyper 0.14.30",
"hyper-timeout",
"percent-encoding",
- "pin-project 1.1.5",
- "prost 0.11.9",
- "prost-derive 0.11.9",
+ "pin-project",
+ "prost",
+ "prost-derive",
"tokio",
"tokio-stream",
"tokio-util",
@@ -5885,38 +5698,6 @@ dependencies = [
"tracing-futures",
]
-[[package]]
-name = "tonic"
-version = "0.9.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a"
-dependencies = [
- "async-stream",
- "async-trait",
- "axum",
- "base64 0.21.7",
- "bytes",
- "futures-core",
- "futures-util",
- "h2",
- "http 0.2.12",
- "http-body 0.4.6",
- "hyper 0.14.30",
- "hyper-timeout",
- "percent-encoding",
- "pin-project 1.1.5",
- "prost 0.11.9",
- "rustls-native-certs",
- "rustls-pemfile 1.0.4",
- "tokio",
- "tokio-rustls 0.24.1",
- "tokio-stream",
- "tower 0.4.13",
- "tower-layer",
- "tower-service",
- "tracing",
-]
-
[[package]]
name = "tower"
version = "0.4.13"
@@ -5926,7 +5707,7 @@ dependencies = [
"futures-core",
"futures-util",
"indexmap 1.9.3",
- "pin-project 1.1.5",
+ "pin-project",
"pin-project-lite",
"rand",
"slab",
@@ -5964,18 +5745,6 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
-[[package]]
-name = "tower-util"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d1093c19826d33807c72511e68f73b4a0469a3f22c2bd5f7d5212178b4b89674"
-dependencies = [
- "futures-core",
- "futures-util",
- "pin-project 0.4.30",
- "tower-service",
-]
-
[[package]]
name = "tracing"
version = "0.1.44"
@@ -6015,7 +5784,7 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2"
dependencies = [
- "pin-project 1.1.5",
+ "pin-project",
"tracing",
]
diff --git a/api/Cargo.toml b/api/Cargo.toml
index 8ecdd081d..19e77129c 100644
--- a/api/Cargo.toml
+++ b/api/Cargo.toml
@@ -73,8 +73,15 @@ opentelemetry = { version = "0.19", features = [
"rt-tokio-current-thread",
"trace",
] }
-opentelemetry-otlp = "0.12"
-opentelemetry-gcloud-trace = "0.5.0"
+# OTLP over HTTP/protobuf (not gRPC): the managed Grafana Cloud OTLP gateway only
+# speaks OTLP/HTTP. `reqwest-client` routes export through reqwest, the same HTTP
+# stack every other outbound call in this crate already uses. Default
+# `grpc-tonic` is off.
+opentelemetry-otlp = { version = "0.12", default-features = false, features = [
+ "trace",
+ "http-proto",
+ "reqwest-client",
+] }
rust-s3 = { version = "0.37.1", default-features = false, features = ["tokio-rustls-tls"] }
deno_semver = "0.9.1"
flate2 = "1"
diff --git a/api/src/auth/github.rs b/api/src/auth/github.rs
index d09640bc3..e61a613df 100644
--- a/api/src/auth/github.rs
+++ b/api/src/auth/github.rs
@@ -1,5 +1,4 @@
// Copyright 2024 the JSR authors. All rights reserved. MIT license.
-// Copyright Deno Land Inc. All Rights Reserved. Proprietary and confidential.
use crate::api::ApiError;
use crate::db::*;
diff --git a/api/src/auth/gitlab.rs b/api/src/auth/gitlab.rs
index d92f660dd..e7bd13dd3 100644
--- a/api/src/auth/gitlab.rs
+++ b/api/src/auth/gitlab.rs
@@ -1,5 +1,4 @@
// Copyright 2024 the JSR authors. All rights reserved. MIT license.
-// Copyright Deno Land Inc. All Rights Reserved. Proprietary and confidential.
use crate::api::ApiError;
use crate::db::*;
diff --git a/api/src/auth/mod.rs b/api/src/auth/mod.rs
index 40334ff04..657923e90 100644
--- a/api/src/auth/mod.rs
+++ b/api/src/auth/mod.rs
@@ -1,5 +1,4 @@
// Copyright 2024 the JSR authors. All rights reserved. MIT license.
-// Copyright Deno Land Inc. All Rights Reserved. Proprietary and confidential.
use crate::RegistryUrl;
use crate::api::ApiError;
diff --git a/api/src/config.rs b/api/src/config.rs
index b6e0c158f..6c30a6655 100644
--- a/api/src/config.rs
+++ b/api/src/config.rs
@@ -115,13 +115,26 @@ pub struct Config {
/// The Orama symbol data source
pub orama_symbols_data_source: Option,
- #[clap(long = "otlp_endpoint", env = "OTLP_ENDPOINT", group = "trace")]
- /// OTLP endpoint to send traces to.
+ #[clap(long = "otlp_endpoint", env = "OTLP_ENDPOINT")]
+ /// Base OTLP/HTTP endpoint (e.g. Grafana Cloud's
+ /// `https://otlp-gateway-.grafana.net/otlp`), OTEL
+ /// `OTEL_EXPORTER_OTLP_ENDPOINT` style: the per-signal path (`/v1/traces`,
+ /// and `/v1/logs` in the future) is appended automatically. A full
+ /// signal URL is also accepted. Export is disabled when unset.
pub otlp_endpoint: Option,
- #[clap(long = "cloud_trace", group = "trace")]
- /// Whether to enable cloud trace.
- pub cloud_trace: bool,
+ #[clap(long = "otlp_headers", env = "OTLP_HEADERS")]
+ /// Extra headers sent with every OTLP request, as a comma-separated list of
+ /// `key=value` pairs (the OpenTelemetry `OTEL_EXPORTER_OTLP_HEADERS` format).
+ /// Used to carry the backend's auth, e.g. `Authorization=Basic ` for
+ /// Grafana Cloud. Only the first `=` in each pair separates key from value.
+ pub otlp_headers: Option,
+
+ #[clap(long = "deployment_environment", env = "DEPLOYMENT_ENVIRONMENT")]
+ /// Deployment environment name (e.g. `staging`, `production`), exported as the
+ /// `deployment.environment` OTLP resource attribute so telemetry from each
+ /// environment can be told apart in the backend. Unset omits the attribute.
+ pub deployment_environment: Option,
#[clap(long = "registry_url", env = "REGISTRY_URL")]
/// The base URL of the registry, where module code and metadata can be
@@ -169,10 +182,6 @@ pub struct Config {
/// The ID of the npm tarball build queue.
pub npm_tarball_build_queue_id: Option,
- #[clap(long = "gcp_project_id", env = "GCP_PROJECT_ID")]
- /// The ID of the project.
- pub gcp_project_id: Option,
-
#[clap(long = "cloudflare_account_id", env = "CLOUDFLARE_ACCOUNT_ID")]
/// The Cloudflare account ID for Analytics Engine.
pub cloudflare_account_id: Option,
@@ -222,7 +231,8 @@ impl std::fmt::Debug for Config {
.field("github_client_id", &self.github_client_id)
.field("github_client_secret", &"***")
.field("otlp_endpoint", &self.otlp_endpoint)
- .field("cloud_trace", &self.cloud_trace)
+ .field("otlp_headers", &self.otlp_headers.as_ref().map(|_| "***"))
+ .field("deployment_environment", &self.deployment_environment)
.field("registry_url", &self.registry_url)
.field("api", &self.api)
.field("tasks", &self.tasks)
diff --git a/api/src/db/models.rs b/api/src/db/models.rs
index 87b206ee1..2ea437f74 100644
--- a/api/src/db/models.rs
+++ b/api/src/db/models.rs
@@ -1,5 +1,5 @@
// Copyright 2024 the JSR authors. All rights reserved. MIT license.
-// Copyright Deno Land Inc. All Rights Reserved. Proprietary and confidential.
+
#![allow(dead_code)]
use chrono::DateTime;
diff --git a/api/src/docs.rs b/api/src/docs.rs
index 37fe760f5..908ade2e5 100644
--- a/api/src/docs.rs
+++ b/api/src/docs.rs
@@ -604,7 +604,8 @@ fn get_url_rewriter(
is_readme: bool,
) -> URLRewriter {
Arc::new(move |current_file, url| {
- if url.starts_with('#') || url.starts_with('/') {
+ // Anchors and protocol-relative URLs (`//host/...`) are left untouched.
+ if url.starts_with('#') || url.starts_with("//") {
return url.to_string();
}
@@ -639,6 +640,13 @@ fn get_url_rewriter(
base.clone()
};
+ // Root-relative URLs (a single leading `/`) resolve against the
+ // repository root rather than the current file's directory. Without this
+ // they would be served relative to jsr.io and 404 (see #768).
+ if let Some(path) = url.strip_prefix('/') {
+ return format!("{base}/{path}");
+ }
+
if !is_readme && let Some(current_file) = current_file {
let (path, _file) = current_file
.specifier
@@ -1688,6 +1696,9 @@ mod tests {
"/@foo/bar/1.2.3/src/assets/logo.svg"
);
+ // Root-relative links resolve against the package root (see #768).
+ assert_eq!(rewriter(None, "/LICENSE"), "/@foo/bar/1.2.3/LICENSE");
+
assert_eq!(
rewriter(
Some(&ShortPath::new(
@@ -1737,5 +1748,18 @@ mod tests {
),
"https://raw.githubusercontent.com/foo/bar/HEAD/./src/assets/logo.svg"
);
+
+ // Regression for #768: root-relative links resolve against the repository
+ // root rather than being left to 404 against jsr.io.
+ assert_eq!(
+ rewriter(None, "/LICENSE"),
+ "https://github.com/foo/bar/blob/HEAD/LICENSE"
+ );
+ assert_eq!(
+ rewriter(None, "/assets/logo.svg"),
+ "https://raw.githubusercontent.com/foo/bar/HEAD/assets/logo.svg"
+ );
+ // Protocol-relative URLs are left untouched.
+ assert_eq!(rewriter(None, "//example.com/x"), "//example.com/x");
}
}
diff --git a/api/src/external/github.rs b/api/src/external/github.rs
index f38351f9e..f2f3bb921 100644
--- a/api/src/external/github.rs
+++ b/api/src/external/github.rs
@@ -1,5 +1,4 @@
// Copyright 2024 the JSR authors. All rights reserved. MIT license.
-// Copyright Deno Land Inc. All Rights Reserved. Proprietary and confidential.
use std::fmt::Display;
use std::str::FromStr;
diff --git a/api/src/external/gitlab.rs b/api/src/external/gitlab.rs
index fe768d275..59840d7e7 100644
--- a/api/src/external/gitlab.rs
+++ b/api/src/external/gitlab.rs
@@ -1,5 +1,4 @@
// Copyright 2024 the JSR authors. All rights reserved. MIT license.
-// Copyright Deno Land Inc. All Rights Reserved. Proprietary and confidential.
use crate::util::shared_http_client;
use hyper::StatusCode;
diff --git a/api/src/external/orama.rs b/api/src/external/orama.rs
index fcfd3864d..d4be65700 100644
--- a/api/src/external/orama.rs
+++ b/api/src/external/orama.rs
@@ -1,5 +1,4 @@
// Copyright 2024 the JSR authors. All rights reserved. MIT license.
-// Copyright Deno Land Inc. All Rights Reserved. Proprietary and confidential.
use std::sync::Arc;
diff --git a/api/src/main.rs b/api/src/main.rs
index c93124b49..ca1d586cf 100644
--- a/api/src/main.rs
+++ b/api/src/main.rs
@@ -163,14 +163,22 @@ async fn main() {
let config = Config::parse();
println!("{config:?}");
- let export_target = if config.cloud_trace {
- TracingExportTarget::CloudTrace
- } else if let Some(otlp_endpoint) = config.otlp_endpoint {
- TracingExportTarget::Otlp(otlp_endpoint)
+ // Treat a present-but-empty OTLP_ENDPOINT as unset: clap parses an empty env
+ // var as Some(""), which would otherwise build a schemeless endpoint and
+ // panic the exporter at boot. Filtering here means empty == export disabled.
+ let export_target = if let Some(endpoint) =
+ config.otlp_endpoint.filter(|s| !s.trim().is_empty())
+ {
+ TracingExportTarget::Otlp {
+ endpoint,
+ headers: crate::tracing::parse_otlp_headers(
+ config.otlp_headers.as_deref(),
+ ),
+ }
} else {
TracingExportTarget::None
};
- setup_tracing("api", export_target).await;
+ setup_tracing("api", export_target, config.deployment_environment).await;
let database = Database::connect(
&config.database_url,
diff --git a/api/src/provenance.rs b/api/src/provenance.rs
index f8f190a1b..15938985c 100644
--- a/api/src/provenance.rs
+++ b/api/src/provenance.rs
@@ -2,6 +2,7 @@
use anyhow::{Result, bail};
use base64::Engine as _;
use base64::prelude::BASE64_STANDARD;
+use base64::prelude::BASE64_URL_SAFE;
use serde::Deserialize;
use serde::Serialize;
use x509_parser::parse_x509_certificate;
@@ -117,14 +118,23 @@ pub enum ProvenanceAttestationSubject {
Subject(Subject),
}
+/// Decode a DSSE envelope payload. The payload is base64-encoded, but some
+/// clients emit it using the URL-safe alphabet (`-`/`_` instead of `+`/`/`),
+/// so fall back to URL-safe decoding when standard decoding fails.
+fn decode_payload(payload: &str) -> Result> {
+ match BASE64_STANDARD.decode(payload) {
+ Ok(bytes) => Ok(bytes),
+ Err(_) => Ok(BASE64_URL_SAFE.decode(payload)?),
+ }
+}
+
pub fn verify(
subject_name: String,
bundle: ProvenanceBundle,
) -> Result {
// Extract subject from the DSSE envelope
let subject = {
- let payload =
- BASE64_STANDARD.decode(&bundle.content.dsse_envelope.payload)?;
+ let payload = decode_payload(&bundle.content.dsse_envelope.payload)?;
serde_json::from_slice::(&payload)?.subject
};
@@ -162,3 +172,29 @@ pub fn verify(
let tls = &bundle.verification_material.tlog_entries[0];
Ok(tls.log_index.to_string())
}
+
+#[cfg(test)]
+mod tests {
+ use super::decode_payload;
+ use base64::Engine as _;
+ use base64::prelude::BASE64_STANDARD;
+ use base64::prelude::BASE64_URL_SAFE;
+
+ #[test]
+ fn decode_payload_accepts_standard_and_url_safe() {
+ // These bytes encode to "+/8=" in standard base64 and "-_8=" in URL-safe
+ // base64, exercising both alphabet-specific characters (`+`/`/` vs `-`/`_`).
+ let raw = [0xfb_u8, 0xff];
+
+ let standard = BASE64_STANDARD.encode(raw);
+ assert!(standard.contains('+') && standard.contains('/'));
+ assert_eq!(decode_payload(&standard).unwrap(), raw);
+
+ // Regression test for jsr-io/jsr#1312: some clients emit the DSSE payload
+ // using the URL-safe alphabet, which the standard decoder rejected with
+ // "Invalid symbol 45, offset ..." (45 being `-`).
+ let url_safe = BASE64_URL_SAFE.encode(raw);
+ assert!(url_safe.contains('-') && url_safe.contains('_'));
+ assert_eq!(decode_payload(&url_safe).unwrap(), raw);
+ }
+}
diff --git a/api/src/token.rs b/api/src/token.rs
index 4f1cacbe9..ade8617ce 100644
--- a/api/src/token.rs
+++ b/api/src/token.rs
@@ -1,5 +1,4 @@
// Copyright 2024 the JSR authors. All rights reserved. MIT license.
-// Copyright Deno Land Inc. All Rights Reserved. Proprietary and confidential.
use crate::db::*;
use chrono::DateTime;
diff --git a/api/src/traced_router.rs b/api/src/traced_router.rs
index 37b156252..db2889c2f 100644
--- a/api/src/traced_router.rs
+++ b/api/src/traced_router.rs
@@ -1,5 +1,4 @@
// Copyright 2024 the JSR authors. All rights reserved. MIT license.
-// Copyright Deno Land Inc. All Rights Reserved. Proprietary and confidential.
//! This module implements a hyper service that wraps routerify and handles
//! tracing. It starts a span for each request, and records successes and
diff --git a/api/src/tracing.rs b/api/src/tracing.rs
index 121450da3..a03840b55 100644
--- a/api/src/tracing.rs
+++ b/api/src/tracing.rs
@@ -1,5 +1,4 @@
// Copyright 2024 the JSR authors. All rights reserved. MIT license.
-// Copyright Deno Land Inc. All Rights Reserved. Proprietary and confidential.
use opentelemetry::KeyValue;
use opentelemetry::global;
@@ -21,11 +20,44 @@ use tracing_subscriber::registry::LookupSpan;
use tracing_subscriber::reload;
pub enum TracingExportTarget {
- Otlp(String),
- CloudTrace,
+ Otlp {
+ endpoint: String,
+ headers: std::collections::HashMap,
+ },
None,
}
+/// Append an OTLP signal subpath to the configured base endpoint, OTEL
+/// `OTEL_EXPORTER_OTLP_ENDPOINT` style: the endpoint is the base (e.g. Grafana
+/// Cloud's `.../otlp`) and each signal posts to its own path (`/v1/traces`,
+/// later `/v1/logs`). The opentelemetry-otlp 0.12 HTTP exporter uses the
+/// endpoint verbatim and does NOT do this itself, so posting to the bare base
+/// 404s. Tolerates a trailing slash and an endpoint that already carries the
+/// signal path.
+fn otlp_signal_endpoint(base: &str, signal_path: &str) -> String {
+ let base = base.trim_end_matches('/');
+ if base.ends_with(signal_path) {
+ base.to_string()
+ } else {
+ format!("{base}{signal_path}")
+ }
+}
+
+/// Parse the `OTLP_HEADERS` value (`key1=value1,key2=value2`, the OpenTelemetry
+/// `OTEL_EXPORTER_OTLP_HEADERS` format) into a header map. Splits each pair on
+/// its first `=` only, so values containing `=` (e.g. base64 padding in a
+/// `Basic` auth header) survive intact.
+pub fn parse_otlp_headers(
+ raw: Option<&str>,
+) -> std::collections::HashMap {
+ raw
+ .into_iter()
+ .flat_map(|s| s.split(','))
+ .filter_map(|pair| pair.split_once('='))
+ .map(|(k, v)| (k.trim().to_string(), v.trim().to_string()))
+ .collect()
+}
+
/// Initialize tracing infrastructure.
///
/// `tracing` has a three core concepts. These are Spans, Events, and subscribers.
@@ -39,18 +71,31 @@ pub enum TracingExportTarget {
pub async fn setup_tracing(
name: &'static str,
export_target: TracingExportTarget,
+ deployment_environment: Option,
) -> (LogFilterHandle, String) {
- let trace_config = trace::config().with_resource(Resource::new(vec![
+ let mut resource = vec![
KeyValue::new("service.name", name),
KeyValue::new("service.namespace", "registry"),
- ]));
+ ];
+ // Distinguishes staging from prod telemetry when both export to the same
+ // backend. Empty/unset omits it rather than reporting a blank environment.
+ if let Some(env) = deployment_environment.filter(|s| !s.trim().is_empty()) {
+ resource.push(KeyValue::new("deployment.environment", env));
+ }
+ let trace_config = trace::config().with_resource(Resource::new(resource));
let tracer = match export_target {
- TracingExportTarget::Otlp(otlp_endpoint) => {
+ TracingExportTarget::Otlp { endpoint, headers } => {
+ // OTLP/HTTP (protobuf), not gRPC: the managed Grafana Cloud gateway only
+ // accepts HTTP, and it also works directly from the Cloudflare Container.
+ // `endpoint` is the base; the `/v1/traces` signal path is appended here.
+ // `headers` carries the backend auth, e.g. `Authorization: Basic `
+ // for Grafana Cloud.
let exporter = opentelemetry_otlp::new_exporter()
- .tonic()
- .with_endpoint(otlp_endpoint)
- .with_protocol(opentelemetry_otlp::Protocol::Grpc);
+ .http()
+ .with_endpoint(otlp_signal_endpoint(&endpoint, "/v1/traces"))
+ .with_protocol(opentelemetry_otlp::Protocol::HttpBinary)
+ .with_headers(headers);
let tracer = opentelemetry_otlp::new_pipeline()
.tracing()
.with_trace_config(trace_config)
@@ -59,16 +104,6 @@ pub async fn setup_tracing(
.unwrap();
Some(tracer)
}
- TracingExportTarget::CloudTrace => {
- let tracer = opentelemetry_gcloud_trace::GcpCloudTraceExporterBuilder::for_default_project_id()
- .await
- .unwrap()
- .with_trace_config(trace_config)
- .install_batch(opentelemetry::runtime::Tokio)
- .await
- .unwrap();
- Some(tracer)
- }
TracingExportTarget::None => None,
};
@@ -145,3 +180,55 @@ where
let otel_data = extensions.get::()?;
Some(otel_data.parent_cx.span().span_context().trace_id())
}
+
+#[cfg(test)]
+mod tests {
+ use super::otlp_signal_endpoint;
+ use super::parse_otlp_headers;
+
+ #[test]
+ fn appends_signal_path_to_base() {
+ assert_eq!(
+ otlp_signal_endpoint("https://x.grafana.net/otlp", "/v1/traces"),
+ "https://x.grafana.net/otlp/v1/traces"
+ );
+ }
+
+ #[test]
+ fn tolerates_trailing_slash_and_existing_path() {
+ assert_eq!(
+ otlp_signal_endpoint("https://x.grafana.net/otlp/", "/v1/traces"),
+ "https://x.grafana.net/otlp/v1/traces"
+ );
+ assert_eq!(
+ otlp_signal_endpoint(
+ "https://x.grafana.net/otlp/v1/traces",
+ "/v1/traces"
+ ),
+ "https://x.grafana.net/otlp/v1/traces"
+ );
+ }
+
+ #[test]
+ fn none_is_empty() {
+ assert!(parse_otlp_headers(None).is_empty());
+ assert!(parse_otlp_headers(Some("")).is_empty());
+ }
+
+ #[test]
+ fn keeps_equals_in_value() {
+ // A `Basic` auth header's base64 value can contain `=` padding; only the
+ // first `=` of each pair separates key from value.
+ let headers =
+ parse_otlp_headers(Some("Authorization=Basic dXNlcjpwYXNz=="));
+ assert_eq!(headers.len(), 1);
+ assert_eq!(headers["Authorization"], "Basic dXNlcjpwYXNz==");
+ }
+
+ #[test]
+ fn multiple_pairs_are_trimmed() {
+ let headers = parse_otlp_headers(Some("a=1, b=2"));
+ assert_eq!(headers["a"], "1");
+ assert_eq!(headers["b"], "2");
+ }
+}
diff --git a/deno.json b/deno.json
index 0f4fcf98b..4912f7b85 100644
--- a/deno.json
+++ b/deno.json
@@ -24,10 +24,10 @@
"tf:infra:plan": "cd terraform_infra && terraform plan -var-file=infra.tfvars -out=infra.tfplan -input=false",
"tf:infra:apply": "cd terraform_infra && terraform apply -input=false infra.tfplan",
"tf:staging:init": "gcloud config set project deno-registry3-staging && cd terraform && terraform init -backend-config bucket=deno-registry3-staging-terraform",
- "tf:staging:plan": "cd terraform && terraform plan -var-file=staging.tfvars -var-file=staging.secret.tfvars -out=staging.tfplan -input=false -var \"api_image_id=$API_IMAGE_ID\" -var \"frontend_image_id=$FRONTEND_IMAGE_ID\"",
+ "tf:staging:plan": "cd terraform && terraform plan -var-file=staging.tfvars -var-file=staging.secret.tfvars -out=staging.tfplan -input=false -var \"api_image_id=$API_IMAGE_ID\"",
"tf:staging:apply": "cd terraform && terraform apply -input=false staging.tfplan",
"tf:prod:init": "gcloud config set project deno-registry3-prod && cd terraform && terraform init -backend-config bucket=deno-registry3-prod-terraform",
- "tf:prod:plan": "cd terraform && terraform plan -var-file=prod.tfvars -var-file=prod.secret.tfvars -out=prod.tfplan -input=false -var \"api_image_id=$API_IMAGE_ID\" -var \"frontend_image_id=$FRONTEND_IMAGE_ID\"",
+ "tf:prod:plan": "cd terraform && terraform plan -var-file=prod.tfvars -var-file=prod.secret.tfvars -out=prod.tfplan -input=false -var \"api_image_id=$API_IMAGE_ID\"",
"tf:prod:apply": "cd terraform && terraform apply -input=false prod.tfplan",
"e2e:staging": "cd e2e && JSR_URL=https://deno-registry-staging.net/ JSR_API_URL=https://api.deno-registry-staging.net/ deno test -A",
diff --git a/frontend/components/NavOverflow.tsx b/frontend/components/NavOverflow.tsx
index b4c10e12f..b492fdaab 100644
--- a/frontend/components/NavOverflow.tsx
+++ b/frontend/components/NavOverflow.tsx
@@ -64,6 +64,8 @@ export function NavOverflow() {
diff --git a/frontend/routes/new.tsx b/frontend/routes/new.tsx
index a4e54bf2a..6b64708d1 100644
--- a/frontend/routes/new.tsx
+++ b/frontend/routes/new.tsx
@@ -74,7 +74,11 @@ export default define.page(function New(props) {
}`}
class="button-primary"
>
-
+
Sign in with GitHub
(function New(props) {
}`}
class="button-primary"
>
-
+
Sign in with GitLab
diff --git a/frontend/routes/package/(_islands)/DiffVersionSelector.tsx b/frontend/routes/package/(_islands)/DiffVersionSelector.tsx
index 6b751c50e..cdde78caf 100644
--- a/frontend/routes/package/(_islands)/DiffVersionSelector.tsx
+++ b/frontend/routes/package/(_islands)/DiffVersionSelector.tsx
@@ -25,6 +25,7 @@ export default function DiffVersionSelector(