build: update content when upstream changes
This commit adds support to respond to `repository_dispatch` events coming from `electron/electron-website-updater` that contain the SHA of the new commit in `electron/electron` to pin to in `package.json`. More information about the architecture can be found in #19. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Fix #19 Close #23
This commit is contained in:
Родитель
ac21aa7f3b
Коммит
11a0f9d3f1
|
@ -0,0 +1 @@
|
|||
GITHUB_TOKEN=
|
|
@ -10,7 +10,7 @@ on:
|
|||
|
||||
jobs:
|
||||
tests:
|
||||
name: Install & test
|
||||
name: Test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
name: 'Update docs'
|
||||
|
||||
on:
|
||||
repository_dispatch:
|
||||
types: [doc_changes]
|
||||
|
||||
jobs:
|
||||
update-docs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
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 prebuild'
|
||||
- name: 'Create PR'
|
||||
run: 'yarn process-docs-changes'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@ -1,9 +1,9 @@
|
|||
node_modules
|
||||
.docusaurus
|
||||
.DS_Store
|
||||
.env
|
||||
.vscode/settings.json
|
||||
build/
|
||||
content/
|
||||
docs/
|
||||
blog/
|
||||
package-lock.json
|
||||
blog/
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
// See http://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
|
||||
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
|
||||
|
||||
// List of extensions which should be recommended for users of this workspace.
|
||||
"recommendations": ["esbenp.prettier-vscode", "orta.vscode-jest"],
|
||||
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
|
||||
"unwantedRecommendations": []
|
||||
}
|
|
@ -4,6 +4,22 @@
|
|||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"name": "vscode-jest-tests",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/node_modules/.bin/jest",
|
||||
"args": [
|
||||
"--runInBand"
|
||||
],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "neverOpen",
|
||||
"disableOptimisticBPs": true,
|
||||
"windows": {
|
||||
"program": "${workspaceFolder}/node_modules/jest/bin/jest"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "pwa-node",
|
||||
"request": "launch",
|
||||
|
@ -21,6 +37,6 @@
|
|||
"program": "${workspaceFolder}/create-electron-documentation/index.js",
|
||||
"console": "integratedTerminal",
|
||||
"cwd": "${workspaceFolder}/docs/how-to"
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
31
README.md
31
README.md
|
@ -44,10 +44,33 @@ yarn start
|
|||
|
||||
`yarn start` starts a local development server and open up a browser window. Most changes are reflected live without having to restart the server.
|
||||
|
||||
## Build
|
||||
# Repository content organization
|
||||
|
||||
```console
|
||||
yarn build
|
||||
This repository contains the code for 2 related things:
|
||||
|
||||
- The code to generate the contents of https://beta.electronjs.org
|
||||
- [`create-electron-documentation`][ced] package
|
||||
|
||||
The content of this repository is organized as follows:
|
||||
|
||||
```
|
||||
└─ root
|
||||
|
|
||||
├─ .github/workflows → The definitions for the GitHub actions
|
||||
|
|
||||
|
|
||||
├─ create-electron-documentation → Code for the npm package
|
||||
| of the same name. Read the readme in the folder
|
||||
| for more information.
|
||||
|
|
||||
├─ scripts → The code for the package.json tasks and GitHub
|
||||
| actions
|
||||
|
|
||||
├─ spec → Tests for the scripts
|
||||
|
|
||||
├─ src → Docusaurus code
|
||||
|
|
||||
├─ static → Docusaurus static assets
|
||||
```
|
||||
|
||||
This command generates static content into the `build` directory and can be served using any static contents hosting service.
|
||||
[ced]: https://npmjs.com/package/create-electron-documentation
|
||||
|
|
|
@ -0,0 +1,195 @@
|
|||
/*
|
||||
* For a detailed explanation regarding each configuration property, visit:
|
||||
* https://jestjs.io/docs/en/configuration.html
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
// All imported modules in your tests should be mocked automatically
|
||||
// automock: false,
|
||||
|
||||
// Stop running tests after `n` failures
|
||||
// bail: 0,
|
||||
|
||||
// The directory where Jest should store its cached dependency information
|
||||
// cacheDirectory: "/tmp/jest_rs",
|
||||
|
||||
// Automatically clear mock calls and instances between every test
|
||||
clearMocks: true,
|
||||
|
||||
// Indicates whether the coverage information should be collected while executing the test
|
||||
// collectCoverage: false,
|
||||
|
||||
// An array of glob patterns indicating a set of files for which coverage information should be collected
|
||||
// collectCoverageFrom: undefined,
|
||||
|
||||
// The directory where Jest should output its coverage files
|
||||
// coverageDirectory: undefined,
|
||||
|
||||
// An array of regexp pattern strings used to skip coverage collection
|
||||
// coveragePathIgnorePatterns: [
|
||||
// "/node_modules/"
|
||||
// ],
|
||||
|
||||
// Indicates which provider should be used to instrument code for coverage
|
||||
coverageProvider: 'v8',
|
||||
|
||||
// A list of reporter names that Jest uses when writing coverage reports
|
||||
// coverageReporters: [
|
||||
// "json",
|
||||
// "text",
|
||||
// "lcov",
|
||||
// "clover"
|
||||
// ],
|
||||
|
||||
// An object that configures minimum threshold enforcement for coverage results
|
||||
// coverageThreshold: undefined,
|
||||
|
||||
// A path to a custom dependency extractor
|
||||
// dependencyExtractor: undefined,
|
||||
|
||||
// Make calling deprecated APIs throw helpful error messages
|
||||
// errorOnDeprecated: false,
|
||||
|
||||
// Force coverage collection from ignored files using an array of glob patterns
|
||||
// forceCoverageMatch: [],
|
||||
|
||||
// A path to a module which exports an async function that is triggered once before all test suites
|
||||
// globalSetup: undefined,
|
||||
|
||||
// A path to a module which exports an async function that is triggered once after all test suites
|
||||
// globalTeardown: undefined,
|
||||
|
||||
// A set of global variables that need to be available in all test environments
|
||||
// globals: {},
|
||||
|
||||
// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
|
||||
// maxWorkers: "50%",
|
||||
|
||||
// An array of directory names to be searched recursively up from the requiring module's location
|
||||
// moduleDirectories: [
|
||||
// "node_modules"
|
||||
// ],
|
||||
|
||||
// An array of file extensions your modules use
|
||||
// moduleFileExtensions: [
|
||||
// "js",
|
||||
// "json",
|
||||
// "jsx",
|
||||
// "ts",
|
||||
// "tsx",
|
||||
// "node"
|
||||
// ],
|
||||
|
||||
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
|
||||
// moduleNameMapper: {},
|
||||
|
||||
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
|
||||
// modulePathIgnorePatterns: [],
|
||||
|
||||
// Activates notifications for test results
|
||||
// notify: false,
|
||||
|
||||
// An enum that specifies notification mode. Requires { notify: true }
|
||||
// notifyMode: "failure-change",
|
||||
|
||||
// A preset that is used as a base for Jest's configuration
|
||||
// preset: undefined,
|
||||
|
||||
// Run tests from one or more projects
|
||||
// projects: undefined,
|
||||
|
||||
// Use this configuration option to add custom reporters to Jest
|
||||
// reporters: undefined,
|
||||
|
||||
// Automatically reset mock state between every test
|
||||
// resetMocks: false,
|
||||
|
||||
// Reset the module registry before running each individual test
|
||||
// resetModules: false,
|
||||
|
||||
// A path to a custom resolver
|
||||
// resolver: undefined,
|
||||
|
||||
// Automatically restore mock state between every test
|
||||
// restoreMocks: false,
|
||||
|
||||
// The root directory that Jest should scan for tests and modules within
|
||||
// rootDir: undefined,
|
||||
|
||||
// A list of paths to directories that Jest should use to search for files in
|
||||
// roots: [
|
||||
// "<rootDir>"
|
||||
// ],
|
||||
|
||||
// Allows you to use a custom runner instead of Jest's default test runner
|
||||
// runner: "jest-runner",
|
||||
|
||||
// The paths to modules that run some code to configure or set up the testing environment before each test
|
||||
// setupFiles: [],
|
||||
|
||||
// A list of paths to modules that run some code to configure or set up the testing framework before each test
|
||||
// setupFilesAfterEnv: [],
|
||||
|
||||
silent: true,
|
||||
|
||||
// The number of seconds after which a test is considered as slow and reported as such in the results.
|
||||
// slowTestThreshold: 5,
|
||||
|
||||
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
|
||||
// snapshotSerializers: [],
|
||||
|
||||
// The test environment that will be used for testing
|
||||
testEnvironment: 'node',
|
||||
|
||||
// Options that will be passed to the testEnvironment
|
||||
// testEnvironmentOptions: {},
|
||||
|
||||
// Adds a location field to test results
|
||||
// testLocationInResults: false,
|
||||
|
||||
// The glob patterns Jest uses to detect test files
|
||||
// testMatch: [
|
||||
// "**/__tests__/**/*.[jt]s?(x)",
|
||||
// "**/?(*.)+(spec|test).[tj]s?(x)"
|
||||
// ],
|
||||
|
||||
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
|
||||
// testPathIgnorePatterns: [
|
||||
// "/node_modules/"
|
||||
// ],
|
||||
|
||||
testTimeout: 50000,
|
||||
|
||||
// The regexp pattern or array of patterns that Jest uses to detect test files
|
||||
// testRegex: [],
|
||||
|
||||
// This option allows the use of a custom results processor
|
||||
// testResultsProcessor: undefined,
|
||||
|
||||
// This option allows use of a custom test runner
|
||||
// testRunner: "jasmine2",
|
||||
|
||||
// This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
|
||||
// testURL: "http://localhost",
|
||||
|
||||
// Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
|
||||
// timers: "real",
|
||||
|
||||
// A map from regular expressions to paths to transformers
|
||||
// transform: undefined,
|
||||
|
||||
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
|
||||
transformIgnorePatterns: ['/node_modules/', '.js$'],
|
||||
|
||||
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
|
||||
// unmockedModulePathPatterns: undefined,
|
||||
|
||||
// Indicates whether each individual test should be reported during the run
|
||||
// verbose: undefined,
|
||||
|
||||
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
|
||||
// watchPathIgnorePatterns: [],
|
||||
|
||||
// Whether to use watchman for file crawling
|
||||
// watchman: true,
|
||||
};
|
16
package.json
16
package.json
|
@ -12,14 +12,19 @@
|
|||
"serve": "docusaurus serve",
|
||||
"write-translations": "docusaurus write-translations",
|
||||
"write-heading-ids": "docusaurus write-heading-ids",
|
||||
"test": "prettier -c ./scripts/",
|
||||
"prebuild": "node ./scripts/pre-build.js"
|
||||
"lint": "prettier -c ./scripts/**/*.js",
|
||||
"test": "yarn lint && jest",
|
||||
"prebuild": "node ./scripts/pre-build.js",
|
||||
"process-docs-changes": "node ./scripts/process-docs-changes.js",
|
||||
"update-pinned-version": "node ./scripts/update-pinned-version.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "^2.0.0-beta.0",
|
||||
"@docusaurus/preset-classic": "^2.0.0-beta.0",
|
||||
"@mdx-js/react": "^1.6.21",
|
||||
"clsx": "^1.1.1",
|
||||
"dotenv-safe": "^8.2.0",
|
||||
"execa": "^5.0.0",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1"
|
||||
},
|
||||
|
@ -36,17 +41,22 @@
|
|||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@actions/core": "^1.2.7",
|
||||
"@actions/github": "^4.0.0",
|
||||
"@types/jest": "^26.0.23",
|
||||
"@types/unist": "^2.0.3",
|
||||
"del": "^6.0.0",
|
||||
"fs-extra": "^9.1.0",
|
||||
"globby": "^11.0.3",
|
||||
"got": "^11.8.2",
|
||||
"gunzip-maybe": "^1.4.2",
|
||||
"jest": "^26.6.3",
|
||||
"json5": "^2.2.0",
|
||||
"latest-version": "^5.1.0",
|
||||
"make-dir": "^3.1.0",
|
||||
"prettier": "^2.2.1",
|
||||
"tar-stream": "^2.2.0",
|
||||
"unist-util-visit-parents": "^3.1.1"
|
||||
}
|
||||
},
|
||||
"sha": "eb5ce11b415a956182c59465980204ba695fd4ee"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
const gitMock = jest.createMockFromModule('../utils/git-commands');
|
||||
jest.mock('../utils/git-commands', () => gitMock);
|
||||
// Make sure we do not run any real git commands by accident
|
||||
const executeMock = jest.createMockFromModule('../utils/execute');
|
||||
jest.mock('../utils/execute', () => executeMock);
|
||||
|
||||
const { processDocsChanges } = require('../process-docs-changes');
|
||||
|
||||
describe('process-docs-changes', () => {
|
||||
it('does not create any PR if there are no changes', async () => {
|
||||
gitMock.getChanges.mockResolvedValue('');
|
||||
|
||||
await processDocsChanges();
|
||||
|
||||
expect(gitMock.createPR).toHaveBeenCalledTimes(0);
|
||||
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');
|
||||
|
||||
await processDocsChanges();
|
||||
|
||||
expect(gitMock.createPR).toHaveBeenCalledTimes(0);
|
||||
expect(gitMock.pushChanges).toHaveBeenCalledTimes(1);
|
||||
expect(gitMock.pushChanges).toHaveBeenCalledWith(
|
||||
'main',
|
||||
'electron@github.com',
|
||||
'electron-bot',
|
||||
'"chore: update ref to docs (🤖)"'
|
||||
);
|
||||
});
|
||||
|
||||
it('does create a PR if more files than package.json are modified', async () => {
|
||||
gitMock.getChanges.mockResolvedValue('M package.json\nM sidebars.json');
|
||||
|
||||
await processDocsChanges();
|
||||
|
||||
expect(gitMock.pushChanges).toHaveBeenCalledTimes(0);
|
||||
expect(gitMock.createPR).toHaveBeenCalledTimes(1);
|
||||
expect(gitMock.createPR).toHaveBeenCalledWith(
|
||||
'chore/docs-updates',
|
||||
'main',
|
||||
'electron@github.com',
|
||||
'electron-bot',
|
||||
'"chore: update ref to docs (🤖)"'
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,35 @@
|
|||
const fs = require('fs');
|
||||
const mock = {
|
||||
promises: {
|
||||
readFile: jest.fn(),
|
||||
writeFile: jest.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
jest.mock('fs', () => {
|
||||
return mock;
|
||||
});
|
||||
|
||||
const { updateSha } = require('../update-pinned-version');
|
||||
|
||||
describe('update-pinned-version', () => {
|
||||
it('updates package.json with the given sha', async () => {
|
||||
const sha = 'new-sha';
|
||||
const expected = {
|
||||
sha,
|
||||
};
|
||||
const packageJson = {
|
||||
sha: 'old-sha',
|
||||
};
|
||||
mock.promises.readFile.mockResolvedValue(JSON.stringify(packageJson));
|
||||
|
||||
await updateSha(sha);
|
||||
|
||||
expect(mock.promises.writeFile).toBeCalledTimes(1);
|
||||
expect(mock.promises.writeFile).toBeCalledWith(
|
||||
expect.any(String),
|
||||
`${JSON.stringify(expected, null, 2)}\n`,
|
||||
'utf-8'
|
||||
);
|
||||
});
|
||||
});
|
|
@ -16,37 +16,46 @@ 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 { sha } = require('../package.json');
|
||||
|
||||
const DOCS_FOLDER = 'docs';
|
||||
// const BLOG_FOLDER = 'blog';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} localElectron
|
||||
* @param {string} source
|
||||
*/
|
||||
const start = async (localElectron) => {
|
||||
console.log(`Detecting latest Electron version`);
|
||||
const version = await latestVersion('electron');
|
||||
const stableBranch = version.replace(/\.\d+\.\d+/, '-x-y');
|
||||
console.log(`Latest version: ${version}`);
|
||||
console.log(`Stable branch: ${stableBranch}`);
|
||||
|
||||
const start = async (source) => {
|
||||
console.log(`Deleting previous content`);
|
||||
await del(DOCS_FOLDER);
|
||||
|
||||
const localElectron =
|
||||
source && (source.includes('/') || source.includes('\\'));
|
||||
|
||||
// TODO: Uncomment once we have the blog up and running
|
||||
// await del(BLOG_FOLDER);
|
||||
|
||||
if (!localElectron) {
|
||||
console.log(`Downloading docs from branch "${stableBranch}"`);
|
||||
console.log(`Detecting latest Electron version`);
|
||||
const version = await latestVersion('electron');
|
||||
const stableBranch = version.replace(/\.\d+\.\d+/, '-x-y');
|
||||
console.log(`Latest version: ${version}`);
|
||||
console.log(`Stable branch: ${stableBranch}`);
|
||||
console.log(`Specified SHA: ${sha}`);
|
||||
|
||||
const target = source || sha || stableBranch;
|
||||
|
||||
console.log(`Downloading docs using "${target}"`);
|
||||
await download({
|
||||
target: stableBranch,
|
||||
target,
|
||||
org: process.env.ORG || 'electron',
|
||||
repository: 'electron',
|
||||
destination: DOCS_FOLDER,
|
||||
downloadMatch: 'docs',
|
||||
});
|
||||
} else if (existsSync(localElectron)) {
|
||||
} else if (existsSync(source)) {
|
||||
await copy({
|
||||
target: localElectron,
|
||||
target: source,
|
||||
destination: DOCS_FOLDER,
|
||||
downloadMatch: 'docs',
|
||||
});
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
/**
|
||||
* 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
|
||||
* in order to produce the right result.
|
||||
*/
|
||||
|
||||
//@ts-check
|
||||
if (!(process.env.CI || process.env.NODE_ENV === 'test') && !GITHUB_TOKEN) {
|
||||
console.error('Missing GITHUB_TOKEN environment variable');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const { createPR, getChanges, pushChanges } = require('./utils/git-commands');
|
||||
|
||||
const HEAD = 'main';
|
||||
const PR_BRANCH = 'chore/docs-updates';
|
||||
const COMMIT_MESSAGE = '"chore: update ref to docs (🤖)"';
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
const processDocsChanges = async () => {
|
||||
const output = await getChanges();
|
||||
|
||||
if (output === '') {
|
||||
console.log('Nothing updated, skipping');
|
||||
return;
|
||||
} else if (!output.includes('M package.json')) {
|
||||
console.log('package.json is not modified, skipping');
|
||||
return;
|
||||
} else {
|
||||
const lines = output.split('\n');
|
||||
if (lines.length > 1) {
|
||||
console.log(`New documents available, creating PR.`);
|
||||
await createPR(PR_BRANCH, HEAD, EMAIL, NAME, COMMIT_MESSAGE);
|
||||
} else {
|
||||
console.log(
|
||||
`Only existing content has been modified. Pushing changes directly.`
|
||||
);
|
||||
await pushChanges(HEAD, EMAIL, NAME, COMMIT_MESSAGE);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 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) {
|
||||
changeExitCodeIfException(processDocsChanges);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
processDocsChanges,
|
||||
};
|
|
@ -13,7 +13,8 @@ const fixedFolders = ['api', 'images', 'fiddles'];
|
|||
/**
|
||||
* @typedef DownloadOptions
|
||||
* @type {object}
|
||||
* @property {string} [repository] - The repository in the electron org to download the contents from
|
||||
* @property {string} [org] - The organization to download the contents from
|
||||
* @property {string} [repository] - The repository to download the contents from
|
||||
* @property {string} destination - The destination absolute path.
|
||||
* @property {string} target - The branch, commit, version. (e.g. `v1.0.0`, `master`)
|
||||
* @property {string} downloadMatch - The math to use to filter the downloaded contents
|
||||
|
@ -88,9 +89,9 @@ const saveContents = async (files, destination) => {
|
|||
* @param {DownloadOptions} options
|
||||
*/
|
||||
const downloadFromGitHub = async (options) => {
|
||||
const { repository, target, downloadMatch = '' } = options;
|
||||
const { org, repository, target, downloadMatch = '' } = options;
|
||||
|
||||
const tarballUrl = `https://github.com/electron/${repository}/archive/${target}.tar.gz`;
|
||||
const tarballUrl = `https://github.com/${org}/${repository}/archive/${target}.tar.gz`;
|
||||
|
||||
const contents = [];
|
||||
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
//@ts-check
|
||||
|
||||
const fs = require('fs').promises;
|
||||
const path = require('path');
|
||||
const packageJsonPath = path.join(__dirname, '..', 'package.json');
|
||||
|
||||
/**
|
||||
* Updates the field `sha` of the `package.json` with the value passed
|
||||
* via parameter in the CLI.
|
||||
* @param {string} sha
|
||||
*/
|
||||
const updateSha = async (sha) => {
|
||||
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'));
|
||||
const oldSha = packageJson.sha;
|
||||
console.log(`New SHA: ${sha}`);
|
||||
console.log(`Old SHA: ${oldSha}`);
|
||||
|
||||
if (sha === oldSha) {
|
||||
console.log(`Nothing to update`);
|
||||
return;
|
||||
}
|
||||
|
||||
packageJson.sha = sha;
|
||||
|
||||
await fs.writeFile(
|
||||
packageJsonPath,
|
||||
`${JSON.stringify(packageJson, null, 2)}\n`,
|
||||
'utf-8'
|
||||
);
|
||||
|
||||
console.log(`SHA updated`);
|
||||
};
|
||||
|
||||
// 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 sha = process.argv[2];
|
||||
|
||||
if (!sha) {
|
||||
console.error('Please provide an SHA value as follows:');
|
||||
console.error('yarn update-pinned-version SHA');
|
||||
} else {
|
||||
updateSha(sha);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
updateSha,
|
||||
};
|
|
@ -0,0 +1,52 @@
|
|||
// Make sure we do not run any real git commands by accident
|
||||
const executeMock = jest.createMockFromModule('../execute');
|
||||
jest.mock('../execute', () => executeMock);
|
||||
const octokitMock = {
|
||||
pulls: { list: jest.fn(), create: jest.fn() },
|
||||
};
|
||||
const github = {
|
||||
getOctokit: () => {
|
||||
return octokitMock;
|
||||
},
|
||||
context: {
|
||||
repo: {
|
||||
repo: 'mock-repo',
|
||||
owner: 'mock-owner',
|
||||
},
|
||||
},
|
||||
};
|
||||
jest.mock('@actions/github', () => github);
|
||||
|
||||
const { createPR } = require('../git-commands.js');
|
||||
|
||||
describe('git-commands', () => {
|
||||
it('creates a new PR if there are no PRs opened', async () => {
|
||||
executeMock.execute.mockResolvedValue();
|
||||
octokitMock.pulls.list.mockResolvedValue({ data: [] });
|
||||
octokitMock.pulls.create.mockResolvedValue({ data: { id: 42 } });
|
||||
|
||||
await createPR('mock-branch', 'mock-base', 'chore: mock mesage');
|
||||
|
||||
expect(octokitMock.pulls.list).toBeCalledTimes(1);
|
||||
expect(octokitMock.pulls.create).toBeCalledTimes(1);
|
||||
expect(octokitMock.pulls.create).toBeCalledWith({
|
||||
owner: github.context.repo.owner,
|
||||
repo: github.context.repo.repo,
|
||||
base: 'mock-base',
|
||||
head: 'mock-branch',
|
||||
});
|
||||
});
|
||||
|
||||
it('force pushes to the branch if there is already a PR', async () => {
|
||||
executeMock.execute.mockResolvedValue();
|
||||
octokitMock.pulls.list.mockResolvedValue({
|
||||
data: [{ head: { ref: 'mock-branch' } }],
|
||||
});
|
||||
octokitMock.pulls.create.mockResolvedValue({ data: { id: 42 } });
|
||||
|
||||
await createPR('mock-branch', 'mock-base', 'chore: mock mesage');
|
||||
|
||||
expect(octokitMock.pulls.list).toBeCalledTimes(1);
|
||||
expect(octokitMock.pulls.create).toBeCalledTimes(0);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,82 @@
|
|||
const path = require('path');
|
||||
const execa = require('execa');
|
||||
|
||||
/**
|
||||
* Groups all string arguments into a single one. E.g.:
|
||||
* ```js
|
||||
* ['-m', '"Upgrade:', 'to', 'latest', 'version"'] --> ['-m', '"Upgrade: to latest version"']`
|
||||
* ```
|
||||
*
|
||||
* Original code writeng by @molant for webhint/hint.
|
||||
* Distributed under an Apache-2.0 License
|
||||
*
|
||||
* https://github.com/webhintio/hint/blob/606fee86054a54aa55a598fdc6d86400d1269851/release/lib/utils.ts#L78
|
||||
* @param {string[]} args The arguments
|
||||
*/
|
||||
const groupArgs = (args) => {
|
||||
let isStringArgument = false;
|
||||
const newArgs = args.reduce((acum, current) => {
|
||||
if (isStringArgument) {
|
||||
const last = acum[acum.length - 1];
|
||||
|
||||
acum[acum.length - 1] = `${last} ${current}`.replace(/"/g, '');
|
||||
|
||||
if (current.endsWith('"')) {
|
||||
isStringArgument = false;
|
||||
}
|
||||
|
||||
return acum;
|
||||
}
|
||||
|
||||
if (current.startsWith('"')) {
|
||||
/**
|
||||
* Argument is split. I.e.: `['"part1', 'part2"'];`
|
||||
*/
|
||||
if (!current.endsWith('"')) {
|
||||
isStringArgument = true;
|
||||
|
||||
acum.push(current);
|
||||
|
||||
return acum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Argument is surrounded by "" that need to be removed.
|
||||
* We just remove all the quotes because we don't escape any in our commands
|
||||
*/
|
||||
acum.push(current.replace(/"/g, ''));
|
||||
|
||||
return acum;
|
||||
}
|
||||
|
||||
acum.push(current);
|
||||
|
||||
return acum;
|
||||
}, []);
|
||||
|
||||
return newArgs;
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes the given `command` using `execa` and doing the
|
||||
* required transformations (e.g.: splitting and grouping arguments).
|
||||
* @param {string} command
|
||||
* @param {execa.Options} [options]
|
||||
* @returns
|
||||
*/
|
||||
const execute = (command, options) => {
|
||||
console.log(
|
||||
`${options && options.cwd ? options.cwd : process.cwd()}${
|
||||
path.sep
|
||||
}${command}`
|
||||
);
|
||||
|
||||
const args = command.split(' ');
|
||||
const program = args.shift();
|
||||
|
||||
return execa(program, groupArgs(args), options);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
execute,
|
||||
};
|
|
@ -0,0 +1,91 @@
|
|||
const github = require('@actions/github');
|
||||
const { execute } = require('./execute');
|
||||
const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
|
||||
|
||||
/**
|
||||
* Creates a new commit with the current changes.
|
||||
* @param {string} email
|
||||
* @param {string} name
|
||||
* @param {string} commitMessage
|
||||
*/
|
||||
const createCommit = async (email, name, commitMessage) => {
|
||||
await execute('git remote -vv');
|
||||
await execute('git status');
|
||||
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}`);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the current modified files in the repo.
|
||||
*/
|
||||
const getChanges = async () => {
|
||||
const { stdout } = await execute('git status --porcelain');
|
||||
|
||||
return stdout.trim();
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new commit and pushes the given branch
|
||||
* @param {string} branch
|
||||
* @param {string} email
|
||||
* @param {string} name
|
||||
* @param {string} message
|
||||
*/
|
||||
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`);
|
||||
};
|
||||
|
||||
/**
|
||||
* Force pushes the changes to the documentation update branch
|
||||
* and creates a new PR if there is none available.
|
||||
* @param {string} branch
|
||||
* @param {string} base
|
||||
* @param {string} email
|
||||
* @param {string} name
|
||||
* @param {string} message
|
||||
*/
|
||||
const createPR = async (branch, base, email, name, message) => {
|
||||
await createCommit(email, name, message);
|
||||
await execute(`git checkout -b ${branch}`);
|
||||
await execute(`git push --force --set-upstream origin ${branch}`);
|
||||
|
||||
console.log(`Changes pushed to ${branch}`);
|
||||
|
||||
const { context } = github;
|
||||
const octokit = github.getOctokit(GITHUB_TOKEN);
|
||||
|
||||
const pulls = await octokit.pulls.list({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
state: 'open',
|
||||
});
|
||||
|
||||
const doesExist = pulls.data.some((pull) => {
|
||||
return pull.head.ref === branch;
|
||||
});
|
||||
|
||||
if (doesExist) {
|
||||
console.log('PR already exists, nothing to do');
|
||||
} else {
|
||||
console.log('Creating PR');
|
||||
const result = await octokit.pulls.create({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
title: message,
|
||||
base,
|
||||
head: branch,
|
||||
});
|
||||
|
||||
console.log(`PR created (#${result.data.id})`);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
createPR,
|
||||
getChanges,
|
||||
pushChanges,
|
||||
};
|
2136
yarn.lock
2136
yarn.lock
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Загрузка…
Ссылка в новой задаче