mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
ci: add a script to copy cdk api files to adev (#61081)
Refactors the update-cli-help script into a generic script to copy json assets, and uses the shared code to also copy the CDK apis PR Close #61081
This commit is contained in:
parent
88858118ea
commit
8828a84ecf
10 changed files with 271 additions and 147 deletions
44
.github/workflows/update-cdk-apis.yml
vendored
Normal file
44
.github/workflows/update-cdk-apis.yml
vendored
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
name: Update ADEV Angular CDK apis
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs: {}
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
- '[0-9]+.[0-9]+.x'
|
||||
|
||||
# Declare default permissions as read only.
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
update_cli_help:
|
||||
name: Update Angular CDK apis (if necessary)
|
||||
if: github.repository == 'angular/angular'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
# Setting `persist-credentials: false` prevents the github-action account from being the
|
||||
# account that is attempted to be used for authentication, instead the remote is set to
|
||||
# an authenticated URL.
|
||||
persist-credentials: false
|
||||
# This is needed as otherwise the PR creation will fail with `shallow update not allowed` when the forked branch is not in sync.
|
||||
fetch-depth: 0
|
||||
- name: Generate CDK apis
|
||||
run: node adev/scripts/update-cdk-apis/index.mjs
|
||||
env:
|
||||
ANGULAR_CDK_BUILDS_READONLY_GITHUB_TOKEN: ${{ secrets.ANGULAR_CDK_BUILDS_READONLY_GITHUB_TOKEN }}
|
||||
- name: Create a PR (if necessary)
|
||||
uses: angular/dev-infra/github-actions/create-pr-for-changes@14c3d6bd2fa5c3231be7bd4b3c0bba68c9d79e94
|
||||
with:
|
||||
branch-prefix: update-cdk-apis
|
||||
pr-title: 'docs: update Angular CDK apis [${{github.ref_name}}]'
|
||||
pr-description: |
|
||||
Updated Angular CDK api files.
|
||||
pr-labels: |
|
||||
action: review
|
||||
area: docs
|
||||
angular-robot-token: ${{ secrets.ANGULAR_ROBOT_ACCESS_TOKEN }}
|
||||
90
adev/scripts/shared/copy-json-assets.mjs
Normal file
90
adev/scripts/shared/copy-json-assets.mjs
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
/**
|
||||
* @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 {execSync} from 'node:child_process';
|
||||
import {existsSync, constants as fsConstants} from 'node:fs';
|
||||
import {copyFile, mkdtemp, readdir, readFile, realpath, unlink, writeFile} from 'node:fs/promises';
|
||||
import {tmpdir} from 'node:os';
|
||||
import {join} from 'node:path';
|
||||
|
||||
export async function copyJsonAssets({repo, githubApi, assetsPath, destPath}) {
|
||||
const buildInfoPath = join(destPath, '_build-info.json');
|
||||
if (!existsSync(buildInfoPath)) {
|
||||
throw new Error(`${buildInfoPath} does not exist.`);
|
||||
}
|
||||
|
||||
const branch = process.env.GITHUB_REF;
|
||||
const {sha: currentSha} = JSON.parse(await readFile(buildInfoPath, 'utf-8'));
|
||||
const latestSha = await githubApi.getShaForBranch(branch);
|
||||
|
||||
console.log(`Comparing ${currentSha}...${latestSha}.`);
|
||||
const affectedFiles = await githubApi.getAffectedFiles(currentSha, latestSha);
|
||||
const changedFiles = affectedFiles.filter((file) => file.startsWith(`${assetsPath}/`));
|
||||
|
||||
if (changedFiles.length === 0) {
|
||||
console.log(`No '${assetsPath}/**' files changed between ${currentSha} and ${latestSha}.`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`The below files changed between ${currentSha} and ${latestSha}:\n` +
|
||||
changedFiles.map((f) => '* ' + f).join('\n'),
|
||||
);
|
||||
|
||||
const temporaryDir = await realpath(await mkdtemp(join(tmpdir(), 'copy-json-assets-')));
|
||||
const execOptions = {cwd: temporaryDir, stdio: 'inherit'};
|
||||
execSync('git init', execOptions);
|
||||
execSync(`git remote add origin https://github.com/${repo}.git`, execOptions);
|
||||
// fetch a commit
|
||||
execSync(`git fetch origin ${latestSha}`, execOptions);
|
||||
// reset this repository's main branch to the commit of interest
|
||||
execSync('git reset --hard FETCH_HEAD', execOptions);
|
||||
// get sha when files where changed
|
||||
const shaWhenFilesChanged = execSync(`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);
|
||||
|
||||
// Write SHA to file.
|
||||
await writeFile(
|
||||
buildInfoPath,
|
||||
JSON.stringify(
|
||||
{
|
||||
branchName: branch,
|
||||
sha: shaWhenFilesChanged,
|
||||
},
|
||||
undefined,
|
||||
2,
|
||||
),
|
||||
);
|
||||
|
||||
console.log('\nChanges: ');
|
||||
execSync(`git status --porcelain`, {stdio: 'inherit'});
|
||||
|
||||
console.log(`Successfully updated asset files in '${destPath}'.\n`);
|
||||
}
|
||||
77
adev/scripts/shared/github-client.mjs
Normal file
77
adev/scripts/shared/github-client.mjs
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
import {get} from 'node:https';
|
||||
import {posix} from 'node:path';
|
||||
|
||||
const GITHUB_API = 'https://api.github.com/repos/';
|
||||
|
||||
export class GithubClient {
|
||||
#token;
|
||||
#ua;
|
||||
#api;
|
||||
|
||||
constructor(repo, token, ua) {
|
||||
this.#token = token;
|
||||
this.#ua = ua;
|
||||
this.#api = posix.join(GITHUB_API, repo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the affected files.
|
||||
*
|
||||
* @param {string} baseSha
|
||||
* @param {string} headSha
|
||||
* @returns Promise<string[]>
|
||||
*/
|
||||
async getAffectedFiles(baseSha, headSha) {
|
||||
const {files} = JSON.parse(await this.#httpGet(`${this.#api}/compare/${baseSha}...${headSha}`));
|
||||
return files.map((f) => f.filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SHA of a branch.
|
||||
*
|
||||
* @param {string} branch
|
||||
* @returns Promise<string>
|
||||
*/
|
||||
async getShaForBranch(branch) {
|
||||
const sha = await this.#httpGet(`${this.#api}/commits/${branch}`, {
|
||||
headers: {Accept: 'application/vnd.github.VERSION.sha'},
|
||||
});
|
||||
|
||||
if (!sha) {
|
||||
throw new Error(`Unable to extract the SHA for '${branch}'.`);
|
||||
}
|
||||
|
||||
return sha;
|
||||
}
|
||||
|
||||
#httpGet(url, options = {}) {
|
||||
options.headers ??= {};
|
||||
options.headers['Authorization'] = `token ${this.#token}`;
|
||||
// User agent is required
|
||||
// https://docs.github.com/en/rest/overview/resources-in-the-rest-api?apiVersion=2022-11-28#user-agent-required
|
||||
options.headers['User-Agent'] = this.#ua;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
get(url, options, (res) => {
|
||||
let data = '';
|
||||
res
|
||||
.on('data', (chunk) => {
|
||||
data += chunk;
|
||||
})
|
||||
.on('end', () => {
|
||||
resolve(data);
|
||||
});
|
||||
}).on('error', (e) => {
|
||||
reject(e);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
3
adev/scripts/update-cdk-apis/README.md
Normal file
3
adev/scripts/update-cdk-apis/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Generating data for `angular.dev/api` CDK packages
|
||||
|
||||
This script updates the Angular CDK api JSON files stored in `adev/src/content/cdk`. This files are used to generate the [angular.dev api](https://angular.dev/api) pages for the CDK packages.
|
||||
35
adev/scripts/update-cdk-apis/index.mjs
Normal file
35
adev/scripts/update-cdk-apis/index.mjs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
import {dirname, resolve as resolvePath} from 'node:path';
|
||||
import {fileURLToPath} from 'node:url';
|
||||
import {copyJsonAssets} from '../shared/copy-json-assets.mjs';
|
||||
import {GithubClient} from '../shared/github-client.mjs';
|
||||
|
||||
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
const CDK_BUILDS_REPO = 'angular/cdk-builds';
|
||||
const CDK_APIS_CONTENT_PATH = resolvePath(scriptDir, '../../src/content/cdk');
|
||||
|
||||
async function main() {
|
||||
await copyJsonAssets({
|
||||
repo: CDK_BUILDS_REPO,
|
||||
assetsPath: '_adev_assets',
|
||||
destPath: CDK_APIS_CONTENT_PATH,
|
||||
githubApi: new GithubClient(
|
||||
CDK_BUILDS_REPO,
|
||||
process.env.ANGULAR_CDK_BUILDS_READONLY_GITHUB_TOKEN,
|
||||
'ADEV_Angular_CDK_Sources_Update',
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
@ -6,154 +6,26 @@
|
|||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
//tslint:disable:no-console
|
||||
import {execSync} from 'node:child_process';
|
||||
import {readFile, writeFile, readdir, mkdtemp, realpath, copyFile, unlink} from 'node:fs/promises';
|
||||
import {tmpdir} from 'node:os';
|
||||
import {get} from 'node:https';
|
||||
import {dirname, resolve as resolvePath, posix, join} from 'node:path';
|
||||
import {dirname, resolve as resolvePath} from 'node:path';
|
||||
import {fileURLToPath} from 'node:url';
|
||||
import {existsSync, constants as fsConstants} from 'node:fs';
|
||||
|
||||
const GITHUB_API = 'https://api.github.com/repos/';
|
||||
const CLI_BUILDS_REPO = 'angular/cli-builds';
|
||||
const GITHUB_API_CLI_BUILDS = posix.join(GITHUB_API, CLI_BUILDS_REPO);
|
||||
import {copyJsonAssets} from '../shared/copy-json-assets.mjs';
|
||||
import {GithubClient} from '../shared/github-client.mjs';
|
||||
|
||||
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
const CLI_BUILDS_REPO = 'angular/cli-builds';
|
||||
const CLI_HELP_CONTENT_PATH = resolvePath(scriptDir, '../../src/content/cli/help');
|
||||
const CLI_SHA_PATH = join(CLI_HELP_CONTENT_PATH, 'build-info.json');
|
||||
|
||||
async function main() {
|
||||
if (!existsSync(CLI_SHA_PATH)) {
|
||||
throw new Error(`${CLI_SHA_PATH} does not exist.`);
|
||||
}
|
||||
|
||||
const branch = process.env.GITHUB_REF;
|
||||
const {sha: currentSha} = JSON.parse(await readFile(CLI_SHA_PATH, 'utf-8'));
|
||||
const latestSha = await getShaFromCliBuilds(branch);
|
||||
|
||||
console.log(`Comparing ${currentSha}...${latestSha}.`);
|
||||
const affectedFiles = await getAffectedFiles(currentSha, latestSha);
|
||||
const changedHelpFiles = affectedFiles.filter((file) => file.startsWith('help/'));
|
||||
|
||||
if (changedHelpFiles.length === 0) {
|
||||
console.log(`No 'help/**' files changed between ${currentSha} and ${latestSha}.`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`The below help files changed between ${currentSha} and ${latestSha}:\n` +
|
||||
changedHelpFiles.map((f) => '* ' + f).join('\n'),
|
||||
);
|
||||
|
||||
const temporaryDir = await realpath(await mkdtemp(join(tmpdir(), 'cli-src-')));
|
||||
const execOptions = {cwd: temporaryDir, stdio: 'inherit'};
|
||||
execSync('git init', execOptions);
|
||||
execSync('git remote add origin https://github.com/angular/cli-builds.git', execOptions);
|
||||
// fetch a commit
|
||||
execSync(`git fetch origin ${latestSha}`, execOptions);
|
||||
// reset this repository's main branch to the commit of interest
|
||||
execSync('git reset --hard FETCH_HEAD', execOptions);
|
||||
// get sha when files where changed
|
||||
const shaWhenFilesChanged = execSync(`git rev-list -1 ${latestSha} "help/"`, {
|
||||
encoding: 'utf8',
|
||||
cwd: temporaryDir,
|
||||
stdio: ['ignore', 'pipe', 'ignore'],
|
||||
}).trim();
|
||||
|
||||
// Delete existing JSON help files.
|
||||
const helpFilesUnlink = (await readdir(CLI_HELP_CONTENT_PATH))
|
||||
.filter((f) => f.endsWith('.json'))
|
||||
.map((f) => unlink(join(CLI_HELP_CONTENT_PATH, f)));
|
||||
|
||||
await Promise.allSettled(helpFilesUnlink);
|
||||
|
||||
// Copy new help files
|
||||
const tempHelpDir = join(temporaryDir, 'help');
|
||||
const helpFilesCopy = (await readdir(tempHelpDir)).map((f) => {
|
||||
const src = join(tempHelpDir, f);
|
||||
const dest = join(CLI_HELP_CONTENT_PATH, f);
|
||||
|
||||
return copyFile(src, dest, fsConstants.COPYFILE_FICLONE);
|
||||
});
|
||||
|
||||
await Promise.allSettled(helpFilesCopy);
|
||||
|
||||
// Write SHA to file.
|
||||
await writeFile(
|
||||
CLI_SHA_PATH,
|
||||
JSON.stringify(
|
||||
{
|
||||
branchName: branch,
|
||||
sha: shaWhenFilesChanged,
|
||||
},
|
||||
undefined,
|
||||
2,
|
||||
await copyJsonAssets({
|
||||
repo: CLI_BUILDS_REPO,
|
||||
assetsPath: 'help',
|
||||
destPath: CLI_HELP_CONTENT_PATH,
|
||||
githubApi: new GithubClient(
|
||||
CLI_BUILDS_REPO,
|
||||
process.env.ANGULAR_CLI_BUILDS_READONLY_GITHUB_TOKEN,
|
||||
'ADEV_Angular_CLI_Sources_Update',
|
||||
),
|
||||
);
|
||||
|
||||
console.log('\nChanges: ');
|
||||
execSync(`git status --porcelain`, {stdio: 'inherit'});
|
||||
|
||||
console.log(`Successfully updated help files in '${CLI_HELP_CONTENT_PATH}'.\n`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SHA of a branch.
|
||||
*
|
||||
* @param {string} branch
|
||||
* @param {string} headSha
|
||||
* @returns Promise<string>
|
||||
*/
|
||||
async function getShaFromCliBuilds(branch) {
|
||||
const sha = await httpGet(`${GITHUB_API_CLI_BUILDS}/commits/${branch}`, {
|
||||
headers: {Accept: 'application/vnd.github.VERSION.sha'},
|
||||
});
|
||||
|
||||
if (!sha) {
|
||||
throw new Error(`Unable to extract the SHA for '${branch}'.`);
|
||||
}
|
||||
|
||||
return sha;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the affected files.
|
||||
*
|
||||
* @param {string} baseSha
|
||||
* @param {string} headSha
|
||||
* @returns Promise<string[]>
|
||||
*/
|
||||
async function getAffectedFiles(baseSha, headSha) {
|
||||
const {files} = JSON.parse(
|
||||
await httpGet(`${GITHUB_API_CLI_BUILDS}/compare/${baseSha}...${headSha}`),
|
||||
);
|
||||
return files.map((f) => f.filename);
|
||||
}
|
||||
|
||||
function httpGet(url, options = {}) {
|
||||
options.headers ??= {};
|
||||
options.headers[
|
||||
'Authorization'
|
||||
] = `token ${process.env.ANGULAR_CLI_BUILDS_READONLY_GITHUB_TOKEN}`;
|
||||
// User agent is required
|
||||
// https://docs.github.com/en/rest/overview/resources-in-the-rest-api?apiVersion=2022-11-28#user-agent-required
|
||||
options.headers['User-Agent'] = `ADEV_Angular_CLI_Sources_Update`;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
get(url, options, (res) => {
|
||||
let data = '';
|
||||
res
|
||||
.on('data', (chunk) => {
|
||||
data += chunk;
|
||||
})
|
||||
.on('end', () => {
|
||||
resolve(data);
|
||||
});
|
||||
}).on('error', (e) => {
|
||||
reject(e);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
4
adev/src/content/cdk/_build-info.json
Normal file
4
adev/src/content/cdk/_build-info.json
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"branchName": "refs/heads/main",
|
||||
"sha": "9500b84652d34db69e545a1b24b2728ea86f0328"
|
||||
}
|
||||
|
|
@ -9,6 +9,6 @@ filegroup(
|
|||
],
|
||||
) + [
|
||||
"//adev/src/content/cli/help",
|
||||
"//adev/src/content/cli/help:build-info.json",
|
||||
"//adev/src/content/cli/help:_build-info.json",
|
||||
],
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,16 +2,15 @@ load("//adev/shared-docs/pipeline/api-gen/rendering:render_api_to_html.bzl", "re
|
|||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
exports_files(["build-info.json"])
|
||||
exports_files(["_build-info.json"])
|
||||
|
||||
filegroup(
|
||||
name = "help",
|
||||
srcs = glob(
|
||||
["*"],
|
||||
["*.json"],
|
||||
exclude = [
|
||||
# Exlucde build-info.json as it is not a help entry.
|
||||
"build-info.json",
|
||||
"BUILD.bazel",
|
||||
# Exlucde _build-info.json as it is not a help entry.
|
||||
"_*.json",
|
||||
],
|
||||
),
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue