mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
3093edcad0
- Validate storedSha and storedBranch from _build-info.json. - Validate latestSha returned from GitHub API. - Validate branch in GithubClient.getShaForBranch and baseSha/headSha in GithubClient.getAffectedFiles. - Use execFileSync instead of execSync to avoid shell execution. TAG=agy CONV=4e3e69ba-3f3d-416b-9ce4-9ef75486d2f3
151 lines
4.7 KiB
JavaScript
151 lines
4.7 KiB
JavaScript
/**
|
|
* @license
|
|
* Copyright Google LLC All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.dev/license
|
|
*/
|
|
|
|
//tslint:disable:no-console
|
|
import assert from 'node:assert';
|
|
import {execFileSync} from 'node:child_process';
|
|
import {existsSync, constants as fsConstants} from 'node:fs';
|
|
import {
|
|
copyFile,
|
|
mkdtemp,
|
|
readdir,
|
|
readFile,
|
|
realpath,
|
|
rm,
|
|
unlink,
|
|
writeFile,
|
|
} from 'node:fs/promises';
|
|
import {tmpdir} from 'node:os';
|
|
import {join} from 'node:path';
|
|
import {GithubClient} from './github-client.mjs';
|
|
|
|
export async function updateAssets({repo, assetsPath, destPath}) {
|
|
console.log('\n-----------------------------------------------');
|
|
console.log(`Processing: ${repo}`);
|
|
console.log('-----------------------------------------------\n');
|
|
|
|
const buildInfoPath = join(destPath, '_build-info.json');
|
|
if (!existsSync(buildInfoPath)) {
|
|
throw new Error(`${buildInfoPath} does not exist.`);
|
|
}
|
|
|
|
assert(process.env.GITHUB_REF);
|
|
const currentBranch = process.env.GITHUB_REF;
|
|
|
|
const {sha: storedSha, branchName: storedBranch} = JSON.parse(
|
|
await readFile(buildInfoPath, 'utf-8'),
|
|
);
|
|
|
|
const shaRegex = /^[0-9a-f]{40}$/i;
|
|
const branchRegex = /^(?!.*\.\.)[a-zA-Z0-9/_.-]+$/;
|
|
|
|
if (!shaRegex.test(storedSha)) {
|
|
throw new Error(`Invalid SHA in build info: ${storedSha}`);
|
|
}
|
|
if (!branchRegex.test(storedBranch)) {
|
|
throw new Error(`Invalid branch name in build info: ${storedBranch}`);
|
|
}
|
|
|
|
assert(process.env.ANGULAR_READONLY_GITHUB_TOKEN);
|
|
const githubApi = new GithubClient(
|
|
repo,
|
|
process.env.ANGULAR_READONLY_GITHUB_TOKEN,
|
|
'ADEV_Cross_Repo_Docs_Update',
|
|
);
|
|
|
|
let downstreamBranch = currentBranch;
|
|
let latestSha = await githubApi.getShaForBranch(currentBranch);
|
|
if (
|
|
latestSha.includes('No commit') &&
|
|
currentBranch !== 'refs/heads/main' &&
|
|
currentBranch !== storedBranch
|
|
) {
|
|
// In some cases, such as when a new branch is created for a feature,
|
|
// the branch may not exist in the downstream repo. For example, during an
|
|
// exceptional minor release (e.g. FW 20.3.x and Components: 20.2.x).
|
|
// In such scenarios, we fallback to the last known branch.
|
|
latestSha = await githubApi.getShaForBranch(storedBranch);
|
|
downstreamBranch = storedBranch;
|
|
}
|
|
|
|
if (!shaRegex.test(latestSha)) {
|
|
throw new Error(`Invalid SHA resolved: ${latestSha}`);
|
|
}
|
|
|
|
console.log(`Comparing ${storedSha}...${latestSha}.`);
|
|
const affectedFiles = await githubApi.getAffectedFiles(storedSha, latestSha);
|
|
const changedFiles = affectedFiles.filter((file) => file.startsWith(`${assetsPath}/`));
|
|
|
|
let shaWhenFilesChanged;
|
|
if (changedFiles.length > 0) {
|
|
console.log(
|
|
`The below files changed between ${storedSha} and ${latestSha}:\n` +
|
|
changedFiles.map((f) => '* ' + f).join('\n'),
|
|
);
|
|
|
|
const temporaryDir = await realpath(await mkdtemp(join(tmpdir(), 'update-assets-')));
|
|
|
|
try {
|
|
const execOptions = {cwd: temporaryDir, stdio: 'inherit'};
|
|
execFileSync('git', ['init'], execOptions);
|
|
execFileSync(
|
|
'git',
|
|
['remote', 'add', 'origin', `https://github.com/${repo}.git`],
|
|
execOptions,
|
|
);
|
|
// fetch a commit
|
|
execFileSync('git', ['fetch', 'origin', latestSha], execOptions);
|
|
// reset this repository's main branch to the commit of interest
|
|
execFileSync('git', ['reset', '--hard', 'FETCH_HEAD'], execOptions);
|
|
// get sha when files where changed
|
|
shaWhenFilesChanged = execFileSync('git', ['rev-list', '-1', latestSha, `${assetsPath}/`], {
|
|
encoding: 'utf8',
|
|
cwd: temporaryDir,
|
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
}).trim();
|
|
|
|
// Delete existing asset files.
|
|
const apiFilesUnlink = (await readdir(destPath))
|
|
.filter((f) => f.endsWith('.json'))
|
|
.map((f) => unlink(join(destPath, f)));
|
|
|
|
await Promise.allSettled(apiFilesUnlink);
|
|
|
|
// Copy new asset files
|
|
const tempAssetsDir = join(temporaryDir, assetsPath);
|
|
const assetFilesCopy = (await readdir(tempAssetsDir)).map((f) => {
|
|
const src = join(tempAssetsDir, f);
|
|
const dest = join(destPath, f);
|
|
|
|
return copyFile(src, dest, fsConstants.COPYFILE_FICLONE);
|
|
});
|
|
|
|
await Promise.allSettled(assetFilesCopy);
|
|
} finally {
|
|
await rm(temporaryDir, {force: true, recursive: true});
|
|
}
|
|
|
|
console.log(`Successfully updated asset files in '${destPath}'.\n`);
|
|
} else {
|
|
console.log(`No '${assetsPath}/**' files changed between ${storedSha} and ${latestSha}.`);
|
|
}
|
|
|
|
// Write SHA to file.
|
|
await writeFile(
|
|
buildInfoPath,
|
|
JSON.stringify(
|
|
{
|
|
branchName: downstreamBranch,
|
|
sha: shaWhenFilesChanged ?? storedSha,
|
|
},
|
|
undefined,
|
|
2,
|
|
),
|
|
);
|
|
}
|