mirror of
				https://github.com/home-assistant/frontend.git
				synced 2025-11-04 08:29:52 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			172 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			172 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
// Task to download the latest Lokalise translations from the nightly workflow artifacts
 | 
						|
 | 
						|
import { createOAuthDeviceAuth } from "@octokit/auth-oauth-device";
 | 
						|
import { retry } from "@octokit/plugin-retry";
 | 
						|
import { Octokit } from "@octokit/rest";
 | 
						|
import { deleteAsync } from "del";
 | 
						|
import { mkdir, readFile, writeFile } from "fs/promises";
 | 
						|
import gulp from "gulp";
 | 
						|
import jszip from "jszip";
 | 
						|
import path from "path";
 | 
						|
import process from "process";
 | 
						|
import { extract } from "tar";
 | 
						|
 | 
						|
const MAX_AGE = 24; // hours
 | 
						|
const OWNER = "home-assistant";
 | 
						|
const REPO = "frontend";
 | 
						|
const WORKFLOW_NAME = "nightly.yaml";
 | 
						|
const ARTIFACT_NAME = "translations";
 | 
						|
const CLIENT_ID = "Iv1.3914e28cb27834d1";
 | 
						|
const EXTRACT_DIR = "translations";
 | 
						|
const TOKEN_FILE = path.posix.join(EXTRACT_DIR, "token.json");
 | 
						|
const ARTIFACT_FILE = path.posix.join(EXTRACT_DIR, "artifact.json");
 | 
						|
 | 
						|
let allowTokenSetup = false;
 | 
						|
gulp.task("allow-setup-fetch-nightly-translations", (done) => {
 | 
						|
  allowTokenSetup = true;
 | 
						|
  done();
 | 
						|
});
 | 
						|
 | 
						|
