import { createHash } from "node:crypto"; interface ServicePreset { readonly id: string; readonly name: string; readonly summary: string; readonly url?: string; readonly scopes?: readonly string[];} const gd = (service: string, version: string): string => `https://www.googleapis.com/discovery/v1/apis/${service}/${version}/rest`; const googleOpenApiPresets: readonly ServicePreset[] = [ { id: "google-calendar", name: "Google Calendar", summary: "Calendars, events, ACLs, and scheduling.", url: gd("calendar", "v3"), }, { id: "google-gmail", name: "Gmail", summary: "Messages, threads, labels, and drafts.", url: gd("gmail", "v1"), }, { id: "google-sheets", name: "Google Sheets", summary: "Spreadsheets, values, ranges, and formatting.", url: gd("sheets", "v4"), }, { id: "google-drive", name: "Google Drive", summary: "Files, folders, permissions, and shared drives.", url: gd("drive", "v3"), }, { id: "google-docs", name: "Google Docs", summary: "Documents, structural edits, and formatting.", url: gd("docs", "v1"), }, { id: "google-slides", name: "Google Slides", summary: "Presentations, slides, page elements, and deck updates.", url: gd("slides", "v1"), }, { id: "google-forms", name: "Google Forms", summary: "Forms, questions, responses, and quizzes.", url: "https://forms.googleapis.com/$discovery/rest?version=v1", }, { id: "google-tasks", name: "Google Tasks", summary: "Task lists, task items, notes, and due dates.", url: gd("tasks", "v1"), }, { id: "google-people", name: "Google People", summary: "Contacts, profiles, directory people, and contact groups.", url: gd("people", "v1"), }, { id: "google-photos-library", name: "Google Photos Library", summary: "Albums, uploads, and app-created media through Google Photos.", url: gd("photoslibrary", "v1"), }, { id: "google-photos-picker", name: "Google Photos Picker", summary: "Picker sessions and user-selected Google Photos media items.", url: "https://photospicker.googleapis.com/$discovery/rest?version=v1", }, { id: "google-chat", name: "Google Chat", summary: "Spaces, messages, members, reactions, and chat workflows.", url: gd("chat", "v1"), }, { id: "google-keep", name: "Google Keep", summary: "Notes, lists, attachments, and annotations.", url: "https://keep.googleapis.com/$discovery/rest?version=v1", }, { id: "google-youtube-data", name: "YouTube Data", summary: "Channels, playlists, videos, comments, and uploads.", url: gd("youtube", "v3"), }, { id: "google-search-console", name: "Google Search Console", summary: "Sites, sitemaps, URL inspection, and search performance.", url: gd("searchconsole", "v1"), }, { id: "google-classroom", name: "Google Classroom", summary: "Courses, rosters, coursework, and grading.", url: gd("classroom", "v1"), }, { id: "google-admin-directory", name: "Google Admin Directory", summary: "Users, groups, org units, roles, and domain resources.", url: "https://www.googleapis.com/discovery/v1/apis/admin/directory_v1/rest", }, { id: "google-admin-reports", name: "Google Admin Reports", summary: "Audit events, usage reports, and admin activity logs.", url: "https://www.googleapis.com/discovery/v1/apis/admin/reports_v1/rest", }, { id: "google-apps-script", name: "Google Apps Script", summary: "Projects, deployments, and script execution.", url: gd("script", "v1"), }, { id: "google-bigquery", name: "Google BigQuery", summary: "Datasets, tables, jobs, and analytical queries.", url: gd("bigquery", "v2"), }, { id: "google-cloud-resource-manager", name: "Google Cloud Resource Manager", summary: "Projects, folders, organizations, and IAM hierarchy.", url: "https://cloudresourcemanager.googleapis.com/$discovery/rest?version=v3", },]; const microsoftGraphScopePresets: readonly ServicePreset[] = [ { id: "profile", name: "Profile", summary: "Signed-in user profile and photo.", scopes: ["User.Read"], }, { id: "me-surface", name: "My Graph Operations", summary: "All operation groups rooted under /me.", scopes: ["User.Read"], }, { id: "mail", name: "Outlook Mail", summary: "Messages, folders, attachments, settings, and send mail.", scopes: ["Mail.ReadWrite", "Mail.Send", "MailboxSettings.ReadWrite"], }, { id: "calendar", name: "Outlook Calendar", summary: "Calendars, events, and scheduling.", scopes: ["Calendars.ReadWrite"], }, { id: "contacts", name: "Outlook Contacts", summary: "Contacts, contact folders, and people suggestions.", scopes: ["Contacts.ReadWrite", "People.Read.All"], }, { id: "tasks", name: "To Do Tasks", summary: "Task lists, tasks, and checklist items.", scopes: ["Tasks.ReadWrite"], }, { id: "planner", name: "Planner", summary: "Plans, buckets, tasks, assignments, and Planner user data.", scopes: ["Tasks.ReadWrite"], }, { id: "files", name: "OneDrive Files", summary: "Drives, files, folders, sharing links, and permissions.", scopes: ["Files.ReadWrite.All", "Sites.ReadWrite.All"], }, { id: "excel", name: "Excel Workbooks", summary: "Workbook tables, worksheets, ranges, charts, and sessions.", scopes: ["Files.ReadWrite.All"], }, { id: "sites", name: "SharePoint Sites", summary: "Sites, lists, pages, columns, content types, and stores.", scopes: ["Sites.ReadWrite.All"], }, { id: "onenote", name: "OneNote", summary: "Notebooks, sections, pages, and page content.", scopes: ["Notes.ReadWrite"], }, { id: "teams-chat", name: "Teams Chats", summary: "Chats, chat messages, installed apps, and members.", scopes: ["Chat.ReadWrite"], }, { id: "teams-channels", name: "Teams Channels", summary: "Teams, channels, channel messages, replies, and joined teams.", scopes: [ "Team.ReadBasic.All", "Channel.ReadBasic.All", "ChannelMessage.Read.All", "ChannelMessage.Send", ], }, { id: "meetings-calls", name: "Meetings and Calls", summary: "Online meetings, calls, call records, and presence.", scopes: ["OnlineMeetings.ReadWrite", "Calls.AccessMedia.All"], },]; const MICROSOFT_GRAPH_DEFAULT_PRESET_IDS: readonly string[] = [ "profile", "mail", "calendar", "contacts", "tasks", "files", "excel", "sites", "onenote", "teams-chat", "teams-channels", "meetings-calls",]; const googleServiceSlug = (presetId: string): string => presetId.replaceAll("-", "_");const microsoftServiceSlug = (presetId: string): string => `microsoft_${presetId.replaceAll("-", "_")}`;const microsoftGraphScopesForPresetIds = ( presetIds: Iterable<string>, customScopes: Iterable<string> = [],): readonly string[] => unique([ ...[...presetIds].flatMap((presetId) => microsoftPresetById.get(presetId)?.scopes ?? []), ...customScopes, ]); const normalizeUrl = (url: string): string => { const trimmed = url.trim(); if (!URL.canParse(trimmed)) return trimmed.replace(/\/$/, ""); const parsed = new URL(trimmed); parsed.hash = ""; parsed.searchParams.sort(); return parsed.toString().replace(/\/$/, "");}; const googlePresetByNormalizedUrl = new Map( googleOpenApiPresets.flatMap((preset) => preset.url ? [[normalizeUrl(preset.url), preset] as const] : [], ),); const googlePresetForDiscoveryUrl = (url: string): ServicePreset | undefined => googlePresetByNormalizedUrl.get(normalizeUrl(url)); const matchPattern = (pattern: string, toolId: string): boolean => { if (pattern === "*") return true; const patternSegments = pattern.split("."); const toolSegments = toolId.split("."); for (let index = 0; index < patternSegments.length; index += 1) { const segment = patternSegments[index]!; if (segment === "*") { if (index === patternSegments.length - 1) return toolSegments.length >= index; if (index >= toolSegments.length) return false; continue; } if (index >= toolSegments.length || toolSegments[index] !== segment) return false; } return patternSegments.length === toolSegments.length;}; export type PluginId = "google" | "microsoft"; export interface IntegrationRow { readonly tenant: string; readonly slug: string; readonly plugin_id: string; readonly name: string | null; readonly description: string | null; readonly config: unknown; readonly health_check?: unknown; readonly config_revised_at?: string | number | bigint | null; readonly can_remove: boolean; readonly can_refresh: boolean; readonly created_at: string; readonly updated_at: string; readonly row_id: string;} export interface ConnectionRow { readonly tenant: string; readonly owner: string; readonly subject: string; readonly integration: string; readonly name: string; readonly template: string; readonly provider: string; readonly item_ids: unknown; readonly identity_label: string | null; readonly description?: string | null; readonly last_health?: unknown; readonly tools_synced_at?: string | number | bigint | null; readonly oauth_client: string | null; readonly oauth_client_owner: string | null; readonly refresh_item_id: string | null; readonly expires_at: string | number | bigint | null; readonly oauth_scope: string | null; readonly oauth_token_url?: string | null; readonly provider_state: unknown; readonly created_at: string; readonly updated_at: string; readonly row_id: string;} export interface ToolRow { readonly tenant: string; readonly owner: string; readonly subject: string; readonly integration: string; readonly connection: string; readonly plugin_id: string; readonly name: string; readonly description?: string; readonly input_schema?: unknown; readonly output_schema?: unknown; readonly annotations?: unknown; readonly created_at?: string; readonly updated_at?: string; readonly row_id: string;} export interface PluginStorageRow { readonly tenant: string; readonly owner: string; readonly subject: string; readonly plugin_id: string; readonly collection: string; readonly key: string; readonly data: unknown; readonly created_at: string; readonly updated_at: string; readonly row_id: string;} export interface BlobRow { readonly id: string; readonly namespace: string; readonly key: string;} export interface ToolPolicyRow { readonly tenant: string; readonly owner: string; readonly subject: string; readonly id: string; readonly pattern: string; readonly action: string; readonly position: string; readonly created_at: string; readonly updated_at: string; readonly row_id: string;} export interface MigrationInput { readonly integrations: readonly IntegrationRow[]; readonly connections: readonly ConnectionRow[]; readonly tools: readonly ToolRow[]; readonly pluginStorage?: readonly PluginStorageRow[]; readonly blobs?: readonly BlobRow[]; readonly policies: readonly ToolPolicyRow[]; readonly completedTenants?: readonly string[]; readonly trafficLastTenant?: string; readonly collectPolicyErrors?: boolean; readonly blobBackend?: "database" | "external";} export interface ServiceTarget { readonly pluginId: PluginId; readonly presetId: string; readonly slug: string; readonly name: string; readonly description: string;} export interface PlannedIntegration { readonly source: Pick<IntegrationRow, "tenant" | "slug" | "plugin_id" | "name">; readonly target: ServiceTarget; readonly action: "create" | "skip_existing"; readonly config: unknown; readonly servingState: { readonly specHash: string; readonly specSource: string; readonly blobBackend: "database" | "external"; readonly specBlobPresent: boolean; readonly defsBlobPresent: boolean; readonly operationsToBuild: number; readonly operationToolNames: readonly string[]; };} export interface PlannedConnection { readonly source: Pick<ConnectionRow, "tenant" | "owner" | "subject" | "integration" | "name">; readonly targetIntegration: string; readonly action: "clone" | "skip_existing"; readonly tokenReuse: "copy_item_ids_and_oauth_columns";} export interface PlannedPolicyRewrite { readonly policy: Pick< ToolPolicyRow, "tenant" | "owner" | "subject" | "id" | "pattern" | "action" | "position" >; readonly action: "rewrite"; readonly afterPatterns: readonly string[]; readonly matchedServices: readonly string[];} export interface OrgPlan { readonly tenant: string; readonly tenantHash: string; readonly completed: boolean; readonly integrations: readonly PlannedIntegration[]; readonly connections: readonly PlannedConnection[]; readonly policies: readonly PlannedPolicyRewrite[]; readonly deleteMonoliths: readonly Pick< IntegrationRow, "tenant" | "slug" | "plugin_id" | "name" >[]; readonly clonedToolRows: number; readonly operationsToBuild: number; readonly hardErrors: readonly string[];} export interface MigrationPlan { readonly orgs: readonly OrgPlan[]; readonly summary: { readonly orgs: number; readonly completedOrgs: number; readonly integrationsCreate: number; readonly integrationsSkipExisting: number; readonly connectionsClone: number; readonly connectionsSkipExisting: number; readonly policiesRewrite: number; readonly policiesSkip: number; readonly policyRowsAfter: number; readonly monolithDeletes: number; readonly clonedToolRows: number; readonly operationsToBuild: number; readonly integrationsMissingSpecBlob: number; readonly integrationsMissingDefsBlob: number; readonly hardErrorOrgs: number; readonly policyHardErrors: number; };} const GOOGLE_IDENTITY_DISCOVERY_URL = "https://www.googleapis.com/discovery/v1/apis/oauth2/v2/rest"; const GOOGLE_TOOL_PREFIX_TO_PRESET_ID: ReadonlyMap<string, string> = new Map([ ["calendar", "google-calendar"], ["gmail", "google-gmail"], ["sheets", "google-sheets"], ["drive", "google-drive"], ["docs", "google-docs"], ["slides", "google-slides"], ["forms", "google-forms"], ["tasks", "google-tasks"], ["people", "google-people"], ["photoslibrary", "google-photos-library"], ["photospicker", "google-photos-picker"], ["chat", "google-chat"], ["keep", "google-keep"], ["youtube", "google-youtube-data"], ["searchconsole", "google-search-console"], ["webmasters", "google-search-console"], ["classroom", "google-classroom"], ["admin", "google-admin-directory"],Lines 1–500 of 1179. Load more
import { createHash } from "node:crypto"; interface ServicePreset { readonly id: string; readonly name: string; readonly summary: string; readonly url?: string; readonly scopes?: readonly string[]; } const gd = (service: string, version: string): string => `https://www.googleapis.com/discovery/v1/apis/${service}/${version}/rest`; const googleOpenApiPresets: readonly ServicePreset[] = [ { id: "google-calendar", name: "Google Calendar", summary: "Calendars, events, ACLs, and scheduling.", url: gd("calendar", "v3"), }, { id: "google-gmail", name: "Gmail", summary: "Messages, threads, labels, and drafts.", url: gd("gmail", "v1"), }, { id: "google-sheets", name: "Google Sheets", summary: "Spreadsheets, values, ranges, and formatting.", url: gd("sheets", "v4"), }, { id: "google-drive", name: "Google Drive", summary: "Files, folders, permissions, and shared drives.", url: gd("drive", "v3"), }, { id: "google-docs", name: "Google Docs", summary: "Documents, structural edits, and formatting.", url: gd("docs", "v1"), }, { id: "google-slides", name: "Google Slides", summary: "Presentations, slides, page elements, and deck updates.", url: gd("slides", "v1"), }, { id: "google-forms", name: "Google Forms", summary: "Forms, questions, responses, and quizzes.", url: "https://forms.googleapis.com/$discovery/rest?version=v1", }, { id: "google-tasks", name: "Google Tasks", summary: "Task lists, task items, notes, and due dates.", url: gd("tasks", "v1"), }, { id: "google-people", name: "Google People", summary: "Contacts, profiles, directory people, and contact groups.", url: gd("people", "v1"), }, { id: "google-photos-library", name: "Google Photos Library", summary: "Albums, uploads, and app-created media through Google Photos.", url: gd("photoslibrary", "v1"), }, { id: "google-photos-picker", name: "Google Photos Picker", summary: "Picker sessions and user-selected Google Photos media items.", url: "https://photospicker.googleapis.com/$discovery/rest?version=v1", }, { id: "google-chat", name: "Google Chat", summary: "Spaces, messages, members, reactions, and chat workflows.", url: gd("chat", "v1"), }, { id: "google-keep", name: "Google Keep", summary: "Notes, lists, attachments, and annotations.", url: "https://keep.googleapis.com/$discovery/rest?version=v1", }, { id: "google-youtube-data", name: "YouTube Data", summary: "Channels, playlists, videos, comments, and uploads.", url: gd("youtube", "v3"), }, { id: "google-search-console", name: "Google Search Console", summary: "Sites, sitemaps, URL inspection, and search performance.", url: gd("searchconsole", "v1"), }, { id: "google-classroom", name: "Google Classroom", summary: "Courses, rosters, coursework, and grading.", url: gd("classroom", "v1"), }, { id: "google-admin-directory", name: "Google Admin Directory", summary: "Users, groups, org units, roles, and domain resources.", url: "https://www.googleapis.com/discovery/v1/apis/admin/directory_v1/rest", }, { id: "google-admin-reports", name: "Google Admin Reports", summary: "Audit events, usage reports, and admin activity logs.", url: "https://www.googleapis.com/discovery/v1/apis/admin/reports_v1/rest", }, { id: "google-apps-script", name: "Google Apps Script", summary: "Projects, deployments, and script execution.", url: gd("script", "v1"), }, { id: "google-bigquery", name: "Google BigQuery", summary: "Datasets, tables, jobs, and analytical queries.", url: gd("bigquery", "v2"), }, { id: "google-cloud-resource-manager", name: "Google Cloud Resource Manager", summary: "Projects, folders, organizations, and IAM hierarchy.", url: "https://cloudresourcemanager.googleapis.com/$discovery/rest?version=v3", }, ]; const microsoftGraphScopePresets: readonly ServicePreset[] = [ { id: "profile", name: "Profile", summary: "Signed-in user profile and photo.", scopes: ["User.Read"], }, { id: "me-surface", name: "My Graph Operations", summary: "All operation groups rooted under /me.", scopes: ["User.Read"], }, { id: "mail", name: "Outlook Mail", summary: "Messages, folders, attachments, settings, and send mail.", scopes: ["Mail.ReadWrite", "Mail.Send", "MailboxSettings.ReadWrite"], }, { id: "calendar", name: "Outlook Calendar", summary: "Calendars, events, and scheduling.", scopes: ["Calendars.ReadWrite"], }, { id: "contacts", name: "Outlook Contacts", summary: "Contacts, contact folders, and people suggestions.", scopes: ["Contacts.ReadWrite", "People.Read.All"], }, { id: "tasks", name: "To Do Tasks", summary: "Task lists, tasks, and checklist items.", scopes: ["Tasks.ReadWrite"], }, { id: "planner", name: "Planner", summary: "Plans, buckets, tasks, assignments, and Planner user data.", scopes: ["Tasks.ReadWrite"], }, { id: "files", name: "OneDrive Files", summary: "Drives, files, folders, sharing links, and permissions.", scopes: ["Files.ReadWrite.All", "Sites.ReadWrite.All"], }, { id: "excel", name: "Excel Workbooks", summary: "Workbook tables, worksheets, ranges, charts, and sessions.", scopes: ["Files.ReadWrite.All"], }, { id: "sites", name: "SharePoint Sites", summary: "Sites, lists, pages, columns, content types, and stores.", scopes: ["Sites.ReadWrite.All"], }, { id: "onenote", name: "OneNote", summary: "Notebooks, sections, pages, and page content.", scopes: ["Notes.ReadWrite"], }, { id: "teams-chat", name: "Teams Chats", summary: "Chats, chat messages, installed apps, and members.", scopes: ["Chat.ReadWrite"], }, { id: "teams-channels", name: "Teams Channels", summary: "Teams, channels, channel messages, replies, and joined teams.", scopes: [ "Team.ReadBasic.All", "Channel.ReadBasic.All", "ChannelMessage.Read.All", "ChannelMessage.Send", ], }, { id: "meetings-calls", name: "Meetings and Calls", summary: "Online meetings, calls, call records, and presence.", scopes: ["OnlineMeetings.ReadWrite", "Calls.AccessMedia.All"], }, ]; const MICROSOFT_GRAPH_DEFAULT_PRESET_IDS: readonly string[] = [ "profile", "mail", "calendar", "contacts", "tasks", "files", "excel", "sites", "onenote", "teams-chat", "teams-channels", "meetings-calls", ]; const googleServiceSlug = (presetId: string): string => presetId.replaceAll("-", "_"); const microsoftServiceSlug = (presetId: string): string => `microsoft_${presetId.replaceAll("-", "_")}`; const microsoftGraphScopesForPresetIds = ( presetIds: Iterable<string>, customScopes: Iterable<string> = [], ): readonly string[] => unique([ ...[...presetIds].flatMap((presetId) => microsoftPresetById.get(presetId)?.scopes ?? []), ...customScopes, ]); const normalizeUrl = (url: string): string => { const trimmed = url.trim(); if (!URL.canParse(trimmed)) return trimmed.replace(/\/$/, ""); const parsed = new URL(trimmed); parsed.hash = ""; parsed.searchParams.sort(); return parsed.toString().replace(/\/$/, ""); }; const googlePresetByNormalizedUrl = new Map( googleOpenApiPresets.flatMap((preset) => preset.url ? [[normalizeUrl(preset.url), preset] as const] : [], ), ); const googlePresetForDiscoveryUrl = (url: string): ServicePreset | undefined => googlePresetByNormalizedUrl.get(normalizeUrl(url)); const matchPattern = (pattern: string, toolId: string): boolean => { if (pattern === "*") return true; const patternSegments = pattern.split("."); const toolSegments = toolId.split("."); for (let index = 0; index < patternSegments.length; index += 1) { const segment = patternSegments[index]!; if (segment === "*") { if (index === patternSegments.length - 1) return toolSegments.length >= index; if (index >= toolSegments.length) return false; continue; } if (index >= toolSegments.length || toolSegments[index] !== segment) return false; } return patternSegments.length === toolSegments.length; }; export type PluginId = "google" | "microsoft"; export interface IntegrationRow { readonly tenant: string; readonly slug: string; readonly plugin_id: string; readonly name: string | null; readonly description: string | null; readonly config: unknown; readonly health_check?: unknown; readonly config_revised_at?: string | number | bigint | null; readonly can_remove: boolean; readonly can_refresh: boolean; readonly created_at: string; readonly updated_at: string; readonly row_id: string; } export interface ConnectionRow { readonly tenant: string; readonly owner: string; readonly subject: string; readonly integration: string; readonly name: string; readonly template: string; readonly provider: string; readonly item_ids: unknown; readonly identity_label: string | null; readonly description?: string | null; readonly last_health?: unknown; readonly tools_synced_at?: string | number | bigint | null; readonly oauth_client: string | null; readonly oauth_client_owner: string | null; readonly refresh_item_id: string | null; readonly expires_at: string | number | bigint | null; readonly oauth_scope: string | null; readonly oauth_token_url?: string | null; readonly provider_state: unknown; readonly created_at: string; readonly updated_at: string; readonly row_id: string; } export interface ToolRow { readonly tenant: string; readonly owner: string; readonly subject: string; readonly integration: string; readonly connection: string; readonly plugin_id: string; readonly name: string; readonly description?: string; readonly input_schema?: unknown; readonly output_schema?: unknown; readonly annotations?: unknown; readonly created_at?: string; readonly updated_at?: string; readonly row_id: string; } export interface PluginStorageRow { readonly tenant: string; readonly owner: string; readonly subject: string; readonly plugin_id: string; readonly collection: string; readonly key: string; readonly data: unknown; readonly created_at: string; readonly updated_at: string; readonly row_id: string; } export interface BlobRow { readonly id: string; readonly namespace: string; readonly key: string; } export interface ToolPolicyRow { readonly tenant: string; readonly owner: string; readonly subject: string; readonly id: string; readonly pattern: string; readonly action: string; readonly position: string; readonly created_at: string; readonly updated_at: string; readonly row_id: string; } export interface MigrationInput { readonly integrations: readonly IntegrationRow[]; readonly connections: readonly ConnectionRow[]; readonly tools: readonly ToolRow[]; readonly pluginStorage?: readonly PluginStorageRow[]; readonly blobs?: readonly BlobRow[]; readonly policies: readonly ToolPolicyRow[]; readonly completedTenants?: readonly string[]; readonly trafficLastTenant?: string; readonly collectPolicyErrors?: boolean; readonly blobBackend?: "database" | "external"; } export interface ServiceTarget { readonly pluginId: PluginId; readonly presetId: string; readonly slug: string; readonly name: string; readonly description: string; } export interface PlannedIntegration { readonly source: Pick<IntegrationRow, "tenant" | "slug" | "plugin_id" | "name">; readonly target: ServiceTarget; readonly action: "create" | "skip_existing"; readonly config: unknown; readonly servingState: { readonly specHash: string; readonly specSource: string; readonly blobBackend: "database" | "external"; readonly specBlobPresent: boolean; readonly defsBlobPresent: boolean; readonly operationsToBuild: number; readonly operationToolNames: readonly string[]; }; } export interface PlannedConnection { readonly source: Pick<ConnectionRow, "tenant" | "owner" | "subject" | "integration" | "name">; readonly targetIntegration: string; readonly action: "clone" | "skip_existing"; readonly tokenReuse: "copy_item_ids_and_oauth_columns"; } export interface PlannedPolicyRewrite { readonly policy: Pick< ToolPolicyRow, "tenant" | "owner" | "subject" | "id" | "pattern" | "action" | "position" >; readonly action: "rewrite"; readonly afterPatterns: readonly string[]; readonly matchedServices: readonly string[]; } export interface OrgPlan { readonly tenant: string; readonly tenantHash: string; readonly completed: boolean; readonly integrations: readonly PlannedIntegration[]; readonly connections: readonly PlannedConnection[]; readonly policies: readonly PlannedPolicyRewrite[]; readonly deleteMonoliths: readonly Pick< IntegrationRow, "tenant" | "slug" | "plugin_id" | "name" >[]; readonly clonedToolRows: number; readonly operationsToBuild: number; readonly hardErrors: readonly string[]; } export interface MigrationPlan { readonly orgs: readonly OrgPlan[]; readonly summary: { readonly orgs: number; readonly completedOrgs: number; readonly integrationsCreate: number; readonly integrationsSkipExisting: number; readonly connectionsClone: number; readonly connectionsSkipExisting: number; readonly policiesRewrite: number; readonly policiesSkip: number; readonly policyRowsAfter: number; readonly monolithDeletes: number; readonly clonedToolRows: number; readonly operationsToBuild: number; readonly integrationsMissingSpecBlob: number; readonly integrationsMissingDefsBlob: number; readonly hardErrorOrgs: number; readonly policyHardErrors: number; }; } const GOOGLE_IDENTITY_DISCOVERY_URL = "https://www.googleapis.com/discovery/v1/apis/oauth2/v2/rest"; const GOOGLE_TOOL_PREFIX_TO_PRESET_ID: ReadonlyMap<string, string> = new Map([ ["calendar", "google-calendar"], ["gmail", "google-gmail"], ["sheets", "google-sheets"], ["drive", "google-drive"], ["docs", "google-docs"], ["slides", "google-slides"], ["forms", "google-forms"], ["tasks", "google-tasks"], ["people", "google-people"], ["photoslibrary", "google-photos-library"], ["photospicker", "google-photos-picker"], ["chat", "google-chat"], ["keep", "google-keep"], ["youtube", "google-youtube-data"], ["searchconsole", "google-search-console"], ["webmasters", "google-search-console"], ["classroom", "google-classroom"], ["admin", "google-admin-directory"],