run = async () => {
  try {
    debug("Retrieve action information from context...");
    debug(`Context: ${inspect(github.context)}`);
    let commentBody = `
      \r# ${COMMENT_TITLE}
      \r${THEME_CONTRIB_GUIDELINES}
    `;
    const ccc = new ColorContrastChecker();
    OCTOKIT = github.getOctokit(getGithubToken());
    PULL_REQUEST_ID = getPrNumber();
    const { owner, repo } = getRepoInfo(github.context);
    OWNER = owner;
    REPO = repo;
    const commenter = getCommenter();
    PULL_REQUEST_ID = getPrNumber();
    debug(`Owner: ${OWNER}`);
    debug(`Repo: ${REPO}`);
    debug(`Commenter: ${commenter}`);

    // Retrieve the PR diff and preview-theme comment.
    debug("Retrieve PR diff...");
    const res = await OCTOKIT.rest.pulls.get({
      owner: OWNER,
      repo: REPO,
      pull_number: PULL_REQUEST_ID,
      mediaType: {
        format: "diff",
      },
    });
    debug("Retrieve preview-theme comment...");
    const comment = await findComment(
      OCTOKIT,
      PULL_REQUEST_ID,
      OWNER,
      REPO,
      commenter,
    );

    // Retrieve theme changes from the PR diff.
    debug("Retrieve themes...");
    const diff = parse(res.data);

    // Retrieve all theme changes from the PR diff and convert to JSON.
    debug("Retrieve theme changes...");
    const content = diff
      .find((file) => file.to === "themes/index.js")
      .chunks.map((chunk) =>
        chunk.changes
          .filter((c) => c.type === "add")
          .map((c) => c.content.replace("+", ""))
          .join(""),
      )
      .join("");
    const themeObject = parseJSON(content);
    if (
      Object.keys(themeObject).every(
        (key) => typeof themeObject[key] !== "object",
      )
    ) {
      throw new Error("PR diff is not a valid theme JSON object.");
    }

    // Loop through themes and create theme preview body.
    debug("Create theme preview body...");
    const themeValid = Object.fromEntries(
      Object.keys(themeObject).map((name) => [name, true]),
    );
    let previewBody = "";
    for (const theme in themeObject) {
      debug(`Create theme preview for ${theme}...`);
      const themeName = theme;
      const colors = themeObject[theme];
      const warnings = [];
      const errors = [];

      // Check if the theme name is valid.
      debug("Theme preview body: Check if the theme name is valid...");
      if (themeNameAlreadyExists(themeName)) {
        warnings.push("Theme name already taken");
        themeValid[theme] = false;
      }
      if (themeName !== snakeCase(themeName)) {
        warnings.push("Theme name isn't in snake_case");
        themeValid[theme] = false;
      }

      // Check if the theme colors are valid.
      debug("Theme preview body: Check if the theme colors are valid...");
      let invalidColors = false;
      if (colors) {
        const missingKeys = REQUIRED_COLOR_PROPS.filter(
          (x) => !Object.keys(colors).includes(x),
        );
        const extraKeys = Object.keys(colors).filter(
          (x) => !ACCEPTED_COLOR_PROPS.includes(x),
        );
        if (missingKeys.length > 0 || extraKeys.length > 0) {
          for (const missingKey of missingKeys) {
            errors.push(`Theme color properties \`${missingKey}\` are missing`);
          }

          for (const extraKey of extraKeys) {
            warnings.push(
              `Theme color properties \`${extraKey}\` is not supported`,
            );
          }
          invalidColors = true;
        } else {
          for (const [colorKey, colorValue] of Object.entries(colors)) {
            if (colorValue[0] === "#") {
              errors.push(
                `Theme color property \`${colorKey}\` should not start with '#'`,
              );
              invalidColors = true;
            } else if (colorValue.length > COLOR_PROPS[colorKey]) {
              errors.push(
                `Theme color property \`${colorKey}\` can not be longer than \`${COLOR_PROPS[colorKey]}\` characters`,
              );
              invalidColors = true;
            } else if (
              !(colorKey === "bg_color" && colorValue.split(",").length > 1
                ? isValidGradient(colorValue.split(","))
                : isValidHexColor(colorValue))
            ) {
              errors.push(
                `Theme color property \`${colorKey}\` is not a valid hex color: <code>${colorValue}</code>`,
              );
              invalidColors = true;
            }
          }
        }
      } else {
        warnings.push("Theme colors are missing");
        invalidColors = true;
      }
      if (invalidColors) {
        themeValid[theme] = false;
        previewBody += `
          \r### ${
            themeName.charAt(0).toUpperCase() + themeName.slice(1)
          } theme preview
          
          \r${warnings.map((warning) => `- :warning: ${warning}.\n`).join("")}
          \r${errors.map((error) => `- :x: ${error}.\n`).join("")}

          \r>:x: Cannot create theme preview.
        `;
        continue;
      }

      // Check color contrast.
      debug("Theme preview body: Check color contrast...");
      const titleColor = colors.title_color;
      const iconColor = colors.icon_color;
      const textColor = colors.text_color;
      const bgColor = colors.bg_color;
      const borderColor = colors.border_color;
      const url = getGRSLink(colors);
      const colorPairs = {
        title_color: [titleColor, bgColor],
        icon_color: [iconColor, bgColor],
        text_color: [textColor, bgColor],
      };
      Object.keys(colorPairs).forEach((item) => {
        let color1 = colorPairs[item][0];
        let color2 = colorPairs[item][1];
        const isGradientColor = color2.split(",").length > 1;
        if (isGradientColor) {
          return;
        }
        color1 = color1.length === 4 ? color1.slice(0, 3) : color1.slice(0, 6);
        color2 = color2.length === 4 ? color2.slice(0, 3) : color2.slice(0, 6);
        if (!ccc.isLevelAA(`#${color1}`, `#${color2}`)) {
          const permalink = getWebAimLink(color1, color2);
          warnings.push(
            `\`${item}\` does not pass [AA contrast ratio](${permalink})`,
          );
          themeValid[theme] = false;
        }
      });

      // Create theme preview body.
      debug("Theme preview body: Create theme preview body...");
      previewBody += `
        \r### ${
          themeName.charAt(0).toUpperCase() + themeName.slice(1)
        } theme preview
        
        \r${warnings.map((warning) => `- :warning: ${warning}.\n`).join("")}

        \ntitle_color: <code>#${titleColor}</code> | icon_color: <code>#${iconColor}</code> | text_color: <code>#${textColor}</code> | bg_color: <code>#${bgColor}</code>${
          borderColor ? ` | border_color: <code>#${borderColor}</code>` : ""
        }

        \r[Preview Link](${url})

        \r[![](${url})](${url})
      `;
    }

    // Create comment body.
    debug("Create comment body...");
    commentBody += `
      \r${
        Object.values(themeValid).every((value) => value)
          ? THEME_PR_SUCCESS_TEXT
          : THEME_PR_FAIL_TEXT
      }
      \r## Test results
      \r${Object.entries(themeValid)
        .map(
          ([key, value]) => `- ${value ? ":heavy_check_mark:" : ":x:"} ${key}`,
        )
        .join("\r")}

      \r${
        Object.values(themeValid).every((value) => value)
          ? "**Result:** :heavy_check_mark: All themes are valid."
          : "**Result:** :x: Some themes are invalid.\n\n" + FAIL_TEXT
      }
      
      \r## Details
      \r${previewBody}
    `;

    // Create or update theme-preview comment.
    debug("Create or update theme-preview comment...");
    let comment_url;
    if (DRY_RUN) {
      info(`DRY_RUN: Comment body: ${commentBody}`);
      comment_url = "";
    } else {
      comment_url = await upsertComment(
        OCTOKIT,
        PULL_REQUEST_ID,
        REPO,
        OWNER,
        comment?.id,
        commentBody,
      );
    }

    // Change review state and add/remove `invalid` label based on theme PR validity.
    debug(
      "Change review state and add/remove `invalid` label based on whether all themes passed...",
    );
    const themesValid = Object.values(themeValid).every((value) => value);
    const reviewState = themesValid ? "APPROVE" : "REQUEST_CHANGES";
    const reviewReason = themesValid
      ? undefined
      : INVALID_REVIEW_COMMENT(comment_url);
    if (DRY_RUN) {
      info(`DRY_RUN: Review state: ${reviewState}`);
      info(`DRY_RUN: Review reason: ${reviewReason}`);
    } else {
      await addReview(
        OCTOKIT,
        PULL_REQUEST_ID,
        OWNER,
        REPO,
        reviewState,
        reviewReason,
      );
      await addRemoveLabel(
        OCTOKIT,
        PULL_REQUEST_ID,
        OWNER,
        REPO,
        "invalid",
        !themesValid,
      );
    }
  } catch (error) {
    debug("Set review state to `REQUEST_CHANGES` and add `invalid` label...");
    if (DRY_RUN) {
      info(`DRY_RUN: Review state: REQUEST_CHANGES`);
      info(`DRY_RUN: Review reason: ${error.message}`);
    } else {
      await addReview(
        OCTOKIT,
        PULL_REQUEST_ID,
        OWNER,
        REPO,
        "REQUEST_CHANGES",
        "**Something went wrong in the theme preview action:** `" +
          error.message +
          "`",
      );
      await addRemoveLabel(
        OCTOKIT,
        PULL_REQUEST_ID,
        OWNER,
        REPO,
        "invalid",
        true,
      );
    }
    setFailed(error.message);
  }
}
Main function.

@returns {Promise<void>} Promise.