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 wil 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"
|
|
)
|
|
);
|