feat: versioned docs
The way versioned documents are supported on the site differs a bit from Docusaurus' documentation: - Each doc change to a branch is build and publish separately. - The links to the versions are `_blank` to bypass `react-router` even though the content is in the same server. The reason is that because it is built at different steps it is not "found". This is achieve thanks to `electron-website-updater` sending a `repository_dispatch` event with 2 different types of actions for each `push` event that happens in `electron/electron` that contains doc changes. The 2 types of events are: * `doc_changes_branches`: Updates to documentation branches (does not matter if they are the latest or not). * `doc_changes`: Updates to the latest stable branch. The tasks to perform are almost identical and are split in two different GitHub actions for each event: 1. Download the markdown for the SHA, check if there are new version branches and update `versions-info.json`, update the SHA, and publish those changes to Git. This is done in `update-docs-XXX.yml` when we receive the event. 2. Build the content for that branch and publish in the right place. This is done in `push-XXX.yml` when there is a push to `main` or a `vXX` branch. The deployment is done to the storage service. The difference is that a branch will only push `assets` and `docs` while `main` will publish everything. Additionally, Crowdin gets updated every time there is a change in `main` to make sure the latest content is always uploaded. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Fix #118
This commit is contained in:
Родитель
1f37587ca5
Коммит
17a1bd6872
|
@ -11,15 +11,18 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Use Node.js 14
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 14
|
||||
|
||||
- name: Install dependencies
|
||||
uses: bahmutov/npm-install@HEAD
|
||||
|
||||
- name: Upload sources to Crowdin
|
||||
run: 'yarn i18n:upload'
|
||||
env:
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
||||
|
||||
- name: Test
|
||||
run: yarn test
|
||||
env:
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
name: Push on version branch
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'v**'
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
name: Push updates to previous versions
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set environment variables
|
||||
run: |
|
||||
echo "GIT_BRANCH=${GITHUB_REF##*/}" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js 14
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 14
|
||||
- name: Install dependencies
|
||||
uses: bahmutov/npm-install@HEAD
|
||||
|
||||
- name: 'Build branch version (EN)'
|
||||
run: |
|
||||
node scripts/build-as-doc-version.js ${GIT_BRANCH}
|
||||
yarn i18n:build en
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: 'Publish Assets to Storage'
|
||||
run: ./scripts/bin/azcopy copy "./build/assets/*" "https://electronjsorg.blob.core.windows.net/%24web/assets?$SAS" --recursive
|
||||
env:
|
||||
SAS: ${{ secrets.SAS }}
|
||||
- name: 'Publish docs to Storage'
|
||||
run: ./scripts/bin/azcopy copy "./build/docs/*" "https://electronjsorg.blob.core.windows.net/%24web/docs?$SAS" --recursive
|
||||
env:
|
||||
SAS: ${{ secrets.SAS }}
|
|
@ -0,0 +1,25 @@
|
|||
name: 'Update docs'
|
||||
|
||||
on:
|
||||
repository_dispatch:
|
||||
types: [doc_changes_branches]
|
||||
|
||||
jobs:
|
||||
update-docs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14'
|
||||
- name: 'Switch branches'
|
||||
# We switch to the version branch or create a new one if needed
|
||||
run: git fetch origin && git checkout -t origin/v${{ github.event.client_payload.branch}} || git checkout -b v${{ github.event.client_payload.branch}}
|
||||
- name: Install dependencies
|
||||
run: 'yarn'
|
||||
- name: 'Prebuild'
|
||||
run: 'yarn pre-build ${{ github.event.client_payload.sha }}'
|
||||
- name: 'Push changes or create PR'
|
||||
run: 'yarn process-docs-changes'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@ -14,15 +14,9 @@ jobs:
|
|||
node-version: '14'
|
||||
- name: Install dependencies
|
||||
run: 'yarn'
|
||||
- name: Update pinned version
|
||||
run: 'yarn update-pinned-version ${{ github.event.client_payload.sha }}'
|
||||
- name: 'Prebuild'
|
||||
run: 'yarn pre-build'
|
||||
- name: Upload sources to Crowdin
|
||||
run: yarn i18n:upload
|
||||
env:
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
||||
- name: 'Create PR'
|
||||
- name: 'Download docs'
|
||||
run: 'yarn pre-build ${{ github.event.client_payload.sha }}'
|
||||
- name: 'Push changes or create PR'
|
||||
run: 'yarn process-docs-changes'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
|
|
@ -3,7 +3,10 @@ node_modules
|
|||
.DS_Store
|
||||
.env
|
||||
.vscode/settings.json
|
||||
.tmp/
|
||||
build/
|
||||
content/
|
||||
i18n/
|
||||
!i18n/en/
|
||||
!i18n/en/
|
||||
docs/
|
||||
!docs/latest
|
|
@ -2,13 +2,14 @@
|
|||
const npm2yarn = require('@docusaurus/remark-plugin-npm2yarn');
|
||||
const fiddleEmbedder = require('./src/transformers/fiddle-embedder.js');
|
||||
const apiLabels = require('./src/transformers/api-labels.js');
|
||||
const docVersions = require('./versions-info.json');
|
||||
|
||||
module.exports = {
|
||||
title: 'Electron',
|
||||
tagline: 'Build cross-platform desktop apps with JavaScript, HTML, and CSS',
|
||||
url: 'https://electronjs.org',
|
||||
baseUrl: '/',
|
||||
onBrokenLinks: 'throw',
|
||||
onBrokenLinks: 'warn',
|
||||
onBrokenMarkdownLinks: 'warn',
|
||||
favicon: 'assets/img/favicon.ico',
|
||||
organizationName: 'electron',
|
||||
|
@ -73,6 +74,12 @@ module.exports = {
|
|||
label: 'Releases',
|
||||
position: 'right',
|
||||
},
|
||||
{
|
||||
type: 'dropdown',
|
||||
label: 'View another version',
|
||||
position: 'right',
|
||||
items: docVersions,
|
||||
},
|
||||
{
|
||||
type: 'localeDropdown',
|
||||
position: 'right',
|
||||
|
|
|
@ -26,5 +26,13 @@
|
|||
"item.label.GitHub": {
|
||||
"message": "GitHub",
|
||||
"description": "Navbar item with label GitHub"
|
||||
},
|
||||
"item.label.View another version": {
|
||||
"message": "View another version",
|
||||
"description": "Navbar item with label View another version"
|
||||
},
|
||||
"item.label.Current": {
|
||||
"message": "Current",
|
||||
"description": "Navbar item with label Current"
|
||||
}
|
||||
}
|
|
@ -16,17 +16,9 @@ describe('process-docs-changes', () => {
|
|||
expect(gitMock.pushChanges).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('does not create any PR if package.json is not modified', async () => {
|
||||
gitMock.getChanges.mockResolvedValue('M sidebars.json');
|
||||
|
||||
await processDocsChanges();
|
||||
|
||||
expect(gitMock.createPR).toHaveBeenCalledTimes(0);
|
||||
expect(gitMock.pushChanges).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('pushes changes directly to main if only package.json is modified', async () => {
|
||||
gitMock.getChanges.mockResolvedValue('M package.json');
|
||||
gitMock.getCurrentBranchName.mockResolvedValue('main');
|
||||
|
||||
await processDocsChanges();
|
||||
|
||||
|
@ -40,10 +32,12 @@ describe('process-docs-changes', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('does create a PR if more files than package.json are modified', async () => {
|
||||
it('does create a PR if more files than package.json are modified and branch is tracked', async () => {
|
||||
gitMock.getChanges.mockResolvedValue(
|
||||
'M package.json\nM sidebars.json\nU randomDoc.md'
|
||||
);
|
||||
gitMock.isCurrentBranchTracked.mockResolvedValue(true);
|
||||
gitMock.getCurrentBranchName.mockResolvedValue('main');
|
||||
|
||||
await processDocsChanges();
|
||||
|
||||
|
@ -57,4 +51,24 @@ describe('process-docs-changes', () => {
|
|||
'"chore: update ref to docs (🤖)"'
|
||||
);
|
||||
});
|
||||
|
||||
it('does push changes if branch is not tracked regardless of the number of files', async () => {
|
||||
gitMock.getChanges.mockResolvedValue('M package.json\n');
|
||||
gitMock.isCurrentBranchTracked.mockResolvedValue(false);
|
||||
|
||||
await processDocsChanges();
|
||||
|
||||
expect(gitMock.pushChanges).toHaveBeenCalledTimes(1);
|
||||
expect(gitMock.createPR).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('does push changes if branch is tracked and no new files are added', async () => {
|
||||
gitMock.getChanges.mockResolvedValue('M package.json\nM randomdoc.md');
|
||||
gitMock.isCurrentBranchTracked.mockResolvedValue(false);
|
||||
|
||||
await processDocsChanges();
|
||||
|
||||
expect(gitMock.pushChanges).toHaveBeenCalledTimes(1);
|
||||
expect(gitMock.createPR).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
/**
|
||||
* This script takes a version passed as a parameter (i.e. v15-x-y), the
|
||||
* current contents available under `/docs/latest`, and builds the project
|
||||
* in such a way that the documentation is made available under
|
||||
* `/docs/v15-x-y` or equivalent.
|
||||
*/
|
||||
|
||||
//@ts-check
|
||||
|
||||
const fs = require('fs-extra');
|
||||
const globby = require('globby');
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} version
|
||||
*/
|
||||
const moveDocs = async (version) => {
|
||||
await fs.move('docs/latest', `docs/${version}`);
|
||||
|
||||
const files = await globby([`docs/${version}/**/*.md`]);
|
||||
|
||||
for (const file of files) {
|
||||
const content = await fs.readFile(file, 'utf-8');
|
||||
let updatedContent = content.replace(/docs\/latest/gm, `docs/${version}`);
|
||||
updatedContent = content.replace(/latest\//gm, `${version}/`);
|
||||
await fs.writeFile(file, updatedContent, 'utf-8');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} version
|
||||
*/
|
||||
const updateConfigFiles = async (version) => {
|
||||
const configFiles = ['docusaurus.config.js', 'sidebars.js'];
|
||||
for (const configFile of configFiles) {
|
||||
const content = await fs.readFile(configFile, 'utf-8');
|
||||
const updatedContent = content.replace(/latest/g, version);
|
||||
|
||||
await fs.writeFile(configFile, updatedContent, 'utf-8');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} version
|
||||
*/
|
||||
const publishAsVersion = async (version) => {
|
||||
await moveDocs(version);
|
||||
await updateConfigFiles(version);
|
||||
};
|
||||
|
||||
// When a file is run directly from Node.js, `require.main` is set to its module.
|
||||
// That means that it is possible to determine whether a file has been run directly
|
||||
// by testing `require.main === module`.
|
||||
// https://nodejs.org/docs/latest/api/modules.html#modules_accessing_the_main_module
|
||||
if (require.main === module) {
|
||||
const version = process.argv[2];
|
||||
|
||||
if (!version) {
|
||||
console.error('Please provide a version');
|
||||
} else if (!version.match(/v\d+/)) {
|
||||
console.error('Version should be like "v12"');
|
||||
} else {
|
||||
publishAsVersion(version);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
publishAsVersion,
|
||||
};
|
|
@ -8,15 +8,12 @@ const {
|
|||
|
||||
const updateConfig = async (locale) => {
|
||||
const baseUrl = locale !== defaultLocale ? `/${locale}/` : '/';
|
||||
// Translations might not be completely in sync and we need to keep publishing
|
||||
const onBrokenLinks = locale !== defaultLocale ? `warn` : `throw`;
|
||||
const configPath = join(__dirname, '../docusaurus.config.js');
|
||||
|
||||
let docusaurusConfig = await fs.readFile(configPath, 'utf-8');
|
||||
|
||||
docusaurusConfig = docusaurusConfig
|
||||
.replace(/baseUrl: '.*?',/, `baseUrl: '${baseUrl}',`)
|
||||
.replace(/onBrokenLinks: '.*?',/, `onBrokenLinks: '${onBrokenLinks}',`);
|
||||
.replace(/baseUrl: '.*?',/, `baseUrl: '${baseUrl}',`);
|
||||
|
||||
await fs.writeFile(configPath, docusaurusConfig, 'utf-8');
|
||||
};
|
||||
|
|
|
@ -16,6 +16,7 @@ const { addFrontmatter } = require('./tasks/add-frontmatter');
|
|||
const { createSidebar } = require('./tasks/create-sidebar');
|
||||
const { fixContent } = require('./tasks/md-fixers');
|
||||
const { copyNewContent } = require('./tasks/copy-new-content');
|
||||
const { updateVersionsInfo } = require('./tasks/update-versions-info');
|
||||
const { sha } = require('../package.json');
|
||||
|
||||
const DOCS_FOLDER = path.join('docs', 'latest');
|
||||
|
@ -84,6 +85,9 @@ const start = async (source) => {
|
|||
|
||||
console.log('Updating sidebar.js');
|
||||
await createSidebar('docs', path.join(process.cwd(), 'sidebars.js'));
|
||||
|
||||
console.log('Updating docs versions');
|
||||
await updateVersionsInfo();
|
||||
};
|
||||
|
||||
start(process.argv[2]);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* Checks if there are any changes in the repo and creates or updates
|
||||
* a PR if needed. This is part of the `update-docs.yml` workflow and
|
||||
* depends on `update-pinned-version` and `prebuild` being run before
|
||||
* depends on `update-pinned-version` and `pre-build` being run before
|
||||
* in order to produce the right result.
|
||||
*/
|
||||
|
||||
|
@ -14,8 +14,13 @@ if (
|
|||
process.exit(1);
|
||||
}
|
||||
|
||||
const { execute } = require('./utils/execute');
|
||||
const { createPR, getChanges, pushChanges } = require('./utils/git-commands');
|
||||
const {
|
||||
createPR,
|
||||
getChanges,
|
||||
pushChanges,
|
||||
isCurrentBranchTracked,
|
||||
getCurrentBranchName,
|
||||
} = require('./utils/git-commands');
|
||||
|
||||
const HEAD = 'main';
|
||||
const PR_BRANCH = 'chore/docs-updates';
|
||||
|
@ -24,20 +29,7 @@ const EMAIL = 'electron@github.com';
|
|||
const NAME = 'electron-bot';
|
||||
|
||||
/**
|
||||
* Wraps a function on a try/catch and changes the exit code if it fails.
|
||||
* @param {Function} func
|
||||
*/
|
||||
const changeExitCodeIfException = async (func) => {
|
||||
try {
|
||||
await func();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if there are new document files by parsing the given
|
||||
* Checks if there are new document files (*.md) by parsing the given
|
||||
* `git status --porcelain` input.
|
||||
* This is done by looking at the status of each file:
|
||||
* - `A` means it is new and has been staged
|
||||
|
@ -49,35 +41,49 @@ const newDocFiles = (gitOutput) => {
|
|||
const lines = gitOutput.split('\n');
|
||||
const newFiles = lines.filter((line) => {
|
||||
const trimmedLine = line.trim();
|
||||
return trimmedLine.startsWith('U') || trimmedLine.startsWith('??');
|
||||
return (
|
||||
trimmedLine.endsWith('.md') &&
|
||||
(trimmedLine.startsWith('U') || trimmedLine.startsWith('??'))
|
||||
);
|
||||
});
|
||||
|
||||
return newFiles;
|
||||
};
|
||||
|
||||
/**
|
||||
* Analyzes the current `git status` of the local repo and branch to
|
||||
* see if there are new files or just modifications to existing ones.
|
||||
*
|
||||
* - If there is just modifications it pushes the changes directly to
|
||||
* the branch upstream.
|
||||
* - If there is new content it creates a new branch and opens a PR for
|
||||
* review. The format of the pr branch name is `chore/docs-updates` for `main`
|
||||
* and `chore/docs-updates-vXX-Y-X` for the ones targetting `vXX-Y-X`.
|
||||
* - Creates a new branch and pushes the changes directly if it does
|
||||
* not exist.
|
||||
*/
|
||||
const processDocsChanges = async () => {
|
||||
const output = await getChanges();
|
||||
const branchIsTracked = await isCurrentBranchTracked();
|
||||
const branchName = await getCurrentBranchName();
|
||||
|
||||
if (output === '') {
|
||||
console.log('Nothing updated, skipping');
|
||||
return;
|
||||
} else if (!/M\s+package\.json/.test(output)) {
|
||||
console.log('package.json is not modified, skipping');
|
||||
return;
|
||||
} else {
|
||||
console.log(`Uploading changes to Crowdin`);
|
||||
await execute(`yarn i18n:upload`);
|
||||
|
||||
const newFiles = newDocFiles(output);
|
||||
if (newFiles.length > 0) {
|
||||
const prBranchName =
|
||||
branchName === 'main' ? PR_BRANCH : `${PR_BRANCH}-${branchName}`;
|
||||
|
||||
if (newFiles.length > 0 && branchIsTracked) {
|
||||
console.log(`New documents available:
|
||||
${newFiles.join('\n')}`);
|
||||
await createPR(PR_BRANCH, HEAD, EMAIL, NAME, COMMIT_MESSAGE);
|
||||
await createPR(prBranchName, branchName, EMAIL, NAME, COMMIT_MESSAGE);
|
||||
} else {
|
||||
console.log(
|
||||
`Only existing content has been modified. Pushing changes directly.`
|
||||
);
|
||||
await pushChanges(HEAD, EMAIL, NAME, COMMIT_MESSAGE);
|
||||
await pushChanges(branchName, EMAIL, NAME, COMMIT_MESSAGE);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -87,7 +93,12 @@ ${newFiles.join('\n')}`);
|
|||
// by testing `require.main === module`.
|
||||
// https://nodejs.org/docs/latest/api/modules.html#modules_accessing_the_main_module
|
||||
if (require.main === module) {
|
||||
changeExitCodeIfException(processDocsChanges);
|
||||
process.addListener('unhandledRejection', (e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
processDocsChanges();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
//@ts-check
|
||||
const fs = require('fs').promises;
|
||||
|
||||
const {
|
||||
getRemoteBranches,
|
||||
getCurrentBranchName,
|
||||
} = require('../utils/git-commands');
|
||||
|
||||
const VERSIONS_INFO = 'versions-info.json';
|
||||
|
||||
const updateVersionsInfo = async () => {
|
||||
const branches = await getRemoteBranches();
|
||||
const versions = branches
|
||||
.map((branch) => branch.split('/').pop())
|
||||
.filter((branch) => /v\d+-x-y/.test(branch));
|
||||
|
||||
// We might be creating a new docs version branch
|
||||
const current = await getCurrentBranchName();
|
||||
if (!versions.includes(current)) {
|
||||
versions.push(current);
|
||||
}
|
||||
|
||||
const localVersions = JSON.parse(await fs.readFile(VERSIONS_INFO, 'utf-8'));
|
||||
|
||||
for (const version of versions) {
|
||||
let exists = false;
|
||||
for (const localVersion of localVersions) {
|
||||
console.log(localVersion);
|
||||
console.log(version);
|
||||
exists = exists || localVersion.label === version;
|
||||
}
|
||||
|
||||
if (!exists) {
|
||||
console.log(`New version ${version} found`);
|
||||
localVersions.push({
|
||||
label: version,
|
||||
href: `https://electronjs.org/docs/${version}`,
|
||||
target: '_blank',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await fs.writeFile(
|
||||
VERSIONS_INFO,
|
||||
JSON.stringify(localVersions, null, 2),
|
||||
'utf-8'
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
updateVersionsInfo: updateVersionsInfo,
|
||||
};
|
|
@ -2,7 +2,7 @@
|
|||
const executeMock = jest.createMockFromModule('../execute');
|
||||
jest.mock('../execute', () => executeMock);
|
||||
const octokitMock = {
|
||||
pulls: { list: jest.fn(), create: jest.fn() },
|
||||
pulls: { list: jest.fn(), create: jest.fn(), requestReviewers: jest.fn() },
|
||||
};
|
||||
const github = {
|
||||
getOctokit: () => {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
const github = require('@actions/github');
|
||||
const { execute } = require('./execute');
|
||||
const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
|
||||
const REVIEWERS = ['molant', 'erickzhao'];
|
||||
|
||||
/**
|
||||
* Creates a new commit with the current changes.
|
||||
|
@ -14,7 +15,7 @@ const createCommit = async (email, name, commitMessage) => {
|
|||
await execute(`git config --global user.email ${email}`);
|
||||
await execute(`git config --global user.name ${name}`);
|
||||
await execute(`git add .`);
|
||||
await execute(`git commit -am ${commitMessage}`);
|
||||
await execute(`git commit -m ${commitMessage} --no-verify`);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -27,7 +28,8 @@ const getChanges = async () => {
|
|||
};
|
||||
|
||||
/**
|
||||
* Creates a new commit and pushes the given branch
|
||||
* Creates a new commit and pushes the given branch, creating it
|
||||
* upstream if needed.
|
||||
* @param {string} branch
|
||||
* @param {string} email
|
||||
* @param {string} name
|
||||
|
@ -35,13 +37,21 @@ const getChanges = async () => {
|
|||
*/
|
||||
const pushChanges = async (branch, email, name, message) => {
|
||||
await createCommit(email, name, message);
|
||||
await execute(`git pull --rebase`);
|
||||
await execute(`git push origin ${branch} --follow-tags`);
|
||||
if (await isCurrentBranchTracked()) {
|
||||
await execute(`git pull --rebase`);
|
||||
await execute(`git push origin ${branch} --follow-tags`);
|
||||
} else {
|
||||
await execute(`git push --set-upstream origin ${branch}`);
|
||||
|
||||
// HACK: This way GitHub actions for `pushes` on new branches are picked up
|
||||
await execute(`git push --force`);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Force pushes the changes to the documentation update branch
|
||||
* and creates a new PR if there is none available.
|
||||
* and creates a new PR if there is none available with review
|
||||
* request for `REVIEWERS`.
|
||||
* @param {string} branch
|
||||
* @param {string} base
|
||||
* @param {string} email
|
||||
|
@ -50,7 +60,11 @@ const pushChanges = async (branch, email, name, message) => {
|
|||
*/
|
||||
const createPR = async (branch, base, email, name, message) => {
|
||||
await createCommit(email, name, message);
|
||||
await execute(`git checkout -b ${branch}`);
|
||||
|
||||
if (getCurrentBranchName() !== branch) {
|
||||
await execute(`git checkout -b ${branch}`);
|
||||
}
|
||||
|
||||
await execute(`git push --force --set-upstream origin ${branch}`);
|
||||
|
||||
console.log(`Changes pushed to ${branch}`);
|
||||
|
@ -81,11 +95,86 @@ const createPR = async (branch, base, email, name, message) => {
|
|||
});
|
||||
|
||||
console.log(`PR created (#${result.data.id})`);
|
||||
|
||||
await octokit.pulls.requestReviewers({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: result.data.id,
|
||||
reviewers: REVIEWERS,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an array with the remote branches.
|
||||
* @returns {Promise<string[]>}
|
||||
*/
|
||||
const getRemoteBranches = async () => {
|
||||
const { stdout } = await execute(`git branch -r`);
|
||||
|
||||
/**
|
||||
* The output of the command above is similar to
|
||||
*
|
||||
* ```
|
||||
* origin/HEAD -> origin/main
|
||||
* origin/v15-x-y
|
||||
* origin/v14-x-y
|
||||
* origin/feat/i18n
|
||||
* ```
|
||||
*
|
||||
* We do not need `HEAD` so we filter it out
|
||||
*/
|
||||
const branches = stdout
|
||||
.split('\n')
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => !line.includes('->'));
|
||||
|
||||
return branches;
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines if the active branch is current tracked in the
|
||||
* remote by calling `git branch -vv` and parsing the output.
|
||||
*
|
||||
* The output has the following form:
|
||||
*
|
||||
* ```plain
|
||||
* bots 3527526ae110 fix: docs automerge
|
||||
* * versioned-docs 0fe736ed2529 [origin/versioned-docs] feat: versioned docs
|
||||
* ```
|
||||
*
|
||||
* In the case above the branch `bots` is not tracked (no
|
||||
* `[origin/]` information) and is not the active branch (no `*`).
|
||||
*
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
const isCurrentBranchTracked = async () => {
|
||||
const { stdout } = await execute(`git branch -vv`);
|
||||
|
||||
const lines = stdout.trim().split('\n');
|
||||
const current = lines.filter((line) => line.trim().startsWith('*'));
|
||||
|
||||
if (!current) {
|
||||
throw new Error(`Couldn't determine current branch`);
|
||||
}
|
||||
|
||||
return current.includes(`[origin/`);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the name of the current branch
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
const getCurrentBranchName = async () => {
|
||||
const { stdout } = await execute(`git branch --show-current`);
|
||||
return stdout;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
createPR,
|
||||
getChanges,
|
||||
getCurrentBranchName,
|
||||
getRemoteBranches,
|
||||
isCurrentBranchTracked,
|
||||
pushChanges,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
[
|
||||
{
|
||||
"label": "Current",
|
||||
"href": "/docs/latest",
|
||||
"target": "_self"
|
||||
}
|
||||
]
|
Загрузка…
Ссылка в новой задаче