Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/local-actions/update-models/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
load("@devinfra_npm//:defs.bzl", "npm_link_all_packages")
load("//tools:defaults.bzl", "esbuild_checked_in", "ts_project")

package(default_visibility = ["//.github/local-actions/update-models:__subpackages__"])

npm_link_all_packages()

ts_project(
name = "lib",
srcs = glob(["lib/*.ts"]),
tsconfig = "//.github/local-actions:tsconfig",
deps = [
"//.github/local-actions/update-models:node_modules/@actions/core",
"//.github/local-actions/update-models:node_modules/@types/node",
"//.github/local-actions/update-models:node_modules/fast-glob",
],
)

esbuild_checked_in(
name = "main",
srcs = [
":lib",
],
entry_point = "lib/main.ts",
format = "esm",
platform = "node",
target = "node24",
)
5 changes: 5 additions & 0 deletions .github/local-actions/update-models/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
name: 'Update Gemini Models'
description: 'Check Gemini deprecations page and auto-update references to recommended replacements'
runs:
using: 'node24'
main: 'main.js'
121 changes: 121 additions & 0 deletions .github/local-actions/update-models/lib/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/**
* @license
* Copyright Google LLC
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import * as core from '@actions/core';
import {readFile, writeFile} from 'node:fs/promises';
import glob from 'fast-glob';

/** Parse the deprecations page to extract model replacements. */
function parseDeprecations(html: string): Map<string, string> {
const replacements = new Map<string, string>();
const tbodyRegex = /<tbody[^>]*>([\s\S]*?)<\/tbody>/gi;
let tbodyMatch;
while ((tbodyMatch = tbodyRegex.exec(html)) !== null) {
const tbodyContent = tbodyMatch[1];
const trRegex = /<tr[^>]*>([\s\S]*?)<\/tr>/gi;
let trMatch;
while ((trMatch = trRegex.exec(tbodyContent)) !== null) {
const trContent = trMatch[1];
if (!trContent) continue;
if (trContent.includes('colspan')) continue;

const tdRegex = /<td[^>]*>([\s\S]*?)<\/td>/gi;
const tds: string[] = [];
let tdMatch;
while ((tdMatch = tdRegex.exec(trContent)) !== null) {
tds.push(tdMatch[1].trim());
}

if (tds.length >= 4) {
const modelMatch = tds[0].match(/<code[^>]*>([^<]+)<\/code>/);
const model = modelMatch ? modelMatch[1].trim() : tds[0];

const replacementMatch = tds[3].match(/<code[^>]*>([^<]+)<\/code>/);
const replacement = replacementMatch
? replacementMatch[1].trim()
: tds[3].replace(/<[^>]*>/g, '').trim();

if (model && replacement && replacement.includes('gemini')) {
replacements.set(model, replacement);
}
Comment thread
josephperrott marked this conversation as resolved.
}
}
}
return replacements;
}

async function run() {
core.info('Fetching Gemini deprecations page...');
let html = '';
try {
const response = await fetch('https://ai.google.dev/gemini-api/docs/deprecations');
if (!response.ok) {
throw new Error(`Failed to fetch: ${response.statusText}`);
}
html = await response.text();
} catch (error) {
core.setFailed(`Error fetching deprecations page: ${error}`);
return;
}

const replacements = parseDeprecations(html);
if (replacements.size === 0) {
core.warning('No model replacements found on the deprecations page.');
return;
}

core.info(`Found ${replacements.size} model replacement(s) on the deprecations page.`);
for (const [oldModel, newModel] of replacements.entries()) {
core.info(` - ${oldModel} -> ${newModel}`);
}

// Find all .ts, .yml, .yaml files in the repository, ignoring node_modules/dist/.git
const files = await glob(['**/*.{ts,yml,yaml}'], {
ignore: ['**/node_modules/**', '**/dist/**', '**/.git/**'],
dot: true,
});
Comment thread
josephperrott marked this conversation as resolved.

let totalUpdated = 0;

for (const file of files) {
try {
const content = await readFile(file, 'utf-8');
let updatedContent = content;
let fileChanged = false;

for (const [oldModel, newModel] of replacements.entries()) {
const escapedOldModel = oldModel.replaceAll(/[.*+?^${}()|[\]\\]/g, '\\$&');
const regex = new RegExp(`(?<![a-zA-Z0-9.-])${escapedOldModel}(?![a-zA-Z0-9.-])`, 'g');
const newContent = updatedContent.replace(regex, newModel);
if (newContent !== updatedContent) {
updatedContent = newContent;
fileChanged = true;
core.info(`Found reference to deprecated model "${oldModel}" in ${file}.`);
}
}

if (fileChanged) {
await writeFile(file, updatedContent, 'utf-8');
Comment thread
josephperrott marked this conversation as resolved.
core.info(`Updated ${file}`);
totalUpdated++;
}
} catch (err) {
core.error(`Failed to read/write file ${file}: ${err}`);
}
}

if (totalUpdated === 0) {
core.info('No deprecated model references found in the codebase. All up to date! 🎉');
} else {
core.info(`Successfully updated ${totalUpdated} file(s).`);
}
}

run().catch((err) => {
core.setFailed(`Unhandled execution error: ${err}`);
});
Loading
Loading