{"version":3,"file":"index.mjs","names":[],"sources":["../src/lib/graphql/syncCookies.ts","../src/lib/requests/pullPrivacyRequests.ts","../src/lib/helpers/readSafe.ts","../src/lib/api-keys/listDirectories.ts","../src/lib/ai/removeLinks.ts","../src/lib/ai/filterNullishValuesFromObject.ts","../src/lib/ai/getGitFilesThatChanged.ts"],"sourcesContent":["// import { keyBy } from 'lodash-es';\nimport {\n  fetchConsentManagerId,\n  makeGraphQLRequest,\n  UPDATE_OR_CREATE_COOKIES,\n} from '@transcend-io/sdk';\nimport { mapSeries } from '@transcend-io/utils';\nimport colors from 'colors';\nimport { GraphQLClient } from 'graphql-request';\nimport { chunk } from 'lodash-es';\n\nimport { CookieInput } from '../../codecs.js';\nimport { logger } from '../../logger.js';\n\nconst MAX_PAGE_SIZE = 100;\n\n/**\n * Update or create cookies that already existed\n *\n * @param client - GraphQL client\n * @param cookieInputs - List of cookie input\n */\nexport async function updateOrCreateCookies(\n  client: GraphQLClient,\n  cookieInputs: CookieInput[],\n): Promise<void> {\n  const airgapBundleId = await fetchConsentManagerId(client, { logger });\n\n  // TODO: https://transcend.height.app/T-19841 - add with custom purposes\n  // const purposes = await fetchAllPurposes(client);\n  // const purposeNameToId = keyBy(purposes, 'name');\n\n  await mapSeries(chunk(cookieInputs, MAX_PAGE_SIZE), async (page) => {\n    await makeGraphQLRequest(client, UPDATE_OR_CREATE_COOKIES, {\n      variables: {\n        airgapBundleId,\n        cookies: page.map((cookie) => ({\n          name: cookie.name,\n          trackingPurposes:\n            cookie.trackingPurposes && cookie.trackingPurposes.length > 0\n              ? cookie.trackingPurposes\n              : undefined,\n          // TODO: https://transcend.height.app/T-19841 - add with custom purposes\n          // purposeIds: cookie.trackingPurposes\n          //   ? cookie.trackingPurposes\n          //       .filter((purpose) => purpose !== 'Unknown')\n          //       .map((purpose) => purposeNameToId[purpose].id)\n          // : undefined,\n          description: cookie.description,\n          service: cookie.service,\n          status: cookie.status,\n          attributes: cookie.attributes,\n          isRegex: cookie.isRegex,\n          // TODO: https://transcend.height.app/T-23718\n          // owners,\n          // teams,\n        })),\n      },\n      logger,\n    });\n  });\n}\n\n/**\n * Sync the set of cookies from the YML interface into the product\n *\n * @param client - GraphQL client\n * @param cookies - Cookies to sync\n * @returns True upon success, false upon failure\n */\nexport async function syncCookies(client: GraphQLClient, cookies: CookieInput[]): Promise<boolean> {\n  let encounteredError = false;\n  logger.info(colors.magenta(`Syncing \"${cookies.length}\" cookies...`));\n\n  // Ensure no duplicates are being uploaded\n  const notUnique = cookies.filter(\n    (cookie) =>\n      cookies.filter((cook) => cookie.name === cook.name && cookie.isRegex === cook.isRegex)\n        .length > 1,\n  );\n  if (notUnique.length > 0) {\n    throw new Error(\n      `Failed to upload cookies as there were non-unique entries found: ${notUnique\n        .map(({ name }) => name)\n        .join(',')}`,\n    );\n  }\n\n  try {\n    logger.info(colors.magenta(`Upserting \"${cookies.length}\" new cookies...`));\n    await updateOrCreateCookies(client, cookies);\n    logger.info(colors.green(`Successfully synced ${cookies.length} cookies!`));\n  } catch (err) {\n    encounteredError = true;\n    logger.error(colors.red(`Failed to create cookies! - ${err.message}`));\n  }\n\n  return !encounteredError;\n}\n","import { RequestAction, RequestStatus } from '@transcend-io/privacy-types';\nimport {\n  buildTranscendGraphQLClient,\n  createSombraGotInstance,\n  fetchAllRequestIdentifiers,\n  validateSombraVersion,\n  type RequestIdentifier,\n} from '@transcend-io/sdk';\nimport { map } from '@transcend-io/utils';\nimport colors from 'colors';\n\nimport { DEFAULT_TRANSCEND_API } from '../../constants.js';\nimport { logger } from '../../logger.js';\nimport { fetchAllRequests } from '../graphql/index.js';\nimport { formatRequestForCsv, CsvRow, ExportedPrivacyRequest } from './formatRequestForCsv.js';\nimport { splitDateRange } from './splitDateRange.js';\n\n/**\n * Pull down a list of privacy requests\n *\n * @param options - Options\n * @returns The requests with request identifiers and requests formatted for CSV\n */\nexport async function pullPrivacyRequests({\n  auth,\n  sombraAuth,\n  actions = [],\n  statuses = [],\n  identifierSearch,\n  pageLimit = 100,\n  concurrency = 1,\n  transcendUrl = DEFAULT_TRANSCEND_API,\n  createdAtBefore,\n  skipRequestIdentifiers = false,\n  createdAtAfter,\n  updatedAtBefore,\n  updatedAtAfter,\n  isTest,\n}: {\n  /** Transcend API key authentication */\n  auth: string;\n  /** Search for a specific identifier */\n  identifierSearch?: string;\n  /** Sombra API key authentication */\n  sombraAuth?: string;\n  /** API URL for Transcend backend */\n  transcendUrl?: string;\n  /** Statuses to filter on */\n  statuses?: RequestStatus[];\n  /** The request action to fetch */\n  actions?: RequestAction[];\n  /** Page limit when fetching requests */\n  pageLimit?: number;\n  /** Number of parallel date-range chunks */\n  concurrency?: number;\n  /** Filter for requests created before this date */\n  createdAtBefore?: Date;\n  /** Filter for requests created after this date */\n  createdAtAfter?: Date;\n  /** Filter for requests updated before this date */\n  updatedAtBefore?: Date;\n  /** Filter for requests updated after this date */\n  updatedAtAfter?: Date;\n  /** Return test requests */\n  isTest?: boolean;\n  /** Skip fetching request identifier */\n  skipRequestIdentifiers?: boolean;\n}): Promise<{\n  /** All request information with attached identifiers */\n  requestsWithRequestIdentifiers: ExportedPrivacyRequest[];\n  /** Requests that are formatted for CSV */\n  requestsFormattedForCsv: CsvRow[];\n}> {\n  const client = buildTranscendGraphQLClient(transcendUrl, auth);\n  const sombra = await createSombraGotInstance(transcendUrl, auth, {\n    logger,\n    sombraApiKey: sombraAuth,\n    sombraUrl: process.env.SOMBRA_URL,\n  });\n\n  // Log date range\n  let dateRange = '';\n  if (createdAtBefore) {\n    dateRange += ` before ${createdAtBefore.toISOString()}`;\n  }\n  if (createdAtAfter) {\n    dateRange += `${dateRange ? ', and' : ''} after ${createdAtAfter.toISOString()}`;\n  }\n  logger.info(\n    colors.magenta(\n      `${\n        actions.length > 0\n          ? `Pulling requests of type \"${actions.join('\" , \"')}\"`\n          : 'Pulling all requests'\n      }${dateRange}`,\n    ),\n  );\n\n  // Split into parallel date-range chunks when possible\n  const useChunks = concurrency > 1 && createdAtAfter && createdAtBefore;\n  const chunks = useChunks\n    ? splitDateRange(createdAtAfter, createdAtBefore, concurrency)\n    : [{ createdAtAfter, createdAtBefore }];\n\n  if (useChunks) {\n    logger.info(colors.magenta(`Splitting date range into ${concurrency} parallel chunks`));\n  }\n\n  // Fetch requests across all chunks in parallel\n  const chunkResults = await map(\n    chunks,\n    (chunk) =>\n      fetchAllRequests(client, {\n        actions,\n        text: identifierSearch,\n        statuses,\n        createdAtBefore: chunk.createdAtBefore,\n        createdAtAfter: chunk.createdAtAfter,\n        updatedAtBefore,\n        updatedAtAfter,\n        isTest,\n      }),\n    { concurrency: useChunks ? concurrency : 1 },\n  );\n  const requests = chunkResults.flat();\n\n  // Validate Sombra version once before bulk-fetching identifiers\n  if (!skipRequestIdentifiers) {\n    await validateSombraVersion(client, { logger });\n  }\n\n  // Fetch the request identifiers for those requests\n  const requestsWithRequestIdentifiers = skipRequestIdentifiers\n    ? requests.map((request) => ({\n        ...request,\n        requestIdentifiers: [] as RequestIdentifier[],\n      }))\n    : await map(\n        requests,\n        async (request) => {\n          const requestIdentifiers = await fetchAllRequestIdentifiers(client, sombra, {\n            filterBy: { requestId: request.id },\n            skipSombraCheck: true,\n            logger,\n          });\n          return {\n            ...request,\n            requestIdentifiers,\n          };\n        },\n        {\n          concurrency: pageLimit,\n        },\n      );\n\n  logger.info(colors.magenta(`Pulled ${requestsWithRequestIdentifiers.length} requests`));\n\n  const data = requestsWithRequestIdentifiers.map(formatRequestForCsv);\n\n  return { requestsWithRequestIdentifiers, requestsFormattedForCsv: data };\n}\n","import { readFileSync } from 'node:fs';\n\n/**\n * Safely reads the contents of a file as a UTF-8 string.\n * Returns an empty string if the path is not provided or if reading fails.\n *\n * @param p - The path to the file to read.\n * @returns The file contents as a string, or an empty string on error.\n */\nexport function readSafe(p?: string): string {\n  try {\n    return p ? readFileSync(p, 'utf8') : '';\n  } catch {\n    return '';\n  }\n}\n","import { readdirSync, statSync } from 'node:fs';\nimport { join } from 'node:path';\n\n/**\n * List the folders in a directory\n *\n * @param startDir - The base directory to list from\n * @returns The list of folders in that directory\n */\nexport function listDirectories(startDir: string): string[] {\n  return readdirSync(startDir).filter((entryName) =>\n    statSync(join(startDir, entryName)).isDirectory(),\n  );\n}\n","/**\n * Remove links from a string\n *\n * @param inputString - String\n * @returns String without links\n */\nexport function removeLinks(inputString: string): string {\n  const regex = /(https?:\\/\\/[^\\s]+)/g;\n  return inputString.replace(regex, '<link-omitted>');\n}\n","import { ObjByString } from '@transcend-io/type-utils';\n\n/**\n * Given an object, remove all keys that are null-ish\n *\n * @param obj - Object\n * @returns Object with null-ish values removed\n */\nexport function filterNullishValuesFromObject<T extends ObjByString>(obj: T): T {\n  return Object.entries(obj).reduce(\n    (acc, [k, v]) =>\n      v !== null &&\n      v !== undefined &&\n      v !== '' &&\n      !(Array.isArray(v) && v.length === 0) &&\n      !(typeof v === 'object' && Object.keys(v).length === 0)\n        ? Object.assign(acc, { [k]: v })\n        : acc,\n    {} as T,\n  );\n}\n","import { execSync } from 'child_process';\n\nimport fastGlob from 'fast-glob';\nimport { difference } from 'lodash-es';\n\n/**\n * Function thats gets the git files that have changed\n * and returns the code\n *\n * @param options - Options\n * @returns Changes files and diffs\n */\nexport function getGitFilesThatChanged({\n  baseBranch,\n  rootDirectory,\n  githubRepo,\n  excludedGlob = [],\n  fileBlockList = [],\n}: {\n  /** Base branch */\n  baseBranch: string;\n  /** Github repo name */\n  githubRepo: string;\n  /** Root directory */\n  rootDirectory: string;\n  /** A glob that excludes files */\n  excludedGlob?: string[];\n  /** Block list of files to not process */\n  fileBlockList?: string[];\n}): {\n  /** The list of files that changed */\n  changedFiles: string[];\n  /** Github repo name */\n  repoName: string;\n  /** Current commit */\n  commit: string;\n  /** File diffs */\n  fileDiffs: { [k in string]: string };\n} {\n  // Pull base branch\n  execSync(`git fetch origin ${baseBranch}`);\n\n  // Latest commit on base branch. If we are on the base branch, we take the prior commit\n  const latestBasedCommit = execSync(\n    `git ls-remote ${githubRepo} \"refs/heads/${baseBranch}\" | cut -f 1`,\n    { encoding: 'utf-8' },\n  ).split('\\n')[0];\n\n  // This commit\n  const latestThisCommit = execSync('git rev-parse HEAD', {\n    encoding: 'utf-8',\n  }).split('\\n')[0];\n\n  // Ensure commits are present\n  if (!latestBasedCommit || !latestThisCommit) {\n    throw new Error('FAILED TO FIND COMMIT RANGE');\n  }\n\n  // Get the diff between the given branch and base branch\n  const diff = execSync(\n    `git fetch && git diff --name-only \"${\n      baseBranch || latestBasedCommit\n    }...${latestThisCommit}\" -- ${rootDirectory}`,\n    { encoding: 'utf-8' },\n  );\n\n  // Filter out block list\n  const changedFiles = difference(\n    diff.split('\\n').filter((f) => f),\n    fileBlockList,\n  );\n\n  // Filter out globs\n  const filteredChanges =\n    excludedGlob.length > 0 ? fastGlob.sync(changedFiles, { ignore: excludedGlob }) : changedFiles;\n\n  // Get the contents of only the changed files\n  const fileDiffs: { [k in string]: string } = {};\n  filteredChanges.forEach((file) => {\n    const contents = execSync(`git show ${latestThisCommit}:${file}`, {\n      encoding: 'utf-8',\n    });\n    fileDiffs[file] = contents;\n  });\n\n  // Pull the github repo name\n  const repoName = githubRepo.split('/').pop()!.split('.')[0];\n\n  return {\n    changedFiles,\n    fileDiffs,\n    repoName,\n    commit: latestThisCommit,\n  };\n}\n"],"mappings":"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"}