import * as z from "zod" import { defineAction } from "../../../automation/actions" import { getGoogleSheetsApi, normalizeSpreadsheetId, resolveGridRange, resolveSheet, } from "../lib/google-sheets" import { GOOGLE_SHEETS_WRITE_REQUIREMENT } from "../lib/scopes" import { GRID_RANGE_SCHEMA, SHEET_REFERENCE_SCHEMA, SPREADSHEET_REFERENCE_SCHEMA, } from "../lib/schemas" /** Finds and replaces text over a range, sheet, or entire spreadsheet. */ export const findAndReplaceGoogleSheetsText = defineAction( "Find and replace Google Sheets text", ) .describe("Replaces text or Java-compatible regular-expression matches.") .account("google", GOOGLE_SHEETS_WRITE_REQUIREMENT) .input( z .object({ /** Search text or Java-compatible regular expression. */ find: z.string().min(1), /** Whether formula cells are searched. */ includeFormulas: z.boolean().optional(), /** Whether matching is case-sensitive. */ matchCase: z.boolean().optional(), /** Whether the search must match the entire cell. */ matchEntireCell: z.boolean().optional(), /** Optional structured range limiting the operation. */ range: GRID_RANGE_SCHEMA.optional(), /** Replacement text, including supported regex capture references. */ replacement: z.string(), /** Whether `find` is interpreted as a Java regular expression. */ searchByRegex: z.boolean().optional(), /** Optional sheet limiting the operation. */ sheet: SHEET_REFERENCE_SCHEMA.optional(), /** Spreadsheet ID or standard Google Sheets URL. */ spreadsheet: SPREADSHEET_REFERENCE_SCHEMA, }) .refine( ({ range, sheet }) => range === undefined || sheet === undefined, "Find and replace can target either a range or a sheet, not both.", ), ) .output( z.object({ /** Number of formula cells changed. */ formulasChanged: z.number().int().nonnegative(), /** Total matching occurrences replaced. */ occurrencesChanged: z.number().int().nonnegative(), /** Number of rows changed. */ rowsChanged: z.number().int().nonnegative(), /** Number of sheets changed. */ sheetsChanged: z.number().int().nonnegative(), /** Number of non-formula cells changed. */ valuesChanged: z.number().int().nonnegative(), }), ) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { const sheetsApi = getGoogleSheetsApi(account.secret) const spreadsheetId = normalizeSpreadsheetId(input.spreadsheet) const range = input.range ? await resolveGridRange(sheetsApi, spreadsheetId, input.range) : undefined const sheetId = input.sheet === undefined ? undefined : (await resolveSheet(sheetsApi, spreadsheetId, input.sheet)) .properties!.sheetId! const { data } = await sheetsApi.spreadsheets.batchUpdate({ requestBody: { requests: [ { findReplace: { allSheets: range === undefined && sheetId === undefined ? true : undefined, find: input.find, includeFormulas: input.includeFormulas ?? false, matchCase: input.matchCase ?? false, matchEntireCell: input.matchEntireCell ?? false, range, replacement: input.replacement, searchByRegex: input.searchByRegex ?? false, sheetId, }, }, ], }, spreadsheetId, }) const result = data.replies?.[0]?.findReplace return { formulasChanged: result?.formulasChanged ?? 0, occurrencesChanged: result?.occurrencesChanged ?? 0, rowsChanged: result?.rowsChanged ?? 0, sheetsChanged: result?.sheetsChanged ?? 0, valuesChanged: result?.valuesChanged ?? 0, } })