From 4c4c8bda4941bc604002c06efd3c2f54e09820bf Mon Sep 17 00:00:00 2001 From: Eric Zhang Date: Tue, 23 Sep 2025 18:25:24 -0700 Subject: [PATCH 01/20] add script to bump dependencies --- scripts/update_dependencies.js | 83 ++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 scripts/update_dependencies.js diff --git a/scripts/update_dependencies.js b/scripts/update_dependencies.js new file mode 100644 index 00000000..91fb36b0 --- /dev/null +++ b/scripts/update_dependencies.js @@ -0,0 +1,83 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); + +async function getLatestVersion(packageName) { + const https = require('https'); + + return new Promise((resolve, reject) => { + const url = `https://registry.npmjs.org/${packageName}/latest`; + const request = https.get(url, { timeout: 30000 }, (response) => { + let data = ''; + response.on('data', (chunk) => data += chunk); + response.on('end', () => { + try { + if (response.statusCode === 200) { + const packageData = JSON.parse(data); + resolve(packageData.version); + } else { + console.warn(`Warning: Could not get latest version for ${packageName}: HTTP ${response.statusCode}`); + resolve(null); + } + } catch (parseError) { + console.warn(`Warning: Could not parse response for ${packageName}: ${parseError.message}`); + resolve(null); + } + }); + }); + + request.on('error', (requestError) => { + console.warn(`Warning: Could not get latest version for ${packageName}: ${requestError.message}`); + resolve(null); + }); + + request.on('timeout', () => { + request.destroy(); + console.warn(`Warning: Timeout getting latest version for ${packageName}`); + resolve(null); + }); + }); +} + +async function main() { + const packageJsonPath = path.join('aws-distro-opentelemetry-node-autoinstrumentation', 'package.json'); + + try { + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); + let updated = false; + + const dependencies = packageJson.dependencies || {}; + const otelPackages = Object.keys(dependencies).filter(pkg => pkg.startsWith('@opentelemetry/')); + + for (const packageName of otelPackages) { + const latestVersion = await getLatestVersion(packageName); + if (latestVersion) { + const currentVersion = dependencies[packageName]; + + if (currentVersion !== latestVersion) { + packageJson.dependencies[packageName] = latestVersion; + updated = true; + console.log(`Updated ${packageName}: ${currentVersion} → ${latestVersion}`); + } else { + console.log(`${packageName} already at latest version: ${latestVersion}`); + } + } + } + + if (updated) { + fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n'); + console.log('Dependencies updated successfully'); + } else { + console.log('No OpenTelemetry dependencies needed updating'); + } + + } catch (fileError) { + console.error(`Error updating dependencies: ${fileError.message}`); + process.exit(1); + } +} + +if (require.main === module) { + main().catch(console.error); +} From 5d6b7c9e970a12b6288505dcadf881f46ae8bc0f Mon Sep 17 00:00:00 2001 From: Eric Zhang Date: Tue, 23 Sep 2025 18:26:54 -0700 Subject: [PATCH 02/20] add nightly build workflow --- .github/workflows/nightly-build.yml | 70 +++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 .github/workflows/nightly-build.yml diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml new file mode 100644 index 00000000..67f7791b --- /dev/null +++ b/.github/workflows/nightly-build.yml @@ -0,0 +1,70 @@ +name: Nightly Upstream Snapshot Build + +on: + schedule: + - cron: "21 3 * * *" + workflow_dispatch: + +env: + BRANCH_NAME: nightly-dependency-updates + +jobs: + update-and-create-pr: + runs-on: ubuntu-latest + outputs: + has_changes: ${{ steps.check_changes.outputs.has_changes }} + + steps: + - name: Checkout repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Check if nightly branch already exists + run: | + if git ls-remote --exit-code --heads origin "$BRANCH_NAME"; then + echo "Branch $BRANCH_NAME already exists. Skipping run to avoid conflicts." + echo "Please merge or close the existing PR before the next nightly run." + exit 1 + fi + + - name: Configure git and create branch + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + git checkout -b "$BRANCH_NAME" + + - name: Set up Node.js + uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 #v5.0.0 + with: + node-version: '18' + + - name: Install dependencies + run: npm install + + - name: Update dependencies + run: node scripts/update_dependencies.js + + - name: Check for changes and create PR + id: check_changes + run: | + if git diff --quiet; then + echo "No dependency updates needed" + echo "has_changes=false" >> $GITHUB_OUTPUT + else + echo "Dependencies were updated" + echo "has_changes=true" >> $GITHUB_OUTPUT + + git add aws-distro-opentelemetry-node-autoinstrumentation/package.json + git commit -m "chore: update OpenTelemetry dependencies to latest versions" + git push origin "$BRANCH_NAME" + + gh pr create \ + --title "Nightly dependency update: OpenTelemetry packages to latest versions" \ + --body "Automated update of OpenTelemetry dependencies to their latest available versions." \ + --base main \ + --head "$BRANCH_NAME" + fi + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 807dc858d51413fa2ee6f0b0e67405ada3ec708d Mon Sep 17 00:00:00 2001 From: Eric Zhang Date: Tue, 23 Sep 2025 18:30:14 -0700 Subject: [PATCH 03/20] let main build run on nightly build branch --- .github/workflows/main-build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main-build.yml b/.github/workflows/main-build.yml index 51e43ac8..7ec2826d 100644 --- a/.github/workflows/main-build.yml +++ b/.github/workflows/main-build.yml @@ -5,6 +5,7 @@ on: branches: - main - "release/v*" + - nightly-dependency-updates workflow_dispatch: env: @@ -109,7 +110,7 @@ jobs: name: "Publish Main Build Status" needs: [ build, application-signals-e2e-test ] runs-on: ubuntu-latest - if: always() + if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/') steps: - name: Configure AWS Credentials for emitting metrics uses: aws-actions/configure-aws-credentials@a03048d87541d1d9fcf2ecf528a4a65ba9bd7838 #v5.0.0 From 6ffee3abac107e63cb81ae0564de86d6ec84cce9 Mon Sep 17 00:00:00 2001 From: Eric Zhang Date: Thu, 25 Sep 2025 14:37:48 -0700 Subject: [PATCH 04/20] Revert "let main build run on nightly build branch" This reverts commit 807dc858d51413fa2ee6f0b0e67405ada3ec708d. --- .github/workflows/main-build.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/main-build.yml b/.github/workflows/main-build.yml index 7ec2826d..51e43ac8 100644 --- a/.github/workflows/main-build.yml +++ b/.github/workflows/main-build.yml @@ -5,7 +5,6 @@ on: branches: - main - "release/v*" - - nightly-dependency-updates workflow_dispatch: env: @@ -110,7 +109,7 @@ jobs: name: "Publish Main Build Status" needs: [ build, application-signals-e2e-test ] runs-on: ubuntu-latest - if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/') + if: always() steps: - name: Configure AWS Credentials for emitting metrics uses: aws-actions/configure-aws-credentials@a03048d87541d1d9fcf2ecf528a4a65ba9bd7838 #v5.0.0 From 8c11228a81d25d43e999a32e849695231fb2620c Mon Sep 17 00:00:00 2001 From: Eric Zhang Date: Thu, 25 Sep 2025 16:58:43 -0700 Subject: [PATCH 05/20] update dependency upgrade script to use github releases --- scripts/generate-licenses.js | 85 ++++++++++++++++ scripts/update_dependencies.js | 173 +++++++++++++++++++++++++++++---- 2 files changed, 239 insertions(+), 19 deletions(-) create mode 100755 scripts/generate-licenses.js diff --git a/scripts/generate-licenses.js b/scripts/generate-licenses.js new file mode 100755 index 00000000..26063dae --- /dev/null +++ b/scripts/generate-licenses.js @@ -0,0 +1,85 @@ +#!/usr/bin/env node + +const checker = require('license-checker-rseidelsohn'); +const fs = require('fs'); +const path = require('path'); + +const workspaceDir = path.join(__dirname, '..', 'aws-distro-opentelemetry-node-autoinstrumentation'); + +checker.init({ + start: workspaceDir, + production: true, + excludePrivatePackages: true, + // Include packages from both local and hoisted node_modules + includePackages: '', + customFormat: { + name: '', + version: '', + repository: '', + licenseText: '' + } +}, (err, packages) => { + if (err) { + console.error('Error:', err); + process.exit(1); + } + + let output = ''; + const processedPackages = []; + + Object.keys(packages).forEach(packageKey => { + const pkg = packages[packageKey]; + + // Parse package name and version correctly for scoped packages + let name, version; + if (packageKey.startsWith('@')) { + const lastAtIndex = packageKey.lastIndexOf('@'); + name = packageKey.substring(0, lastAtIndex); + version = packageKey.substring(lastAtIndex + 1); + } else { + const atIndex = packageKey.indexOf('@'); + name = packageKey.substring(0, atIndex); + version = packageKey.substring(atIndex + 1); + } + + // Skip our own package + if (name === '@aws/aws-distro-opentelemetry-node-autoinstrumentation') { + return; + } + + processedPackages.push({ name, version, pkg }); + }); + + // Sort by package name for consistent output + processedPackages.sort((a, b) => a.name.localeCompare(b.name)); + + processedPackages.forEach(({ name, version, pkg }) => { + output += `** ${name}; version ${version}`; + if (pkg.repository) { + output += ` -- ${pkg.repository}`; + } + output += '\n'; + + if (pkg.licenseText) { + output += pkg.licenseText + '\n\n'; + } else { + output += 'License text not available\n\n'; + } + }); + + const outputPath = path.join(__dirname, '..', 'THIRD-PARTY-LICENSES'); + fs.writeFileSync(outputPath, output); + console.log(`Generated THIRD-PARTY-LICENSES with ${processedPackages.length} packages`); + + // Also show what direct dependencies might be missing + const workspacePackageJson = JSON.parse( + fs.readFileSync(path.join(workspaceDir, 'package.json')) + ); + const directDeps = Object.keys(workspacePackageJson.dependencies || {}); + const foundPackages = new Set(processedPackages.map(p => p.name)); + const missingDeps = directDeps.filter(dep => !foundPackages.has(dep)); + + if (missingDeps.length > 0) { + console.log(`\nDirect dependencies without license files found: ${missingDeps.join(', ')}`); + } +}); diff --git a/scripts/update_dependencies.js b/scripts/update_dependencies.js index 91fb36b0..735fe1aa 100644 --- a/scripts/update_dependencies.js +++ b/scripts/update_dependencies.js @@ -3,43 +3,151 @@ const fs = require('fs'); const path = require('path'); -async function getLatestVersion(packageName) { +async function httpsGet(url) { const https = require('https'); return new Promise((resolve, reject) => { - const url = `https://registry.npmjs.org/${packageName}/latest`; - const request = https.get(url, { timeout: 30000 }, (response) => { + const options = { + timeout: 30000, + headers: { + 'User-Agent': 'Mozilla/5.0 (compatible; Node.js script)' + } + }; + + const request = https.get(url, options, (response) => { let data = ''; response.on('data', (chunk) => data += chunk); response.on('end', () => { try { if (response.statusCode === 200) { - const packageData = JSON.parse(data); - resolve(packageData.version); + resolve(JSON.parse(data)); } else { - console.warn(`Warning: Could not get latest version for ${packageName}: HTTP ${response.statusCode}`); + console.warn(`Warning: HTTP ${response.statusCode} for ${url}`); resolve(null); } } catch (parseError) { - console.warn(`Warning: Could not parse response for ${packageName}: ${parseError.message}`); + console.warn(`Warning: Could not parse response for ${url}: ${parseError.message}`); resolve(null); } }); }); request.on('error', (requestError) => { - console.warn(`Warning: Could not get latest version for ${packageName}: ${requestError.message}`); + console.warn(`Warning: Request failed for ${url}: ${requestError.message}`); resolve(null); }); request.on('timeout', () => { request.destroy(); - console.warn(`Warning: Timeout getting latest version for ${packageName}`); + console.warn(`Warning: Timeout for ${url}`); resolve(null); }); }); } +async function getVersionsFromGitHubReleases() { + try { + // Get versions from opentelemetry-js releases + const jsReleases = await httpsGet('https://api.github.com/repos/open-telemetry/opentelemetry-js/releases'); + const contribReleases = await httpsGet('https://api.github.com/repos/open-telemetry/opentelemetry-js-contrib/releases'); + + const versions = {}; + + // Process opentelemetry-js releases + if (jsReleases) { + for (const release of jsReleases) { + const tagName = release.tag_name; + + // Core packages: v2.0.0 -> 2.0.0 + if (/^v\d+\.\d+\.\d+$/.test(tagName) && !versions.core) { + versions.core = tagName.substring(1); + } + // Experimental packages: experimental/v0.57.1 -> 0.57.1 + else if (tagName.startsWith('experimental/v') && !versions.experimental) { + versions.experimental = tagName.substring('experimental/v'.length); + } + // API package: api/v1.9.0 -> 1.9.0 + else if (tagName.startsWith('api/v') && !versions.api) { + versions.api = tagName.substring('api/v'.length); + } + // Semantic conventions: semconv/v1.28.0 -> 1.28.0 + else if (tagName.startsWith('semconv/v') && !versions.semconv) { + versions.semconv = tagName.substring('semconv/v'.length); + } + } + } + + // Process opentelemetry-js-contrib releases + if (contribReleases) { + for (const release of contribReleases) { + const tagName = release.tag_name; + + // Extract component name and version from releases like "auto-instrumentations-node: v0.64.4" + const match = tagName.match(/^([^:]+):\s*v(.+)$/); + if (match) { + const componentName = match[1]; + const version = match[2]; + versions[componentName] = version; + } + } + } + + console.log('Found GitHub release versions:', versions); + return versions; + + } catch (error) { + console.warn(`Warning: Could not get GitHub releases: ${error.message}`); + return {}; + } +} + +async function getLatestVersionFromNpm(packageName) { + try { + const data = await httpsGet(`https://registry.npmjs.org/${packageName}/latest`); + return data ? data.version : null; + } catch (error) { + console.warn(`Warning: Could not get npm version for ${packageName}: ${error.message}`); + return null; + } +} + +// Package categorization based on their typical versioning patterns +const PACKAGE_CATEGORIES = { + api: ['@opentelemetry/api'], + core: [ + '@opentelemetry/core', + '@opentelemetry/exporter-zipkin', + '@opentelemetry/resources', + '@opentelemetry/sdk-metrics', + '@opentelemetry/sdk-trace-base' + ], + experimental: [ + '@opentelemetry/api-events', + '@opentelemetry/exporter-metrics-otlp-grpc', + '@opentelemetry/exporter-metrics-otlp-http', + '@opentelemetry/exporter-trace-otlp-proto', + '@opentelemetry/exporter-logs-otlp-grpc', + '@opentelemetry/exporter-logs-otlp-http', + '@opentelemetry/exporter-logs-otlp-proto', + '@opentelemetry/instrumentation', + '@opentelemetry/otlp-transformer', + '@opentelemetry/sdk-events', + '@opentelemetry/sdk-logs', + '@opentelemetry/sdk-node' + ], + semconv: ['@opentelemetry/semantic-conventions'], + // These have individual releases in opentelemetry-js-contrib + contrib: [ + '@opentelemetry/auto-configuration-propagators', + '@opentelemetry/auto-instrumentations-node', + '@opentelemetry/baggage-span-processor', + '@opentelemetry/instrumentation-aws-sdk', + '@opentelemetry/id-generator-aws-xray', + '@opentelemetry/propagator-aws-xray', + '@opentelemetry/resource-detector-aws' + ] +}; + async function main() { const packageJsonPath = path.join('aws-distro-opentelemetry-node-autoinstrumentation', 'package.json'); @@ -47,21 +155,48 @@ async function main() { const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); let updated = false; + // Get versions from GitHub releases + const githubVersions = await getVersionsFromGitHubReleases(); + + // Get all @opentelemetry packages from dependencies const dependencies = packageJson.dependencies || {}; const otelPackages = Object.keys(dependencies).filter(pkg => pkg.startsWith('@opentelemetry/')); - - for (const packageName of otelPackages) { - const latestVersion = await getLatestVersion(packageName); - if (latestVersion) { - const currentVersion = dependencies[packageName]; - if (currentVersion !== latestVersion) { - packageJson.dependencies[packageName] = latestVersion; - updated = true; - console.log(`Updated ${packageName}: ${currentVersion} → ${latestVersion}`); + // Update each package + for (const packageName of otelPackages) { + const currentVersion = dependencies[packageName]; + let newVersion = null; + + // Try to get version from GitHub releases first + if (PACKAGE_CATEGORIES.api.includes(packageName) && githubVersions.api) { + newVersion = githubVersions.api; + } else if (PACKAGE_CATEGORIES.core.includes(packageName) && githubVersions.core) { + newVersion = githubVersions.core; + } else if (PACKAGE_CATEGORIES.experimental.includes(packageName) && githubVersions.experimental) { + newVersion = githubVersions.experimental; + } else if (PACKAGE_CATEGORIES.semconv.includes(packageName) && githubVersions.semconv) { + newVersion = githubVersions.semconv; + } else if (PACKAGE_CATEGORIES.contrib.includes(packageName)) { + // Try to get version from contrib releases by stripping @opentelemetry/ prefix + const componentName = packageName.replace('@opentelemetry/', ''); + if (githubVersions[componentName]) { + newVersion = githubVersions[componentName]; } else { - console.log(`${packageName} already at latest version: ${latestVersion}`); + // Fall back to npm registry + newVersion = await getLatestVersionFromNpm(packageName); } + } else { + // Fall back to npm registry for any uncategorized packages + console.log(`Package ${packageName} not categorized, fetching version from npm`); + newVersion = await getLatestVersionFromNpm(packageName); + } + + if (newVersion && currentVersion !== newVersion) { + packageJson.dependencies[packageName] = newVersion; + updated = true; + console.log(`Updated ${packageName}: ${currentVersion} → ${newVersion}`); + } else if (newVersion) { + console.log(`${packageName} already at latest version: ${newVersion}`); } } From 7cad67fe6a0866d7a025345000afd7801e90cb08 Mon Sep 17 00:00:00 2001 From: Eric Zhang Date: Fri, 26 Sep 2025 10:14:11 -0700 Subject: [PATCH 06/20] add script to find breaking changes --- .github/workflows/nightly-build.yml | 19 +- scripts/find_breaking_changes.js | 289 ++++++++++++++++++++++++++++ 2 files changed, 302 insertions(+), 6 deletions(-) create mode 100644 scripts/find_breaking_changes.js diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml index 67f7791b..df5b29b9 100644 --- a/.github/workflows/nightly-build.yml +++ b/.github/workflows/nightly-build.yml @@ -29,17 +29,21 @@ jobs: exit 1 fi + - name: Set up Node.js + uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 #v5.0.0 + with: + node-version: '18' + + - name: Check for breaking changes + id: breaking_changes + run: node scripts/find_breaking_changes.js + - name: Configure git and create branch run: | git config --local user.email "action@github.com" git config --local user.name "GitHub Action" git checkout -b "$BRANCH_NAME" - - name: Set up Node.js - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 #v5.0.0 - with: - node-version: '18' - - name: Install dependencies run: npm install @@ -62,7 +66,10 @@ jobs: gh pr create \ --title "Nightly dependency update: OpenTelemetry packages to latest versions" \ - --body "Automated update of OpenTelemetry dependencies to their latest available versions." \ + --body "Automated update of OpenTelemetry dependencies to their latest available versions. + + **Upstream releases with breaking changes:** + ${{ steps.breaking_changes.outputs.breaking_changes_info }}" \ --base main \ --head "$BRANCH_NAME" fi diff --git a/scripts/find_breaking_changes.js b/scripts/find_breaking_changes.js new file mode 100644 index 00000000..03e0fc12 --- /dev/null +++ b/scripts/find_breaking_changes.js @@ -0,0 +1,289 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); + +async function httpsGet(url) { + const https = require('https'); + + return new Promise((resolve, reject) => { + const options = { + timeout: 30000, + headers: { + 'User-Agent': 'Mozilla/5.0 (compatible; Node.js script)' + } + }; + + const request = https.get(url, options, (response) => { + let data = ''; + response.on('data', (chunk) => data += chunk); + response.on('end', () => { + try { + if (response.statusCode === 200) { + resolve(JSON.parse(data)); + } else { + console.warn(`Warning: HTTP ${response.statusCode} for ${url}`); + resolve(null); + } + } catch (parseError) { + console.warn(`Warning: Could not parse response for ${url}: ${parseError.message}`); + resolve(null); + } + }); + }); + + request.on('error', (requestError) => { + console.warn(`Warning: Request failed for ${url}: ${requestError.message}`); + resolve(null); + }); + + request.on('timeout', () => { + request.destroy(); + console.warn(`Warning: Timeout for ${url}`); + resolve(null); + }); + }); +} + +function getCurrentVersionsFromPackageJson() { + try { + const packageJsonPath = path.join('aws-distro-opentelemetry-node-autoinstrumentation', 'package.json'); + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); + + const dependencies = packageJson.dependencies || {}; + + // Find representative versions for each category + const currentVersions = {}; + + // API version + if (dependencies['@opentelemetry/api']) { + currentVersions.api = dependencies['@opentelemetry/api']; + } + + // Core version (use sdk-trace-base as representative) + if (dependencies['@opentelemetry/sdk-trace-base']) { + currentVersions.core = dependencies['@opentelemetry/sdk-trace-base']; + } + + // Experimental version (use sdk-node as representative) + if (dependencies['@opentelemetry/sdk-node']) { + currentVersions.experimental = dependencies['@opentelemetry/sdk-node']; + } + + // Semconv version + if (dependencies['@opentelemetry/semantic-conventions']) { + currentVersions.semconv = dependencies['@opentelemetry/semantic-conventions']; + } + + // Get all contrib packages we actually depend on + const contribPackages = {}; + for (const [packageName, version] of Object.entries(dependencies)) { + if (packageName.startsWith('@opentelemetry/') && + !['@opentelemetry/api', '@opentelemetry/sdk-trace-base', '@opentelemetry/sdk-node', '@opentelemetry/semantic-conventions'].includes(packageName)) { + // Check if it's likely a contrib package (not in core/experimental categories) + const componentName = packageName.replace('@opentelemetry/', ''); + contribPackages[componentName] = version; + } + } + + currentVersions.contrib = contribPackages; + + return currentVersions; + + } catch (error) { + console.warn(`Error reading current versions: ${error.message}`); + return {}; + } +} + +function compareVersions(current, target) { + // Simple version comparison - assumes semver format + const currentParts = current.split('.').map(Number); + const targetParts = target.split('.').map(Number); + + for (let i = 0; i < Math.max(currentParts.length, targetParts.length); i++) { + const currentPart = currentParts[i] || 0; + const targetPart = targetParts[i] || 0; + + if (currentPart < targetPart) return -1; + if (currentPart > targetPart) return 1; + } + + return 0; +} + +async function findBreakingChangesInReleases(repoName, currentVersion, newVersion, releasePattern) { + try { + const releases = await httpsGet(`https://api.github.com/repos/open-telemetry/${repoName}/releases?per_page=100`); + if (!releases) return []; + + const breakingReleases = []; + + for (const release of releases) { + const tagName = release.tag_name; + let releaseVersion = null; + + // Extract version based on pattern + if (releasePattern === 'core' && /^v\d+\.\d+\.\d+$/.test(tagName)) { + releaseVersion = tagName.substring(1); + } else if (releasePattern === 'experimental' && tagName.startsWith('experimental/v')) { + releaseVersion = tagName.substring('experimental/v'.length); + } else if (releasePattern === 'api' && tagName.startsWith('api/v')) { + releaseVersion = tagName.substring('api/v'.length); + } else if (releasePattern === 'semconv' && tagName.startsWith('semconv/v')) { + releaseVersion = tagName.substring('semconv/v'.length); + } + + if (releaseVersion) { + // Check if this release is between current and new version + if (compareVersions(releaseVersion, currentVersion) > 0 && + compareVersions(releaseVersion, newVersion) <= 0) { + + // Check if release notes mention breaking changes + const body = release.body || ''; + if (body.includes('💥 Breaking Changes')) { + breakingReleases.push({ + version: releaseVersion, + name: release.name || tagName, + url: release.html_url + }); + } + } + } + } + + return breakingReleases; + + } catch (error) { + console.warn(`Warning: Could not get releases for ${repoName}: ${error.message}`); + return []; + } +} + +async function findContribBreakingChanges(currentContribPackages, newContribVersions) { + try { + const releases = await httpsGet('https://api.github.com/repos/open-telemetry/opentelemetry-js-contrib/releases?per_page=100'); + if (!releases) return []; + + const breakingReleases = []; + + for (const release of releases) { + const tagName = release.tag_name; + + // Extract component name and version from releases like "auto-instrumentations-node: v0.64.4" + const match = tagName.match(/^([^:]+):\s*v(.+)$/); + if (match) { + const componentName = match[1]; + const releaseVersion = match[2]; + + // Check if this is a package we depend on + if (currentContribPackages[componentName]) { + const currentVersion = currentContribPackages[componentName]; + const newVersion = newContribVersions[componentName]; + + if (newVersion && + compareVersions(releaseVersion, currentVersion) > 0 && + compareVersions(releaseVersion, newVersion) <= 0) { + + // Check if release notes mention breaking changes + const body = release.body || ''; + if (body.includes('⚠ BREAKING CHANGES')) { + breakingReleases.push({ + component: componentName, + version: releaseVersion, + name: release.name || tagName, + url: release.html_url + }); + } + } + } + } + } + + return breakingReleases; + + } catch (error) { + console.warn(`Warning: Could not get contrib releases: ${error.message}`); + return []; + } +} +async function main() { + const newCoreVersion = process.env.OTEL_CORE_VERSION; + const newExperimentalVersion = process.env.OTEL_EXPERIMENTAL_VERSION; + const newApiVersion = process.env.OTEL_API_VERSION; + const newSemconvVersion = process.env.OTEL_SEMCONV_VERSION; + + if (!newCoreVersion && !newExperimentalVersion && !newApiVersion && !newSemconvVersion) { + console.error('Error: At least one version environment variable required'); + process.exit(1); + } + + const currentVersions = getCurrentVersionsFromPackageJson(); + + console.log('Checking for breaking changes in JS releases...'); + + let breakingInfo = ''; + + // Check core releases + if (newCoreVersion && currentVersions.core) { + const coreBreaking = await findBreakingChangesInReleases( + 'opentelemetry-js', + currentVersions.core, + newCoreVersion, + 'core' + ); + + if (coreBreaking.length > 0) { + breakingInfo += '**opentelemetry-js (core):**\n'; + for (const release of coreBreaking) { + breakingInfo += `- [${release.name}](${release.url})\n`; + } + } + } + + // Check experimental releases + if (newExperimentalVersion && currentVersions.experimental) { + const experimentalBreaking = await findBreakingChangesInReleases( + 'opentelemetry-js', + currentVersions.experimental, + newExperimentalVersion, + 'experimental' + ); + + if (experimentalBreaking.length > 0) { + breakingInfo += '**opentelemetry-js (experimental):**\n'; + for (const release of experimentalBreaking) { + breakingInfo += `- [${release.name}](${release.url})\n`; + } + } + } + + // Check contrib releases for packages we actually depend on + if (currentVersions.contrib) { + // We need to get the new contrib versions from the update script + // For now, we'll check all contrib packages we depend on + const contribBreaking = await findContribBreakingChanges(currentVersions.contrib, {}); + + if (contribBreaking.length > 0) { + breakingInfo += '**opentelemetry-js-contrib:**\n'; + for (const release of contribBreaking) { + breakingInfo += `- [${release.name}](${release.url})\n`; + } + } + } + + // Set GitHub output + if (process.env.GITHUB_OUTPUT) { + fs.appendFileSync(process.env.GITHUB_OUTPUT, `breaking_changes_info< Date: Fri, 26 Sep 2025 10:19:09 -0700 Subject: [PATCH 07/20] have nightly build call main build --- .github/workflows/main-build.yml | 9 +++++++++ .github/workflows/nightly-build.yml | 11 +++++++++++ 2 files changed, 20 insertions(+) diff --git a/.github/workflows/main-build.yml b/.github/workflows/main-build.yml index 51e43ac8..c1185ff0 100644 --- a/.github/workflows/main-build.yml +++ b/.github/workflows/main-build.yml @@ -6,6 +6,13 @@ on: - main - "release/v*" workflow_dispatch: + workflow_call: + inputs: + ref: + description: 'The branch, tag or SHA to checkout' + required: false + type: string + default: '' env: AWS_DEFAULT_REGION: us-east-1 @@ -34,6 +41,8 @@ jobs: steps: - name: Checkout Contrib Repo @ SHA - ${{ github.sha }} uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0 + with: + ref: ${{ inputs.ref || github.sha }} - name: Get Node Distro Output id: node_output diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml index df5b29b9..c2ad72c0 100644 --- a/.github/workflows/nightly-build.yml +++ b/.github/workflows/nightly-build.yml @@ -75,3 +75,14 @@ jobs: fi env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + build-and-test: + needs: update-and-create-pr + if: needs.update-and-create-pr.outputs.has_changes == 'true' + uses: ./.github/workflows/main-build.yml + secrets: inherit + permissions: + id-token: write + contents: read + with: + ref: nightly-dependency-updates From 05402c09a66948288c0f2077749fa0cb8711ea06 Mon Sep 17 00:00:00 2001 From: Eric Zhang Date: Fri, 26 Sep 2025 10:27:06 -0700 Subject: [PATCH 08/20] fix branching and PR logic --- .github/workflows/nightly-build.yml | 47 ++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml index c2ad72c0..0d940db1 100644 --- a/.github/workflows/nightly-build.yml +++ b/.github/workflows/nightly-build.yml @@ -8,6 +8,11 @@ on: env: BRANCH_NAME: nightly-dependency-updates +permissions: + contents: write + pull-requests: write + id-token: write + jobs: update-and-create-pr: runs-on: ubuntu-latest @@ -21,14 +26,6 @@ jobs: fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} - - name: Check if nightly branch already exists - run: | - if git ls-remote --exit-code --heads origin "$BRANCH_NAME"; then - echo "Branch $BRANCH_NAME already exists. Skipping run to avoid conflicts." - echo "Please merge or close the existing PR before the next nightly run." - exit 1 - fi - - name: Set up Node.js uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 #v5.0.0 with: @@ -42,7 +39,16 @@ jobs: run: | git config --local user.email "action@github.com" git config --local user.name "GitHub Action" - git checkout -b "$BRANCH_NAME" + + - name: Check out dependency update branch + run: | + if git ls-remote --exit-code --heads origin "$BRANCH_NAME"; then + echo "Branch $BRANCH_NAME already exists, checking out..." + git checkout "$BRANCH_NAME" + else + echo "Branch $BRANCH_NAME does not exist, creating new branch..." + git checkout -b "$BRANCH_NAME" + fi - name: Install dependencies run: npm install @@ -50,7 +56,7 @@ jobs: - name: Update dependencies run: node scripts/update_dependencies.js - - name: Check for changes and create PR + - name: Check for changes and commit id: check_changes run: | if git diff --quiet; then @@ -63,13 +69,24 @@ jobs: git add aws-distro-opentelemetry-node-autoinstrumentation/package.json git commit -m "chore: update OpenTelemetry dependencies to latest versions" git push origin "$BRANCH_NAME" - + fi + + - name: Create or update PR + if: steps.check_changes.outputs.has_changes == 'true' + run: | + PR_BODY="Automated update of OpenTelemetry dependencies to their latest available versions. + + **Upstream releases with breaking changes:** + ${{ steps.breaking_changes.outputs.breaking_changes_info }}" + + if gh pr view "$BRANCH_NAME" --json state --jq '.state' 2>/dev/null | grep -q "OPEN"; then + echo "Open PR already exists, updating description..." + gh pr edit "$BRANCH_NAME" --body "$PR_BODY" + else + echo "Creating new PR..." gh pr create \ --title "Nightly dependency update: OpenTelemetry packages to latest versions" \ - --body "Automated update of OpenTelemetry dependencies to their latest available versions. - - **Upstream releases with breaking changes:** - ${{ steps.breaking_changes.outputs.breaking_changes_info }}" \ + --body "$PR_BODY" \ --base main \ --head "$BRANCH_NAME" fi From 1f711a21bbf0deb96e35ca9a32ebc1523d1c7c4c Mon Sep 17 00:00:00 2001 From: Eric Zhang Date: Fri, 26 Sep 2025 10:27:36 -0700 Subject: [PATCH 09/20] add push trigger for testing --- .github/workflows/nightly-build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml index 0d940db1..947a58ff 100644 --- a/.github/workflows/nightly-build.yml +++ b/.github/workflows/nightly-build.yml @@ -4,6 +4,9 @@ on: schedule: - cron: "21 3 * * *" workflow_dispatch: + push: + branches: + - zhaez/nightly-build env: BRANCH_NAME: nightly-dependency-updates From 60a7b99069749a79b7072769a0e839897a397933 Mon Sep 17 00:00:00 2001 From: Eric Zhang Date: Fri, 26 Sep 2025 10:43:21 -0700 Subject: [PATCH 10/20] extract scripts for getting upstream versions --- scripts/find_breaking_changes.js | 59 +++++++++++----- scripts/get_upstream_versions.js | 117 +++++++++++++++++++++++++++++++ scripts/update_dependencies.js | 59 +--------------- 3 files changed, 162 insertions(+), 73 deletions(-) create mode 100644 scripts/get_upstream_versions.js diff --git a/scripts/find_breaking_changes.js b/scripts/find_breaking_changes.js index 03e0fc12..bc40623a 100644 --- a/scripts/find_breaking_changes.js +++ b/scripts/find_breaking_changes.js @@ -2,6 +2,7 @@ const fs = require('fs'); const path = require('path'); +const { getLatestVersionsFromGitHub } = require('./get_upstream_versions.js'); async function httpsGet(url) { const https = require('https'); @@ -207,16 +208,10 @@ async function findContribBreakingChanges(currentContribPackages, newContribVers return []; } } + async function main() { - const newCoreVersion = process.env.OTEL_CORE_VERSION; - const newExperimentalVersion = process.env.OTEL_EXPERIMENTAL_VERSION; - const newApiVersion = process.env.OTEL_API_VERSION; - const newSemconvVersion = process.env.OTEL_SEMCONV_VERSION; - - if (!newCoreVersion && !newExperimentalVersion && !newApiVersion && !newSemconvVersion) { - console.error('Error: At least one version environment variable required'); - process.exit(1); - } + console.log('Getting latest versions from GitHub...'); + const latestVersions = await getLatestVersionsFromGitHub(); const currentVersions = getCurrentVersionsFromPackageJson(); @@ -225,11 +220,11 @@ async function main() { let breakingInfo = ''; // Check core releases - if (newCoreVersion && currentVersions.core) { + if (latestVersions.core && currentVersions.core) { const coreBreaking = await findBreakingChangesInReleases( 'opentelemetry-js', currentVersions.core, - newCoreVersion, + latestVersions.core, 'core' ); @@ -242,11 +237,11 @@ async function main() { } // Check experimental releases - if (newExperimentalVersion && currentVersions.experimental) { + if (latestVersions.experimental && currentVersions.experimental) { const experimentalBreaking = await findBreakingChangesInReleases( 'opentelemetry-js', currentVersions.experimental, - newExperimentalVersion, + latestVersions.experimental, 'experimental' ); @@ -258,11 +253,43 @@ async function main() { } } + // Check API releases + if (latestVersions.api && currentVersions.api) { + const apiBreaking = await findBreakingChangesInReleases( + 'opentelemetry-js', + currentVersions.api, + latestVersions.api, + 'api' + ); + + if (apiBreaking.length > 0) { + breakingInfo += '**opentelemetry-js (api):**\n'; + for (const release of apiBreaking) { + breakingInfo += `- [${release.name}](${release.url})\n`; + } + } + } + + // Check semconv releases + if (latestVersions.semconv && currentVersions.semconv) { + const semconvBreaking = await findBreakingChangesInReleases( + 'opentelemetry-js', + currentVersions.semconv, + latestVersions.semconv, + 'semconv' + ); + + if (semconvBreaking.length > 0) { + breakingInfo += '**opentelemetry-js (semconv):**\n'; + for (const release of semconvBreaking) { + breakingInfo += `- [${release.name}](${release.url})\n`; + } + } + } + // Check contrib releases for packages we actually depend on if (currentVersions.contrib) { - // We need to get the new contrib versions from the update script - // For now, we'll check all contrib packages we depend on - const contribBreaking = await findContribBreakingChanges(currentVersions.contrib, {}); + const contribBreaking = await findContribBreakingChanges(currentVersions.contrib, latestVersions); if (contribBreaking.length > 0) { breakingInfo += '**opentelemetry-js-contrib:**\n'; diff --git a/scripts/get_upstream_versions.js b/scripts/get_upstream_versions.js new file mode 100644 index 00000000..009b91b6 --- /dev/null +++ b/scripts/get_upstream_versions.js @@ -0,0 +1,117 @@ +#!/usr/bin/env node + +const fs = require('fs'); + +async function httpsGet(url) { + const https = require('https'); + + return new Promise((resolve, reject) => { + const options = { + timeout: 30000, + headers: { + 'User-Agent': 'Mozilla/5.0 (compatible; Node.js script)' + } + }; + + const request = https.get(url, options, (response) => { + let data = ''; + response.on('data', (chunk) => data += chunk); + response.on('end', () => { + try { + if (response.statusCode === 200) { + resolve(JSON.parse(data)); + } else { + console.warn(`Warning: HTTP ${response.statusCode} for ${url}`); + resolve(null); + } + } catch (parseError) { + console.warn(`Warning: Could not parse response for ${url}: ${parseError.message}`); + resolve(null); + } + }); + }); + + request.on('error', (requestError) => { + console.warn(`Warning: Request failed for ${url}: ${requestError.message}`); + resolve(null); + }); + + request.on('timeout', () => { + request.destroy(); + console.warn(`Warning: Timeout for ${url}`); + resolve(null); + }); + }); +} + +async function getLatestVersionsFromGitHub() { + try { + // Get versions from opentelemetry-js releases + const jsReleases = await httpsGet('https://api.github.com/repos/open-telemetry/opentelemetry-js/releases?per_page=100'); + const contribReleases = await httpsGet('https://api.github.com/repos/open-telemetry/opentelemetry-js-contrib/releases?per_page=100'); + + console.log('JS releases found:', jsReleases ? jsReleases.length : 'none'); + console.log('Contrib releases found:', contribReleases ? contribReleases.length : 'none'); + + const versions = {}; + + // Process opentelemetry-js releases + if (jsReleases) { + for (const release of jsReleases) { + const tagName = release.tag_name; + + // Core packages: v2.0.0 -> 2.0.0 (only keep first/newest) + if (/^v\d+\.\d+\.\d+$/.test(tagName) && !versions.core) { + versions.core = tagName.substring(1); + } + // Experimental packages: experimental/v0.57.1 -> 0.57.1 (only keep first/newest) + else if (tagName.startsWith('experimental/v') && !versions.experimental) { + versions.experimental = tagName.substring('experimental/v'.length); + } + // API package: api/v1.9.0 -> 1.9.0 (only keep first/newest) + else if (tagName.startsWith('api/v') && !versions.api) { + versions.api = tagName.substring('api/v'.length); + } + // Semantic conventions: semconv/v1.28.0 -> 1.28.0 (only keep first/newest) + else if (tagName.startsWith('semconv/v') && !versions.semconv) { + versions.semconv = tagName.substring('semconv/v'.length); + } + } + } + + // Process opentelemetry-js-contrib releases + if (contribReleases) { + for (const release of contribReleases) { + const tagName = release.tag_name; + + // Extract component name and version from releases like "auto-instrumentations-node: v0.64.4" + const match = tagName.match(/^([^:]+):\s*v(.+)$/); + if (match) { + const componentName = match[1]; + const version = match[2]; + if (!versions[componentName]) { + versions[componentName] = version; + } + } + } + } + + console.log('Found GitHub release versions:', versions); + + return versions; + + } catch (error) { + console.warn(`Warning: Could not get GitHub releases: ${error.message}`); + return {}; + } +} + +async function main() { + await getLatestVersionsFromGitHub(); +} + +if (require.main === module) { + main().catch(console.error); +} + +module.exports = { getLatestVersionsFromGitHub }; diff --git a/scripts/update_dependencies.js b/scripts/update_dependencies.js index 735fe1aa..2d2c4124 100644 --- a/scripts/update_dependencies.js +++ b/scripts/update_dependencies.js @@ -2,6 +2,7 @@ const fs = require('fs'); const path = require('path'); +const { getLatestVersionsFromGitHub } = require('./get_upstream_versions.js'); async function httpsGet(url) { const https = require('https'); @@ -45,62 +46,6 @@ async function httpsGet(url) { }); } -async function getVersionsFromGitHubReleases() { - try { - // Get versions from opentelemetry-js releases - const jsReleases = await httpsGet('https://api.github.com/repos/open-telemetry/opentelemetry-js/releases'); - const contribReleases = await httpsGet('https://api.github.com/repos/open-telemetry/opentelemetry-js-contrib/releases'); - - const versions = {}; - - // Process opentelemetry-js releases - if (jsReleases) { - for (const release of jsReleases) { - const tagName = release.tag_name; - - // Core packages: v2.0.0 -> 2.0.0 - if (/^v\d+\.\d+\.\d+$/.test(tagName) && !versions.core) { - versions.core = tagName.substring(1); - } - // Experimental packages: experimental/v0.57.1 -> 0.57.1 - else if (tagName.startsWith('experimental/v') && !versions.experimental) { - versions.experimental = tagName.substring('experimental/v'.length); - } - // API package: api/v1.9.0 -> 1.9.0 - else if (tagName.startsWith('api/v') && !versions.api) { - versions.api = tagName.substring('api/v'.length); - } - // Semantic conventions: semconv/v1.28.0 -> 1.28.0 - else if (tagName.startsWith('semconv/v') && !versions.semconv) { - versions.semconv = tagName.substring('semconv/v'.length); - } - } - } - - // Process opentelemetry-js-contrib releases - if (contribReleases) { - for (const release of contribReleases) { - const tagName = release.tag_name; - - // Extract component name and version from releases like "auto-instrumentations-node: v0.64.4" - const match = tagName.match(/^([^:]+):\s*v(.+)$/); - if (match) { - const componentName = match[1]; - const version = match[2]; - versions[componentName] = version; - } - } - } - - console.log('Found GitHub release versions:', versions); - return versions; - - } catch (error) { - console.warn(`Warning: Could not get GitHub releases: ${error.message}`); - return {}; - } -} - async function getLatestVersionFromNpm(packageName) { try { const data = await httpsGet(`https://registry.npmjs.org/${packageName}/latest`); @@ -156,7 +101,7 @@ async function main() { let updated = false; // Get versions from GitHub releases - const githubVersions = await getVersionsFromGitHubReleases(); + const githubVersions = await getLatestVersionsFromGitHub(); // Get all @opentelemetry packages from dependencies const dependencies = packageJson.dependencies || {}; From 9cb1714cdec673b85f6835c5ef424b7db127431b Mon Sep 17 00:00:00 2001 From: Eric Zhang Date: Fri, 26 Sep 2025 10:50:58 -0700 Subject: [PATCH 11/20] match more patterns for breaking changes --- scripts/find_breaking_changes.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/scripts/find_breaking_changes.js b/scripts/find_breaking_changes.js index bc40623a..811645ed 100644 --- a/scripts/find_breaking_changes.js +++ b/scripts/find_breaking_changes.js @@ -140,9 +140,11 @@ async function findBreakingChangesInReleases(repoName, currentVersion, newVersio if (compareVersions(releaseVersion, currentVersion) > 0 && compareVersions(releaseVersion, newVersion) <= 0) { - // Check if release notes mention breaking changes + // Check if release notes mention breaking changes (multiple patterns) const body = release.body || ''; - if (body.includes('💥 Breaking Changes')) { + if (body.includes('💥 Breaking Changes') || + body.includes('Breaking changes') || + body.includes('BREAKING CHANGES')) { breakingReleases.push({ version: releaseVersion, name: release.name || tagName, @@ -186,9 +188,11 @@ async function findContribBreakingChanges(currentContribPackages, newContribVers compareVersions(releaseVersion, currentVersion) > 0 && compareVersions(releaseVersion, newVersion) <= 0) { - // Check if release notes mention breaking changes + // Check if release notes mention breaking changes (multiple patterns) const body = release.body || ''; - if (body.includes('⚠ BREAKING CHANGES')) { + if (body.includes('⚠ BREAKING CHANGES') || + body.includes('Breaking changes') || + body.includes('BREAKING CHANGES')) { breakingReleases.push({ component: componentName, version: releaseVersion, From ca0a2ee4b1a37e1eb45bb6e4d768be1e55c645b2 Mon Sep 17 00:00:00 2001 From: Eric Zhang Date: Fri, 26 Sep 2025 11:00:10 -0700 Subject: [PATCH 12/20] use regex matching for breaking changes header --- scripts/find_breaking_changes.js | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/scripts/find_breaking_changes.js b/scripts/find_breaking_changes.js index 811645ed..dcf8644e 100644 --- a/scripts/find_breaking_changes.js +++ b/scripts/find_breaking_changes.js @@ -140,11 +140,10 @@ async function findBreakingChangesInReleases(repoName, currentVersion, newVersio if (compareVersions(releaseVersion, currentVersion) > 0 && compareVersions(releaseVersion, newVersion) <= 0) { - // Check if release notes mention breaking changes (multiple patterns) + // Check if release notes have breaking changes as markdown headers const body = release.body || ''; - if (body.includes('💥 Breaking Changes') || - body.includes('Breaking changes') || - body.includes('BREAKING CHANGES')) { + const breakingHeaderRegex = /^#+.*breaking changes/im; + if (breakingHeaderRegex.test(body)) { breakingReleases.push({ version: releaseVersion, name: release.name || tagName, @@ -188,11 +187,10 @@ async function findContribBreakingChanges(currentContribPackages, newContribVers compareVersions(releaseVersion, currentVersion) > 0 && compareVersions(releaseVersion, newVersion) <= 0) { - // Check if release notes mention breaking changes (multiple patterns) + // Check if release notes have breaking changes as markdown headers const body = release.body || ''; - if (body.includes('⚠ BREAKING CHANGES') || - body.includes('Breaking changes') || - body.includes('BREAKING CHANGES')) { + const breakingHeaderRegex = /^#+.*breaking changes/im; + if (breakingHeaderRegex.test(body)) { breakingReleases.push({ component: componentName, version: releaseVersion, From 9aa1b704935861070c8735d061c7bbde7fd2c863 Mon Sep 17 00:00:00 2001 From: Eric Zhang Date: Fri, 26 Sep 2025 12:04:20 -0700 Subject: [PATCH 13/20] fetch more pages from contrib repo --- scripts/find_breaking_changes.js | 16 +++++++++++++--- scripts/get_upstream_versions.js | 19 +++++++++++++++---- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/scripts/find_breaking_changes.js b/scripts/find_breaking_changes.js index dcf8644e..36a35240 100644 --- a/scripts/find_breaking_changes.js +++ b/scripts/find_breaking_changes.js @@ -164,12 +164,22 @@ async function findBreakingChangesInReleases(repoName, currentVersion, newVersio async function findContribBreakingChanges(currentContribPackages, newContribVersions) { try { - const releases = await httpsGet('https://api.github.com/repos/open-telemetry/opentelemetry-js-contrib/releases?per_page=100'); - if (!releases) return []; + // Fetch multiple pages of contrib releases since they release frequently + const releases1 = await httpsGet('https://api.github.com/repos/open-telemetry/opentelemetry-js-contrib/releases?per_page=100&page=1'); + const releases2 = await httpsGet('https://api.github.com/repos/open-telemetry/opentelemetry-js-contrib/releases?per_page=100&page=2'); + const releases3 = await httpsGet('https://api.github.com/repos/open-telemetry/opentelemetry-js-contrib/releases?per_page=100&page=3'); + + const allReleases = [ + ...(releases1 || []), + ...(releases2 || []), + ...(releases3 || []) + ]; + + if (allReleases.length === 0) return []; const breakingReleases = []; - for (const release of releases) { + for (const release of allReleases) { const tagName = release.tag_name; // Extract component name and version from releases like "auto-instrumentations-node: v0.64.4" diff --git a/scripts/get_upstream_versions.js b/scripts/get_upstream_versions.js index 009b91b6..7e585a9f 100644 --- a/scripts/get_upstream_versions.js +++ b/scripts/get_upstream_versions.js @@ -48,10 +48,21 @@ async function getLatestVersionsFromGitHub() { try { // Get versions from opentelemetry-js releases const jsReleases = await httpsGet('https://api.github.com/repos/open-telemetry/opentelemetry-js/releases?per_page=100'); - const contribReleases = await httpsGet('https://api.github.com/repos/open-telemetry/opentelemetry-js-contrib/releases?per_page=100'); + const contribReleases = await httpsGet('https://api.github.com/repos/open-telemetry/opentelemetry-js-contrib/releases?per_page=100&page=1'); + + // Get additional contrib releases (they release more frequently) + const contribReleases2 = await httpsGet('https://api.github.com/repos/open-telemetry/opentelemetry-js-contrib/releases?per_page=100&page=2'); + const contribReleases3 = await httpsGet('https://api.github.com/repos/open-telemetry/opentelemetry-js-contrib/releases?per_page=100&page=3'); + + // Combine contrib releases + const allContribReleases = [ + ...(contribReleases || []), + ...(contribReleases2 || []), + ...(contribReleases3 || []) + ]; console.log('JS releases found:', jsReleases ? jsReleases.length : 'none'); - console.log('Contrib releases found:', contribReleases ? contribReleases.length : 'none'); + console.log('Contrib releases found:', allContribReleases.length); const versions = {}; @@ -80,8 +91,8 @@ async function getLatestVersionsFromGitHub() { } // Process opentelemetry-js-contrib releases - if (contribReleases) { - for (const release of contribReleases) { + if (allContribReleases.length > 0) { + for (const release of allContribReleases) { const tagName = release.tag_name; // Extract component name and version from releases like "auto-instrumentations-node: v0.64.4" From a09a92dc06566fc5ed07e835e8973c403724cc43 Mon Sep 17 00:00:00 2001 From: Eric Zhang Date: Fri, 26 Sep 2025 12:08:52 -0700 Subject: [PATCH 14/20] log contrib releases found --- scripts/find_breaking_changes.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/find_breaking_changes.js b/scripts/find_breaking_changes.js index 36a35240..453b68b8 100644 --- a/scripts/find_breaking_changes.js +++ b/scripts/find_breaking_changes.js @@ -187,6 +187,7 @@ async function findContribBreakingChanges(currentContribPackages, newContribVers if (match) { const componentName = match[1]; const releaseVersion = match[2]; + console.log(`Found contrib release: ${componentName} version ${releaseVersion}`); // Check if this is a package we depend on if (currentContribPackages[componentName]) { From de00bef3e1572e5f9eb144c435ac4c652af46097 Mon Sep 17 00:00:00 2001 From: Eric Zhang Date: Fri, 26 Sep 2025 12:11:01 -0700 Subject: [PATCH 15/20] add log --- scripts/find_breaking_changes.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/find_breaking_changes.js b/scripts/find_breaking_changes.js index 453b68b8..351c4780 100644 --- a/scripts/find_breaking_changes.js +++ b/scripts/find_breaking_changes.js @@ -180,6 +180,7 @@ async function findContribBreakingChanges(currentContribPackages, newContribVers const breakingReleases = []; for (const release of allReleases) { + console.log(`Processing release: ${release.tag_name}`); const tagName = release.tag_name; // Extract component name and version from releases like "auto-instrumentations-node: v0.64.4" From 2c39cd7df4b48d18c250fdbb90c768511a7a9b17 Mon Sep 17 00:00:00 2001 From: Eric Zhang Date: Fri, 26 Sep 2025 12:13:57 -0700 Subject: [PATCH 16/20] fix tag parsing for contrib packages --- scripts/find_breaking_changes.js | 4 ++-- scripts/get_upstream_versions.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/find_breaking_changes.js b/scripts/find_breaking_changes.js index 351c4780..6f7b90c7 100644 --- a/scripts/find_breaking_changes.js +++ b/scripts/find_breaking_changes.js @@ -183,8 +183,8 @@ async function findContribBreakingChanges(currentContribPackages, newContribVers console.log(`Processing release: ${release.tag_name}`); const tagName = release.tag_name; - // Extract component name and version from releases like "auto-instrumentations-node: v0.64.4" - const match = tagName.match(/^([^:]+):\s*v(.+)$/); + // Extract component name and version from releases like "resource-detector-aws-v2.3.0" + const match = tagName.match(/^(.+)-v(.+)$/); if (match) { const componentName = match[1]; const releaseVersion = match[2]; diff --git a/scripts/get_upstream_versions.js b/scripts/get_upstream_versions.js index 7e585a9f..ec7f8180 100644 --- a/scripts/get_upstream_versions.js +++ b/scripts/get_upstream_versions.js @@ -95,8 +95,8 @@ async function getLatestVersionsFromGitHub() { for (const release of allContribReleases) { const tagName = release.tag_name; - // Extract component name and version from releases like "auto-instrumentations-node: v0.64.4" - const match = tagName.match(/^([^:]+):\s*v(.+)$/); + // Extract component name and version from releases like "resource-detector-aws-v2.3.0" + const match = tagName.match(/^(.+)-v(.+)$/); if (match) { const componentName = match[1]; const version = match[2]; From 53d199cd3efb616cbdc38814e896e8fb7c07d3b5 Mon Sep 17 00:00:00 2001 From: Eric Zhang Date: Fri, 26 Sep 2025 12:27:24 -0700 Subject: [PATCH 17/20] fix PR breaking change formatting --- scripts/find_breaking_changes.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/scripts/find_breaking_changes.js b/scripts/find_breaking_changes.js index 6f7b90c7..3f07f1c6 100644 --- a/scripts/find_breaking_changes.js +++ b/scripts/find_breaking_changes.js @@ -180,7 +180,6 @@ async function findContribBreakingChanges(currentContribPackages, newContribVers const breakingReleases = []; for (const release of allReleases) { - console.log(`Processing release: ${release.tag_name}`); const tagName = release.tag_name; // Extract component name and version from releases like "resource-detector-aws-v2.3.0" @@ -188,7 +187,6 @@ async function findContribBreakingChanges(currentContribPackages, newContribVers if (match) { const componentName = match[1]; const releaseVersion = match[2]; - console.log(`Found contrib release: ${componentName} version ${releaseVersion}`); // Check if this is a package we depend on if (currentContribPackages[componentName]) { @@ -243,7 +241,7 @@ async function main() { ); if (coreBreaking.length > 0) { - breakingInfo += '**opentelemetry-js (core):**\n'; + breakingInfo += '\n**opentelemetry-js (core):**\n'; for (const release of coreBreaking) { breakingInfo += `- [${release.name}](${release.url})\n`; } @@ -260,7 +258,7 @@ async function main() { ); if (experimentalBreaking.length > 0) { - breakingInfo += '**opentelemetry-js (experimental):**\n'; + breakingInfo += '\n**opentelemetry-js (experimental):**\n'; for (const release of experimentalBreaking) { breakingInfo += `- [${release.name}](${release.url})\n`; } @@ -277,7 +275,7 @@ async function main() { ); if (apiBreaking.length > 0) { - breakingInfo += '**opentelemetry-js (api):**\n'; + breakingInfo += '\n**opentelemetry-js (api):**\n'; for (const release of apiBreaking) { breakingInfo += `- [${release.name}](${release.url})\n`; } @@ -294,7 +292,7 @@ async function main() { ); if (semconvBreaking.length > 0) { - breakingInfo += '**opentelemetry-js (semconv):**\n'; + breakingInfo += '\n**opentelemetry-js (semconv):**\n'; for (const release of semconvBreaking) { breakingInfo += `- [${release.name}](${release.url})\n`; } @@ -306,7 +304,7 @@ async function main() { const contribBreaking = await findContribBreakingChanges(currentVersions.contrib, latestVersions); if (contribBreaking.length > 0) { - breakingInfo += '**opentelemetry-js-contrib:**\n'; + breakingInfo += '\n**opentelemetry-js-contrib:**\n'; for (const release of contribBreaking) { breakingInfo += `- [${release.name}](${release.url})\n`; } From 895b495787dbb8a4ed82f8299fe34a83d79faff8 Mon Sep 17 00:00:00 2001 From: Eric Zhang Date: Fri, 26 Sep 2025 12:35:01 -0700 Subject: [PATCH 18/20] publish metric for nightly build outcome --- .github/workflows/main-build.yml | 2 +- .github/workflows/nightly-build.yml | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main-build.yml b/.github/workflows/main-build.yml index c1185ff0..b39addd2 100644 --- a/.github/workflows/main-build.yml +++ b/.github/workflows/main-build.yml @@ -118,7 +118,7 @@ jobs: name: "Publish Main Build Status" needs: [ build, application-signals-e2e-test ] runs-on: ubuntu-latest - if: always() + if: always() && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/')) steps: - name: Configure AWS Credentials for emitting metrics uses: aws-actions/configure-aws-credentials@a03048d87541d1d9fcf2ecf528a4a65ba9bd7838 #v5.0.0 diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml index 947a58ff..a8532695 100644 --- a/.github/workflows/nightly-build.yml +++ b/.github/workflows/nightly-build.yml @@ -9,6 +9,7 @@ on: - zhaez/nightly-build env: + AWS_DEFAULT_REGION: us-east-1 BRANCH_NAME: nightly-dependency-updates permissions: @@ -106,3 +107,27 @@ jobs: contents: read with: ref: nightly-dependency-updates + + publish-nightly-build-status: + name: "Publish Nightly Build Status" + needs: [update-and-create-pr, build-and-test] + runs-on: ubuntu-latest + if: always() + steps: + - name: Configure AWS Credentials for emitting metrics + uses: aws-actions/configure-aws-credentials@a03048d87541d1d9fcf2ecf528a4a65ba9bd7838 #v5.0.0 + with: + role-to-assume: ${{ secrets.MONITORING_ROLE_ARN }} + aws-region: ${{ env.AWS_DEFAULT_REGION}} + + - name: Publish nightly build status + run: | + if [[ "${{ needs.build-and-test.result }}" == "skipped" ]]; then + echo "Build was skipped (no changes), not publishing metric" + else + value="${{ needs.build-and-test.result == 'success' && '0.0' || '1.0'}}" + aws cloudwatch put-metric-data --namespace 'ADOT/GitHubActions' \ + --metric-name Failure \ + --dimensions repository=${{ github.repository }},branch=${{ github.ref_name }},workflow=nightly_build \ + --value $value + fi From b6df6cb7edac4b14c74ab22ae87d1b1f9d6dd6cc Mon Sep 17 00:00:00 2001 From: Eric Zhang Date: Fri, 26 Sep 2025 12:42:35 -0700 Subject: [PATCH 19/20] remove untracked script --- scripts/generate-licenses.js | 85 ------------------------------------ 1 file changed, 85 deletions(-) delete mode 100755 scripts/generate-licenses.js diff --git a/scripts/generate-licenses.js b/scripts/generate-licenses.js deleted file mode 100755 index 26063dae..00000000 --- a/scripts/generate-licenses.js +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/env node - -const checker = require('license-checker-rseidelsohn'); -const fs = require('fs'); -const path = require('path'); - -const workspaceDir = path.join(__dirname, '..', 'aws-distro-opentelemetry-node-autoinstrumentation'); - -checker.init({ - start: workspaceDir, - production: true, - excludePrivatePackages: true, - // Include packages from both local and hoisted node_modules - includePackages: '', - customFormat: { - name: '', - version: '', - repository: '', - licenseText: '' - } -}, (err, packages) => { - if (err) { - console.error('Error:', err); - process.exit(1); - } - - let output = ''; - const processedPackages = []; - - Object.keys(packages).forEach(packageKey => { - const pkg = packages[packageKey]; - - // Parse package name and version correctly for scoped packages - let name, version; - if (packageKey.startsWith('@')) { - const lastAtIndex = packageKey.lastIndexOf('@'); - name = packageKey.substring(0, lastAtIndex); - version = packageKey.substring(lastAtIndex + 1); - } else { - const atIndex = packageKey.indexOf('@'); - name = packageKey.substring(0, atIndex); - version = packageKey.substring(atIndex + 1); - } - - // Skip our own package - if (name === '@aws/aws-distro-opentelemetry-node-autoinstrumentation') { - return; - } - - processedPackages.push({ name, version, pkg }); - }); - - // Sort by package name for consistent output - processedPackages.sort((a, b) => a.name.localeCompare(b.name)); - - processedPackages.forEach(({ name, version, pkg }) => { - output += `** ${name}; version ${version}`; - if (pkg.repository) { - output += ` -- ${pkg.repository}`; - } - output += '\n'; - - if (pkg.licenseText) { - output += pkg.licenseText + '\n\n'; - } else { - output += 'License text not available\n\n'; - } - }); - - const outputPath = path.join(__dirname, '..', 'THIRD-PARTY-LICENSES'); - fs.writeFileSync(outputPath, output); - console.log(`Generated THIRD-PARTY-LICENSES with ${processedPackages.length} packages`); - - // Also show what direct dependencies might be missing - const workspacePackageJson = JSON.parse( - fs.readFileSync(path.join(workspaceDir, 'package.json')) - ); - const directDeps = Object.keys(workspacePackageJson.dependencies || {}); - const foundPackages = new Set(processedPackages.map(p => p.name)); - const missingDeps = directDeps.filter(dep => !foundPackages.has(dep)); - - if (missingDeps.length > 0) { - console.log(`\nDirect dependencies without license files found: ${missingDeps.join(', ')}`); - } -}); From b3ee1b120e23f9a150021a973d1654e7993e3bf0 Mon Sep 17 00:00:00 2001 From: Eric Zhang Date: Fri, 26 Sep 2025 12:53:06 -0700 Subject: [PATCH 20/20] update script comments --- scripts/find_breaking_changes.js | 1 - scripts/get_upstream_versions.js | 10 ++++------ scripts/update_dependencies.js | 2 +- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/scripts/find_breaking_changes.js b/scripts/find_breaking_changes.js index 3f07f1c6..6cd39f56 100644 --- a/scripts/find_breaking_changes.js +++ b/scripts/find_breaking_changes.js @@ -81,7 +81,6 @@ function getCurrentVersionsFromPackageJson() { for (const [packageName, version] of Object.entries(dependencies)) { if (packageName.startsWith('@opentelemetry/') && !['@opentelemetry/api', '@opentelemetry/sdk-trace-base', '@opentelemetry/sdk-node', '@opentelemetry/semantic-conventions'].includes(packageName)) { - // Check if it's likely a contrib package (not in core/experimental categories) const componentName = packageName.replace('@opentelemetry/', ''); contribPackages[componentName] = version; } diff --git a/scripts/get_upstream_versions.js b/scripts/get_upstream_versions.js index ec7f8180..8435255b 100644 --- a/scripts/get_upstream_versions.js +++ b/scripts/get_upstream_versions.js @@ -49,8 +49,6 @@ async function getLatestVersionsFromGitHub() { // Get versions from opentelemetry-js releases const jsReleases = await httpsGet('https://api.github.com/repos/open-telemetry/opentelemetry-js/releases?per_page=100'); const contribReleases = await httpsGet('https://api.github.com/repos/open-telemetry/opentelemetry-js-contrib/releases?per_page=100&page=1'); - - // Get additional contrib releases (they release more frequently) const contribReleases2 = await httpsGet('https://api.github.com/repos/open-telemetry/opentelemetry-js-contrib/releases?per_page=100&page=2'); const contribReleases3 = await httpsGet('https://api.github.com/repos/open-telemetry/opentelemetry-js-contrib/releases?per_page=100&page=3'); @@ -71,19 +69,19 @@ async function getLatestVersionsFromGitHub() { for (const release of jsReleases) { const tagName = release.tag_name; - // Core packages: v2.0.0 -> 2.0.0 (only keep first/newest) + // Core packages: v2.0.0 -> 2.0.0 if (/^v\d+\.\d+\.\d+$/.test(tagName) && !versions.core) { versions.core = tagName.substring(1); } - // Experimental packages: experimental/v0.57.1 -> 0.57.1 (only keep first/newest) + // Experimental packages: experimental/v0.57.1 -> 0.57.1 else if (tagName.startsWith('experimental/v') && !versions.experimental) { versions.experimental = tagName.substring('experimental/v'.length); } - // API package: api/v1.9.0 -> 1.9.0 (only keep first/newest) + // API package: api/v1.9.0 -> 1.9.0 else if (tagName.startsWith('api/v') && !versions.api) { versions.api = tagName.substring('api/v'.length); } - // Semantic conventions: semconv/v1.28.0 -> 1.28.0 (only keep first/newest) + // Semantic conventions: semconv/v1.28.0 -> 1.28.0 else if (tagName.startsWith('semconv/v') && !versions.semconv) { versions.semconv = tagName.substring('semconv/v'.length); } diff --git a/scripts/update_dependencies.js b/scripts/update_dependencies.js index 2d2c4124..88b8ef7c 100644 --- a/scripts/update_dependencies.js +++ b/scripts/update_dependencies.js @@ -122,7 +122,7 @@ async function main() { } else if (PACKAGE_CATEGORIES.semconv.includes(packageName) && githubVersions.semconv) { newVersion = githubVersions.semconv; } else if (PACKAGE_CATEGORIES.contrib.includes(packageName)) { - // Try to get version from contrib releases by stripping @opentelemetry/ prefix + // Independently versioned; get package name by stripping @opentelemetry/ prefix const componentName = packageName.replace('@opentelemetry/', ''); if (githubVersions[componentName]) { newVersion = githubVersions[componentName];