gulp.task("fetch-nightly-translations", async function () {
 | 
						|
  // Skip all when environment flag is set (assumes translations are already in place)
 | 
						|
  if (process.env?.SKIP_FETCH_NIGHTLY_TRANSLATIONS) {
 | 
						|
    console.log("Skipping fetch due to environment signal");
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  // Read current translations artifact info if it exists,
 | 
						|
  // and stop if they are not old enough
 | 
						|
  let currentArtifact;
 | 
						|
  try {
 | 
						|
    currentArtifact = JSON.parse(await readFile(ARTIFACT_FILE, "utf-8"));
 | 
						|
    const currentAge =
 | 
						|
      (Date.now() - Date.parse(currentArtifact.created_at)) / 3600000;
 | 
						|
    if (currentAge < MAX_AGE) {
 | 
						|
      console.log(
 | 
						|
        "Keeping current translations (only %s hours old)",
 | 
						|
        currentAge.toFixed(1)
 | 
						|
      );
 | 
						|
      return;
 | 
						|
    }
 | 
						|
  } catch {
 | 
						|
    currentArtifact = null;
 | 
						|
  }
 | 
						|
 | 
						|
  // To store file writing promises
 | 
						|
  const createExtractDir = mkdir(EXTRACT_DIR, { recursive: true });
 | 
						|
  const writings = [];
 | 
						|
 | 
						|
  // Authenticate to GitHub using GitHub action token if it exists,
 | 
						|
  // otherwise look for a saved user token or generate a new one if none
 | 
						|
  let tokenAuth;
 | 
						|
  if (process.env.GITHUB_TOKEN) {
 | 
						|
    tokenAuth = { token: process.env.GITHUB_TOKEN };
 | 
						|
  } else {
 | 
						|
    try {
 | 
						|
      tokenAuth = JSON.parse(await readFile(TOKEN_FILE, "utf-8"));
 | 
						|
    } catch {
 | 
						|
      if (!allowTokenSetup) {
 | 
						|
        console.log("No token found so build will continue with English only");
 | 
						|
        return;
 | 
						|
      }
 | 
						|
      const auth = createOAuthDeviceAuth({
 | 
						|
        clientType: "github-app",
 | 
						|
        clientId: CLIENT_ID,
 | 
						|
        onVerification: (verification) => {
 | 
						|
          console.log(
 | 
						|
            "Task needs to authenticate to GitHub to fetch the translations from nightly workflow\n" +
 | 
						|
              "Please go to %s to authorize this task\n" +
 | 
						|
              "\nEnter user code: %s\n\n" +
 | 
						|
              "This code will expire in %s minutes\n" +
 | 
						|
              "Task will automatically continue after authorization and token will be saved for future use",
 | 
						|
            verification.verification_uri,
 | 
						|
            verification.user_code,
 | 
						|
            (verification.expires_in / 60).toFixed(0)
 | 
						|
          );
 | 
						|
        },
 | 
						|
      });
 | 
						|
      tokenAuth = await auth({ type: "oauth" });
 | 
						|
      writings.push(
 | 
						|
        createExtractDir.then(
 | 
						|
          writeFile(TOKEN_FILE, JSON.stringify(tokenAuth, null, 2))
 | 
						|
        )
 | 
						|
      );
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // Authenticate with token and request workflow runs from GitHub
 | 
						|
  console.log("Fetching new translations...");
 | 
						|
  const octokit = new (Octokit.plugin(retry))({
 | 
						|
    userAgent: "Fetch Nightly Translations",
 | 
						|
    auth: tokenAuth.token,
 | 
						|
  });
 | 
						|
 | 
						|
  const workflowRunsResponse = await octokit.rest.actions.listWorkflowRuns({
 | 
						|
    owner: OWNER,
 | 
						|
    repo: REPO,
 | 
						|
    workflow_id: WORKFLOW_NAME,
 | 
						|
    status: "success",
 | 
						|
    event: "schedule",
 | 
						|
    per_page: 1,
 | 
						|
    exclude_pull_requests: true,
 | 
						|
  });
 | 
						|
  if (workflowRunsResponse.data.total_count === 0) {
 | 
						|
    throw Error("No successful nightly workflow runs found");
 | 
						|
  }
 | 
						|
  const latestNightlyRun = workflowRunsResponse.data.workflow_runs[0];
 | 
						|
 | 
						|
  // Stop if current is already the latest, otherwise Find the translations artifact
 | 
						|
  if (currentArtifact?.workflow_run.id === latestNightlyRun.id) {
 | 
						|
    console.log("Stopping because current translations are still the latest");
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  const latestArtifact = (
 | 
						|
    await octokit.actions.listWorkflowRunArtifacts({
 | 
						|
      owner: OWNER,
 | 
						|
      repo: REPO,
 | 
						|
      run_id: latestNightlyRun.id,
 | 
						|
    })
 | 
						|
  ).data.artifacts.find((artifact) => artifact.name === ARTIFACT_NAME);
 | 
						|
  if (!latestArtifact) {
 | 
						|
    throw Error("Latest nightly workflow run has no translations artifact");
 | 
						|
  }
 | 
						|
  writings.push(
 | 
						|
    createExtractDir.then(
 | 
						|
      writeFile(ARTIFACT_FILE, JSON.stringify(latestArtifact, null, 2))
 | 
						|
    )
 | 
						|
  );
 | 
						|
 | 
						|
  // Remove the current translations
 | 
						|
  const deleteCurrent = Promise.all(writings).then(
 | 
						|
    deleteAsync([`${EXTRACT_DIR}/*`, `!${ARTIFACT_FILE}`, `!${TOKEN_FILE}`])
 | 
						|
  );
 | 
						|
 | 
						|
  // Get the download URL and follow the redirect to download (stored as ArrayBuffer)
 | 
						|
  const downloadResponse = await octokit.actions.downloadArtifact({
 | 
						|
    owner: OWNER,
 | 
						|
    repo: REPO,
 | 
						|
    artifact_id: latestArtifact.id,
 | 
						|
    archive_format: "zip",
 | 
						|
  });
 | 
						|
  if (downloadResponse.status !== 200) {
 | 
						|
    throw Error("Failure downloading translations artifact");
 | 
						|
  }
 | 
						|
 | 
						|
  // Artifact is a tarball, but GitHub adds it to a zip file
 | 
						|
  console.log("Unpacking downloaded translations...");
 | 
						|
  const zip = await jszip.loadAsync(downloadResponse.data);
 | 
						|
  await deleteCurrent;
 | 
						|
  const extractStream = zip.file(/.*/)[0].nodeStream().pipe(extract());
 | 
						|
  await new Promise((resolve, reject) => {
 | 
						|
    extractStream.on("close", resolve).on("error", reject);
 | 
						|
  });
 | 
						|
});
 | 
						|
 | 
						|
gulp.task(
 | 
						|
  "setup-and-fetch-nightly-translations",
 | 
						|
  gulp.series(
 | 
						|
    "allow-setup-fetch-nightly-translations",
 | 
						|
    "fetch-nightly-translations"
 | 
						|
  )
 | 
						|
);
 |