scripts/create.js (118 lines of code) (raw):
/**
* Create a new translation of the original repo in [source config] with the info in [trans config].
*
* YOU MUST HAVE ADMIN ACCESS TO THE ORGANIZATION FOR THIS TO WORK.
* BEHAVIOR IF YOU ARE NOT AN OWNER IS UNDEFINED.
*
* ```
* node scripts/create.js [source config] [trans config]
* ```
*
* Given the following files:
*
* `config.json`
* ```
* {
* "owner": "reactjs",
* "repository": "reactjs.org",
* "teamSlug": "reactjs-localization"
* }
* ```
*
* `arr.json`
* ```
* {
* "name": "Japanese",
* "code": "ja",
* "maintainers": ["smikitky", "potato4d"]
* }
* ```
*
* Running this script:
*
* ```
* node scripts/create.js config.json ja.json
* ```
*
* will have the following effects:
*
* * Create a new repository reactjs/ja.reactjs.org with the current contents of reactjs.org
* * Create a new issue in this repo "Japanese Translation Progress" with a list
* of pages to translate
* * Create a team "reactjs.org Japanese Translation" and invite all people listed in `maintainers`
* to the reactjs organization and give them access to the repository
*/
const fs = require('fs');
const shell = require('shelljs');
const log4js = require('log4js');
const { Octokit } = require('@octokit/rest');
const {getJSON} = require('../util');
// shell.config.silent = true;
const [srcConfigFile, langConfigFile] = process.argv.slice(2);
if (!srcConfigFile) {
throw new Error('Source config file not provided');
}
if (!langConfigFile) {
throw new Error('Language config file not provided');
}
const {owner, repository, teamSlug} = getJSON(srcConfigFile);
const {code: langCode, name: langName, maintainers} = getJSON(langConfigFile);
const logger = log4js.getLogger(langCode);
logger.level = 'info';
const originalUrl = `https://github.com/${owner}/${repository}.git`;
const newRepoName = `${langCode}.${repository}`;
const newRepoUrl = `https://github.com/${owner}/${newRepoName}.git`;
const defaultBranch = 'main';
const token = process.env.GITHUB_ADMIN_ACCESS_TOKEN;
const octokit = new Octokit({
auth: `token ${token}`,
previews: ['hellcat-preview'],
});
async function doesRepoExist() {
const {
data: {total_count},
} = await octokit.search.repos({
q: `org:${owner} "${newRepoName}"`,
});
return total_count > 0;
}
async function createProgressIssue() {
// Create the progress-tracking issue from the template
const rawBody = fs.readFileSync('./PROGRESS.template.md', 'utf8');
const maintainerList = maintainers.map(name => `* @${name}`).join('\n');
const body = rawBody.replace('{MAINTAINERS}\n', maintainerList);
await octokit.issues.create({
owner,
repo: newRepoName,
title: `${langName} Translation Progress`,
body,
});
logger.info('Created issue to track translation progress.');
}
async function addTeamMembers(team_id, members, role) {
await Promise.all(
members.map(async username => {
await octokit.teams.addOrUpdateMembership({
team_id,
username,
role,
});
}),
);
}
async function giveTeamRepoAccess(team_id) {
await octokit.teams.addOrUpdateRepo({
team_id,
owner,
repo: newRepoName,
permission: 'admin',
});
}
async function createTeam() {
// Find the parent team id
// TODO this may fail once we have more than *100* teams in the main org
const {data: allTeams} = await octokit.teams.list({
org: owner,
per_page: 100,
});
const parent_team_id = allTeams.find(team => team.slug === teamSlug).id;
// Make the team...
const {
data: {id: team_id},
} = await octokit.teams.create({
org: owner,
name: `${repository} ${langName} translation`,
description: `Discuss the translation of ${repository} into ${langName}.`,
privacy: 'closed',
parent_team_id,
});
await Promise.all([
giveTeamRepoAccess(team_id),
addTeamMembers(team_id, maintainers, 'maintainer'),
]);
logger.info('Set up a new team and invited maintainers!');
}
function pushOriginalContents() {
logger.trace('Setting up duplicate repo...');
shell.cd('repo');
// If we can't find the repo, clone it
if (shell.cd(repository).code !== 0) {
logger.debug("Can't find source repo locally. Cloning it...");
shell.exec(`git clone ${originalUrl} ${repository}`);
logger.debug('Finished cloning.');
shell.cd(repository);
}
// Set the remote to the newly created repo
shell.exec(`git pull origin ${defaultBranch}`);
shell.exec(`git remote add ${newRepoName} ${newRepoUrl}`);
shell.exec(`git push -u ${newRepoName} ${defaultBranch}`);
logger.info('Duplicated original repo');
}
// TODO it would be nice to do this as part of an automatic process,
// but I'm too scared not to do it manually rn
async function setupRepositoryAndTeam() {
if (await doesRepoExist()) {
logger.warn('Repo exists already.');
return;
}
logger.debug('Creating new repo in GitHub...');
await octokit.repos.createInOrg({
org: owner,
name: newRepoName,
// TODO generalize this (maybe get from the head repo?)
description: `(Work in progress) React documentation website in ${langName}`,
});
logger.info('Finished creating repo!');
// Create the progress-tracking issue from the template
await Promise.all([
createProgressIssue(),
createTeam(),
pushOriginalContents(),
]);
}
setupRepositoryAndTeam();