mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
As outlined in the previous commit which enabled the `esModuleInterop` TypeScript compiler option, we need to update all namespace imports for `typescript` to default imports. This is needed to allow for TypeScript to be imported at runtime from an ES module. Similar changes are needed for modules like `semver` where the types incorrectly suggest named exports that will not exist at runtime when imported from ESM. This commit refactors all imports to match with the lint rule we have configured in the previous commit. See the previous commit for more details on why certain imports have been changed. A special case are the imports to `@babel/core` and `@babel/types`. For these a special interop is needed as both default imports, or named imports break the other module format. e.g default imports would work well for ESM, but it breaks for CJS. For CJS, the named imports would only work, but in ESM, only the default export exist. We work around this for now until the devmode is using ESM as well (which would be consistent with prodmode and gives us more valuable test results). More details on the interop can be found in the `babel_core.ts` files (two interops are needed for both localize/or the compiler-cli). PR Close #43431
250 lines
8.2 KiB
TypeScript
250 lines
8.2 KiB
TypeScript
/**
|
|
* @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.io/license
|
|
*/
|
|
|
|
/**
|
|
* This script gets contribution stats for all members of the angular org,
|
|
* since a provided date.
|
|
* The script expects the following flag(s):
|
|
*
|
|
* required:
|
|
* --since [date] The data after which contributions are queried for.
|
|
* Uses githubs search format for dates, e.g. "2020-01-21".
|
|
* See
|
|
* https://help.github.com/en/github/searching-for-information-on-github/understanding-the-search-syntax#query-for-dates
|
|
*
|
|
* optional:
|
|
* --use-created [boolean] If the created timestamp should be used for
|
|
* time comparisons, defaults otherwise to the updated timestamp.
|
|
*/
|
|
|
|
import {graphql as unauthenticatedGraphql} from '@octokit/graphql';
|
|
import minimist from 'minimist';
|
|
import {alias, params, query as graphqlQuery, types} from 'typed-graphqlify';
|
|
|
|
// The organization to be considered for the queries.
|
|
const ORG = 'angular';
|
|
// The repositories to be considered for the queries.
|
|
const REPOS = ['angular', 'components', 'angular-cli'];
|
|
|
|
/**
|
|
* Handle flags for the script.
|
|
*/
|
|
const args = minimist(process.argv.slice(2), {
|
|
string: ['since'],
|
|
boolean: ['use-created'],
|
|
unknown: (option: string) => {
|
|
console.error(`Unknown option: ${option}`);
|
|
process.exit(1);
|
|
}
|
|
});
|
|
|
|
if (!args['since']) {
|
|
console.error(`Please provide --since [date]`);
|
|
process.exit(1);
|
|
}
|
|
|
|
/**
|
|
* Authenticated instance of Github GraphQl API service, relies on a
|
|
* personal access token being available in the TOKEN environment variable.
|
|
*/
|
|
const graphql = unauthenticatedGraphql.defaults({
|
|
headers: {
|
|
// TODO(josephperrott): Remove reference to TOKEN environment variable as part of larger
|
|
// effort to migrate to expecting tokens via GITHUB_ACCESS_TOKEN environment variables.
|
|
authorization: `token ${process.env.TOKEN || process.env.GITHUB_ACCESS_TOKEN}`,
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Retrieves all current members of an organization.
|
|
*/
|
|
async function getAllOrgMembers() {
|
|
// The GraphQL query object to get a page of members of an organization.
|
|
const MEMBERS_QUERY = params(
|
|
{
|
|
$first: 'Int', // How many entries to get with each request
|
|
$after: 'String', // The cursor to start the page at
|
|
$owner: 'String!', // The organization to query for
|
|
},
|
|
{
|
|
organization: params({login: '$owner'}, {
|
|
membersWithRole: params(
|
|
{
|
|
first: '$first',
|
|
after: '$after',
|
|
},
|
|
{
|
|
nodes: [{login: types.string}],
|
|
pageInfo: {
|
|
hasNextPage: types.boolean,
|
|
endCursor: types.string,
|
|
},
|
|
}),
|
|
})
|
|
});
|
|
const query = graphqlQuery('members', MEMBERS_QUERY);
|
|
|
|
/**
|
|
* Gets the query and queryParams for a specific page of entries.
|
|
*/
|
|
const queryBuilder = (count: number, cursor?: string) => {
|
|
return {
|
|
query,
|
|
params: {
|
|
after: cursor || null,
|
|
first: count,
|
|
owner: ORG,
|
|
},
|
|
};
|
|
};
|
|
|
|
// The current cursor
|
|
let cursor = undefined;
|
|
// If an additional page of members is expected
|
|
let hasNextPage = true;
|
|
// Array of Github usernames of the organization
|
|
const members: string[] = [];
|
|
|
|
while (hasNextPage) {
|
|
const {query, params} = queryBuilder(100, cursor);
|
|
const results = await graphql(query.toString(), params) as typeof MEMBERS_QUERY;
|
|
|
|
results.organization.membersWithRole.nodes.forEach(
|
|
(node: {login: string}) => members.push(node.login));
|
|
hasNextPage = results.organization.membersWithRole.pageInfo.hasNextPage;
|
|
cursor = results.organization.membersWithRole.pageInfo.endCursor;
|
|
}
|
|
return members.sort();
|
|
}
|
|
|
|
/**
|
|
* Build metadata for making requests for a specific user and date.
|
|
*
|
|
* Builds GraphQL query string, Query Params and Labels for making queries to GraphQl.
|
|
*/
|
|
function buildQueryAndParams(username: string, date: string) {
|
|
// Whether the updated or created timestamp should be used.
|
|
const updatedOrCreated = args['use-created'] ? 'created' : 'updated';
|
|
let dataQueries: {[key: string]: {query: string, label: string}} = {};
|
|
// Add queries and params for all values queried for each repo.
|
|
for (let repo of REPOS) {
|
|
dataQueries = {
|
|
...dataQueries,
|
|
[`${repo.replace(/[\/\-]/g, '_')}_issue_author`]: {
|
|
query: `repo:${ORG}/${repo} is:issue author:${username} ${updatedOrCreated}:>${date}`,
|
|
label: `${ORG}/${repo} Issue Authored`,
|
|
},
|
|
[`${repo.replace(/[\/\-]/g, '_')}_issues_involved`]: {
|
|
query: `repo:${ORG}/${repo} is:issue -author:${username} involves:${username} ${
|
|
updatedOrCreated}:>${date}`,
|
|
label: `${ORG}/${repo} Issue Involved`,
|
|
},
|
|
[`${repo.replace(/[\/\-]/g, '_')}_pr_author`]: {
|
|
query: `repo:${ORG}/${repo} is:pr author:${username} ${updatedOrCreated}:>${date}`,
|
|
label: `${ORG}/${repo} PR Author`,
|
|
},
|
|
[`${repo.replace(/[\/\-]/g, '_')}_pr_involved`]: {
|
|
query: `repo:${ORG}/${repo} is:pr involves:${username} ${updatedOrCreated}:>${date}`,
|
|
label: `${ORG}/${repo} PR Involved`,
|
|
},
|
|
[`${repo.replace(/[\/\-]/g, '_')}_pr_reviewed`]: {
|
|
query: `repo:${ORG}/${repo} is:pr -author:${username} reviewed-by:${username} ${
|
|
updatedOrCreated}:>${date}`,
|
|
label: `${ORG}/${repo} PR Reviewed`,
|
|
},
|
|
[`${repo.replace(/[\/\-]/g, '_')}_pr_commented`]: {
|
|
query: `repo:${ORG}/${repo} is:pr -author:${username} commenter:${username} ${
|
|
updatedOrCreated}:>${date}`,
|
|
label: `${ORG}/${repo} PR Commented`,
|
|
},
|
|
};
|
|
}
|
|
// Add queries and params for all values queried for the org.
|
|
dataQueries = {
|
|
...dataQueries,
|
|
[`${ORG}_org_issue_author`]: {
|
|
query: `org:${ORG} is:issue author:${username} ${updatedOrCreated}:>${date}`,
|
|
label: `${ORG} org Issue Authored`,
|
|
},
|
|
[`${ORG}_org_issues_involved`]: {
|
|
query: `org:${ORG} is:issue -author:${username} involves:${username} ${updatedOrCreated}:>${
|
|
date}`,
|
|
label: `${ORG} org Issue Involved`,
|
|
},
|
|
[`${ORG}_org_pr_author`]: {
|
|
query: `org:${ORG} is:pr author:${username} ${updatedOrCreated}:>${date}`,
|
|
label: `${ORG} org PR Author`,
|
|
},
|
|
[`${ORG}_org_pr_involved`]: {
|
|
query: `org:${ORG} is:pr involves:${username} ${updatedOrCreated}:>${date}`,
|
|
label: `${ORG} org PR Involved`,
|
|
},
|
|
[`${ORG}_org_pr_reviewed`]: {
|
|
query: `org:${ORG} is:pr -author:${username} reviewed-by:${username} ${updatedOrCreated}:>${
|
|
date}`,
|
|
label: `${ORG} org PR Reviewed`,
|
|
},
|
|
[`${ORG}_org_pr_commented`]: {
|
|
query:
|
|
`org:${ORG} is:pr -author:${username} commenter:${username} ${updatedOrCreated}:>${date}`,
|
|
label: `${ORG} org PR Commented`,
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Gets the labels for each requested value to be used as headers.
|
|
*/
|
|
function getLabels(pairs: typeof dataQueries) {
|
|
return Object.values(pairs).map(val => val.label);
|
|
}
|
|
|
|
/**
|
|
* Gets the graphql query object for the GraphQL query.
|
|
*/
|
|
function getQuery(pairs: typeof dataQueries) {
|
|
const output: {[key: string]: {}} = {};
|
|
Object.entries(pairs).map(([key, val]) => {
|
|
output[alias(key, 'search')] = params(
|
|
{
|
|
query: `"${val.query}"`,
|
|
type: 'ISSUE',
|
|
},
|
|
{
|
|
issueCount: types.number,
|
|
});
|
|
});
|
|
return output;
|
|
}
|
|
|
|
return {
|
|
query: graphqlQuery(getQuery(dataQueries)),
|
|
labels: getLabels(dataQueries),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Runs the script to create a CSV string with the requested data for each member
|
|
* of the organization.
|
|
*/
|
|
async function run(date: string) {
|
|
try {
|
|
const allOrgMembers = await getAllOrgMembers();
|
|
console.info(['Username', ...buildQueryAndParams('', date).labels].join(','));
|
|
|
|
for (const username of allOrgMembers) {
|
|
const results = await graphql(buildQueryAndParams(username, date).query.toString());
|
|
const values = Object.values(results).map(result => `${result.issueCount}`);
|
|
console.info([username, ...values].join(','));
|
|
}
|
|
} catch (error) {
|
|
console.error(`Error: ${error.message}`);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
run(args['since']);
|