Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
a3f3082bf0 | |||
0570c240e4 | |||
398118f708 | |||
dcc3f6d74d | |||
0af95332e9 | |||
f9a76a7d06 | |||
832a54ee42 | |||
e8f4294e7f | |||
ce448f4341 | |||
847eaa0fa3 |
10
CHANGELOG.md
10
CHANGELOG.md
@ -1,3 +1,13 @@
|
||||
<a name="10.0.11"></a>
|
||||
## 10.0.11 (2020-08-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **router:** ensure routerLinkActive updates when associated routerLinks change (resubmit of [#38349](https://github.com/angular/angular/issues/38349)) ([#38511](https://github.com/angular/angular/issues/38511)) ([0af9533](https://github.com/angular/angular/commit/0af9533)), closes [#18469](https://github.com/angular/angular/issues/18469)
|
||||
|
||||
|
||||
|
||||
<a name="10.0.10"></a>
|
||||
## 10.0.10 (2020-08-17)
|
||||
|
||||
|
@ -208,7 +208,7 @@ about the event and gives that data to the parent.
|
||||
The child's template has two controls. The first is an HTML `<input>` with a
|
||||
[template reference variable](guide/template-reference-variables) , `#newItem`,
|
||||
where the user types in an item name. Whatever the user types
|
||||
into the `<input>` gets stored in the `#newItem` variable.
|
||||
into the `<input>` gets stored in the `value` property of the `#newItem` variable.
|
||||
|
||||
<code-example path="inputs-outputs/src/app/item-output/item-output.component.html" region="child-output" header="src/app/item-output/item-output.component.html"></code-example>
|
||||
|
||||
@ -218,7 +218,7 @@ an event binding because the part to the left of the equal
|
||||
sign is in parentheses, `(click)`.
|
||||
|
||||
The `(click)` event is bound to the `addNewItem()` method in the child component class which
|
||||
takes as its argument whatever the value of `#newItem` is.
|
||||
takes as its argument whatever the value of `#newItem.value` property is.
|
||||
|
||||
Now the child component has an `@Output()`
|
||||
for sending data to the parent and a method for raising an event.
|
||||
|
@ -4,6 +4,7 @@ load("@npm_bazel_typescript//:index.bzl", "ts_library")
|
||||
ts_library(
|
||||
name = "commit-message",
|
||||
srcs = [
|
||||
"builder.ts",
|
||||
"cli.ts",
|
||||
"commit-message-draft.ts",
|
||||
"config.ts",
|
||||
@ -12,14 +13,17 @@ ts_library(
|
||||
"validate.ts",
|
||||
"validate-file.ts",
|
||||
"validate-range.ts",
|
||||
"wizard.ts",
|
||||
],
|
||||
module_name = "@angular/dev-infra-private/commit-message",
|
||||
visibility = ["//dev-infra:__subpackages__"],
|
||||
deps = [
|
||||
"//dev-infra/utils",
|
||||
"@npm//@types/inquirer",
|
||||
"@npm//@types/node",
|
||||
"@npm//@types/shelljs",
|
||||
"@npm//@types/yargs",
|
||||
"@npm//inquirer",
|
||||
"@npm//shelljs",
|
||||
"@npm//yargs",
|
||||
],
|
||||
@ -29,6 +33,7 @@ ts_library(
|
||||
name = "test_lib",
|
||||
testonly = True,
|
||||
srcs = [
|
||||
"builder.spec.ts",
|
||||
"parse.spec.ts",
|
||||
"validate.spec.ts",
|
||||
],
|
||||
|
46
dev-infra/commit-message/builder.spec.ts
Normal file
46
dev-infra/commit-message/builder.spec.ts
Normal file
@ -0,0 +1,46 @@
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
import * as config from '../utils/config';
|
||||
import * as console from '../utils/console';
|
||||
|
||||
import {buildCommitMessage} from './builder';
|
||||
|
||||
|
||||
describe('commit message building:', () => {
|
||||
beforeEach(() => {
|
||||
// stub logging calls to prevent noise in test log
|
||||
spyOn(console, 'info').and.stub();
|
||||
// provide a configuration for DevInfra when loaded
|
||||
spyOn(config, 'getConfig').and.returnValue({
|
||||
commitMessage: {
|
||||
scopes: ['core'],
|
||||
}
|
||||
} as any);
|
||||
});
|
||||
|
||||
it('creates a commit message with a scope', async () => {
|
||||
buildPromptResponseSpies('fix', 'core', 'This is a summary');
|
||||
|
||||
expect(await buildCommitMessage()).toMatch(/^fix\(core\): This is a summary/);
|
||||
});
|
||||
|
||||
it('creates a commit message without a scope', async () => {
|
||||
buildPromptResponseSpies('build', false, 'This is a summary');
|
||||
|
||||
expect(await buildCommitMessage()).toMatch(/^build: This is a summary/);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/** Create spies to return the mocked selections from prompts. */
|
||||
function buildPromptResponseSpies(type: string, scope: string|false, summary: string) {
|
||||
spyOn(console, 'promptAutocomplete')
|
||||
.and.returnValues(Promise.resolve(type), Promise.resolve(scope));
|
||||
spyOn(console, 'promptInput').and.returnValue(Promise.resolve(summary));
|
||||
}
|
70
dev-infra/commit-message/builder.ts
Normal file
70
dev-infra/commit-message/builder.ts
Normal file
@ -0,0 +1,70 @@
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
import {ListChoiceOptions} from 'inquirer';
|
||||
|
||||
import {info, promptAutocomplete, promptInput} from '../utils/console';
|
||||
|
||||
import {COMMIT_TYPES, CommitType, getCommitMessageConfig, ScopeRequirement} from './config';
|
||||
|
||||
/** Validate commit message at the provided file path. */
|
||||
export async function buildCommitMessage() {
|
||||
// TODO(josephperrott): Add support for skipping wizard with local untracked config file
|
||||
// TODO(josephperrott): Add default commit message information/commenting into generated messages
|
||||
info('Just a few questions to start building the commit message!');
|
||||
|
||||
/** The commit message type. */
|
||||
const type = await promptForCommitMessageType();
|
||||
/** The commit message scope. */
|
||||
const scope = await promptForCommitMessageScopeForType(type);
|
||||
/** The commit message summary. */
|
||||
const summary = await promptForCommitMessageSummary();
|
||||
|
||||
return `${type.name}${scope ? '(' + scope + ')' : ''}: ${summary}\n\n`;
|
||||
}
|
||||
|
||||
/** Prompts in the terminal for the commit message's type. */
|
||||
async function promptForCommitMessageType(): Promise<CommitType> {
|
||||
info('The type of change in the commit. Allows a reader to know the effect of the change,');
|
||||
info('whether it brings a new feature, adds additional testing, documents the `project, etc.');
|
||||
|
||||
/** List of commit type options for the autocomplete prompt. */
|
||||
const typeOptions: ListChoiceOptions[] =
|
||||
Object.values(COMMIT_TYPES).map(({description, name}) => {
|
||||
return {
|
||||
name: `${name} - ${description}`,
|
||||
value: name,
|
||||
short: name,
|
||||
};
|
||||
});
|
||||
/** The key of a commit message type, selected by the user via prompt. */
|
||||
const typeName = await promptAutocomplete('Select a type for the commit:', typeOptions);
|
||||
|
||||
return COMMIT_TYPES[typeName];
|
||||
}
|
||||
|
||||
/** Prompts in the terminal for the commit message's scope. */
|
||||
async function promptForCommitMessageScopeForType(type: CommitType): Promise<string|false> {
|
||||
// If the commit type's scope requirement is forbidden, return early.
|
||||
if (type.scope === ScopeRequirement.Forbidden) {
|
||||
info(`Skipping scope selection as the '${type.name}' type does not allow scopes`);
|
||||
return false;
|
||||
}
|
||||
/** Commit message configuration */
|
||||
const config = getCommitMessageConfig();
|
||||
|
||||
info('The area of the repository the changes in this commit most affects.');
|
||||
return await promptAutocomplete(
|
||||
'Select a scope for the commit:', config.commitMessage.scopes,
|
||||
type.scope === ScopeRequirement.Optional ? '<no scope>' : '');
|
||||
}
|
||||
|
||||
/** Prompts in the terminal for the commit message's summary. */
|
||||
async function promptForCommitMessageSummary(): Promise<string> {
|
||||
info('Provide a short summary of what the changes in the commit do');
|
||||
return await promptInput('Provide a short summary of the commit');
|
||||
}
|
@ -12,20 +12,23 @@ import {info} from '../utils/console';
|
||||
import {restoreCommitMessage} from './restore-commit-message';
|
||||
import {validateFile} from './validate-file';
|
||||
import {validateCommitRange} from './validate-range';
|
||||
import {runWizard} from './wizard';
|
||||
|
||||
/** Build the parser for the commit-message commands. */
|
||||
export function buildCommitMessageParser(localYargs: yargs.Argv) {
|
||||
return localYargs.help()
|
||||
.strict()
|
||||
.command(
|
||||
'restore-commit-message-draft', false, {
|
||||
'file-env-variable': {
|
||||
'restore-commit-message-draft', false,
|
||||
args => {
|
||||
return args.option('file-env-variable', {
|
||||
type: 'string',
|
||||
array: true,
|
||||
conflicts: ['file'],
|
||||
required: true,
|
||||
description:
|
||||
'The key for the environment variable which holds the arguments for the ' +
|
||||
'prepare-commit-msg hook as described here: ' +
|
||||
'The key for the environment variable which holds the arguments for the\n' +
|
||||
'prepare-commit-msg hook as described here:\n' +
|
||||
'https://git-scm.com/docs/githooks#_prepare_commit_msg',
|
||||
coerce: arg => {
|
||||
const [file, source] = (process.env[arg] || '').split(' ');
|
||||
@ -34,10 +37,27 @@ export function buildCommitMessageParser(localYargs: yargs.Argv) {
|
||||
}
|
||||
return [file, source];
|
||||
},
|
||||
}
|
||||
});
|
||||
},
|
||||
args => {
|
||||
restoreCommitMessage(args.fileEnvVariable[0], args.fileEnvVariable[1]);
|
||||
restoreCommitMessage(args['file-env-variable'][0], args['file-env-variable'][1] as any);
|
||||
})
|
||||
.command(
|
||||
'wizard <filePath> [source] [commitSha]', '', ((args: any) => {
|
||||
return args
|
||||
.positional(
|
||||
'filePath',
|
||||
{description: 'The file path to write the generated commit message into'})
|
||||
.positional('source', {
|
||||
choices: ['message', 'template', 'merge', 'squash', 'commit'],
|
||||
description: 'The source of the commit message as described here: ' +
|
||||
'https://git-scm.com/docs/githooks#_prepare_commit_msg'
|
||||
})
|
||||
.positional(
|
||||
'commitSha', {description: 'The commit sha if source is set to `commit`'});
|
||||
}),
|
||||
async (args: any) => {
|
||||
await runWizard(args);
|
||||
})
|
||||
.command(
|
||||
'pre-commit-validate', 'Validate the most recent commit message', {
|
||||
@ -61,7 +81,7 @@ export function buildCommitMessageParser(localYargs: yargs.Argv) {
|
||||
}
|
||||
},
|
||||
args => {
|
||||
const file = args.file || args.fileEnvVariable || '.git/COMMIT_EDITMSG';
|
||||
const file = args.file || args['file-env-variable'] || '.git/COMMIT_EDITMSG';
|
||||
validateFile(file);
|
||||
})
|
||||
.command(
|
||||
|
@ -39,36 +39,56 @@ export enum ScopeRequirement {
|
||||
|
||||
/** A commit type */
|
||||
export interface CommitType {
|
||||
description: string;
|
||||
name: string;
|
||||
scope: ScopeRequirement;
|
||||
}
|
||||
|
||||
/** The valid commit types for Angular commit messages. */
|
||||
export const COMMIT_TYPES: {[key: string]: CommitType} = {
|
||||
build: {
|
||||
name: 'build',
|
||||
description: 'Changes to local repository build system and tooling',
|
||||
scope: ScopeRequirement.Forbidden,
|
||||
},
|
||||
ci: {
|
||||
name: 'ci',
|
||||
description: 'Changes to CI configuration and CI specific tooling',
|
||||
scope: ScopeRequirement.Forbidden,
|
||||
},
|
||||
docs: {
|
||||
name: 'docs',
|
||||
description: 'Changes which exclusively affects documentation.',
|
||||
scope: ScopeRequirement.Optional,
|
||||
},
|
||||
feat: {
|
||||
name: 'feat',
|
||||
description: 'Creates a new feature',
|
||||
scope: ScopeRequirement.Required,
|
||||
},
|
||||
fix: {
|
||||
name: 'fix',
|
||||
description: 'Fixes a previously discovered failure/bug',
|
||||
scope: ScopeRequirement.Required,
|
||||
},
|
||||
perf: {
|
||||
name: 'perf',
|
||||
description: 'Improves performance without any change in functionality or API',
|
||||
scope: ScopeRequirement.Required,
|
||||
},
|
||||
refactor: {
|
||||
name: 'refactor',
|
||||
description: 'Refactor without any change in functionality or API (includes style changes)',
|
||||
scope: ScopeRequirement.Required,
|
||||
},
|
||||
release: {
|
||||
name: 'release',
|
||||
description: 'A release point in the repository',
|
||||
scope: ScopeRequirement.Forbidden,
|
||||
},
|
||||
test: {
|
||||
name: 'test',
|
||||
description: 'Improvements or corrections made to the project\'s test suite',
|
||||
scope: ScopeRequirement.Required,
|
||||
},
|
||||
};
|
||||
|
43
dev-infra/commit-message/wizard.ts
Normal file
43
dev-infra/commit-message/wizard.ts
Normal file
@ -0,0 +1,43 @@
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
import {writeFileSync} from 'fs';
|
||||
|
||||
import {info} from '../utils/console';
|
||||
|
||||
import {buildCommitMessage} from './builder';
|
||||
|
||||
/**
|
||||
* The source triggering the git commit message creation.
|
||||
* As described in: https://git-scm.com/docs/githooks#_prepare_commit_msg
|
||||
*/
|
||||
export type PrepareCommitMsgHookSource = 'message'|'template'|'merge'|'squash'|'commit';
|
||||
|
||||
/** The default commit message used if the wizard does not procude a commit message. */
|
||||
const defaultCommitMessage = `<type>(<scope>): <summary>
|
||||
|
||||
# <Describe the motivation behind this change - explain WHY you are making this change. Wrap all
|
||||
# lines at 100 characters.>\n\n`;
|
||||
|
||||
export async function runWizard(
|
||||
args: {filePath: string, source?: PrepareCommitMsgHookSource, commitSha?: string}) {
|
||||
// TODO(josephperrott): Add support for skipping wizard with local untracked config file
|
||||
|
||||
if (args.source !== undefined) {
|
||||
info(`Skipping commit message wizard due because the commit was created via '${
|
||||
args.source}' source`);
|
||||
process.exitCode = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the default commit message to be updated if the user cancels out of the wizard in progress
|
||||
writeFileSync(args.filePath, defaultCommitMessage);
|
||||
|
||||
/** The generated commit message. */
|
||||
const commitMessage = await buildCommitMessage();
|
||||
writeFileSync(args.filePath, commitMessage);
|
||||
}
|
@ -22,28 +22,31 @@ export function buildFormatParser(localYargs: yargs.Argv) {
|
||||
description: 'Run the formatter to check formatting rather than updating code format'
|
||||
})
|
||||
.command(
|
||||
'all', 'Run the formatter on all files in the repository', {},
|
||||
'all', 'Run the formatter on all files in the repository', args => args,
|
||||
({check}) => {
|
||||
const executionCmd = check ? checkFiles : formatFiles;
|
||||
executionCmd(allFiles());
|
||||
})
|
||||
.command(
|
||||
'changed [shaOrRef]', 'Run the formatter on files changed since the provided sha/ref', {},
|
||||
'changed [shaOrRef]', 'Run the formatter on files changed since the provided sha/ref',
|
||||
args => args.positional('shaOrRef', {type: 'string'}),
|
||||
({shaOrRef, check}) => {
|
||||
const sha = shaOrRef || 'master';
|
||||
const executionCmd = check ? checkFiles : formatFiles;
|
||||
executionCmd(allChangedFilesSince(sha));
|
||||
})
|
||||
.command(
|
||||
'staged', 'Run the formatter on all staged files', {},
|
||||
'staged', 'Run the formatter on all staged files', args => args,
|
||||
({check}) => {
|
||||
const executionCmd = check ? checkFiles : formatFiles;
|
||||
executionCmd(allStagedFiles());
|
||||
})
|
||||
.command('files <files..>', 'Run the formatter on provided files', {}, ({check, files}) => {
|
||||
const executionCmd = check ? checkFiles : formatFiles;
|
||||
executionCmd(files);
|
||||
});
|
||||
.command(
|
||||
'files <files..>', 'Run the formatter on provided files',
|
||||
args => args.positional('files', {array: true, type: 'string'}), ({check, files}) => {
|
||||
const executionCmd = check ? checkFiles : formatFiles;
|
||||
executionCmd(files!);
|
||||
});
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
|
@ -6,6 +6,7 @@ ts_library(
|
||||
module_name = "@angular/dev-infra-private/pr",
|
||||
visibility = ["//dev-infra:__subpackages__"],
|
||||
deps = [
|
||||
"//dev-infra/pr/checkout",
|
||||
"//dev-infra/pr/discover-new-conflicts",
|
||||
"//dev-infra/pr/merge",
|
||||
"//dev-infra/pr/rebase",
|
||||
|
13
dev-infra/pr/checkout/BUILD.bazel
Normal file
13
dev-infra/pr/checkout/BUILD.bazel
Normal file
@ -0,0 +1,13 @@
|
||||
load("@npm_bazel_typescript//:index.bzl", "ts_library")
|
||||
|
||||
ts_library(
|
||||
name = "checkout",
|
||||
srcs = glob(["*.ts"]),
|
||||
module_name = "@angular/dev-infra-private/pr/checkout",
|
||||
visibility = ["//dev-infra:__subpackages__"],
|
||||
deps = [
|
||||
"//dev-infra/pr/common",
|
||||
"//dev-infra/utils",
|
||||
"@npm//@types/yargs",
|
||||
],
|
||||
)
|
50
dev-infra/pr/checkout/cli.ts
Normal file
50
dev-infra/pr/checkout/cli.ts
Normal file
@ -0,0 +1,50 @@
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
import {Arguments, Argv, CommandModule} from 'yargs';
|
||||
|
||||
import {error} from '../../utils/console';
|
||||
import {checkOutPullRequestLocally} from '../common/checkout-pr';
|
||||
|
||||
export interface CheckoutOptions {
|
||||
prNumber: number;
|
||||
'github-token'?: string;
|
||||
}
|
||||
|
||||
/** URL to the Github page where personal access tokens can be generated. */
|
||||
export const GITHUB_TOKEN_GENERATE_URL = `https://github.com/settings/tokens`;
|
||||
|
||||
/** Builds the checkout pull request command. */
|
||||
function builder(yargs: Argv) {
|
||||
return yargs.positional('prNumber', {type: 'number', demandOption: true}).option('github-token', {
|
||||
type: 'string',
|
||||
description: 'Github token. If not set, token is retrieved from the environment variables.'
|
||||
});
|
||||
}
|
||||
|
||||
/** Handles the checkout pull request command. */
|
||||
async function handler({prNumber, 'github-token': token}: Arguments<CheckoutOptions>) {
|
||||
const githubToken = token || process.env.GITHUB_TOKEN || process.env.TOKEN;
|
||||
if (!githubToken) {
|
||||
error('No Github token set. Please set the `GITHUB_TOKEN` environment variable.');
|
||||
error('Alternatively, pass the `--github-token` command line flag.');
|
||||
error(`You can generate a token here: ${GITHUB_TOKEN_GENERATE_URL}`);
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
const prCheckoutOptions = {allowIfMaintainerCannotModify: true, branchName: `pr-${prNumber}`};
|
||||
await checkOutPullRequestLocally(prNumber, githubToken, prCheckoutOptions);
|
||||
}
|
||||
|
||||
/** yargs command module for checking out a PR */
|
||||
export const CheckoutCommandModule: CommandModule<{}, CheckoutOptions> = {
|
||||
handler,
|
||||
builder,
|
||||
command: 'checkout <pr-number>',
|
||||
describe: 'Checkout a PR from the upstream repo',
|
||||
};
|
@ -8,6 +8,7 @@
|
||||
|
||||
import * as yargs from 'yargs';
|
||||
|
||||
import {CheckoutCommandModule} from './checkout/cli';
|
||||
import {buildDiscoverNewConflictsCommand, handleDiscoverNewConflictsCommand} from './discover-new-conflicts/cli';
|
||||
import {buildMergeCommand, handleMergeCommand} from './merge/cli';
|
||||
import {buildRebaseCommand, handleRebaseCommand} from './rebase/cli';
|
||||
@ -24,7 +25,8 @@ export function buildPrParser(localYargs: yargs.Argv) {
|
||||
buildDiscoverNewConflictsCommand, handleDiscoverNewConflictsCommand)
|
||||
.command(
|
||||
'rebase <pr-number>', 'Rebase a pending PR and push the rebased commits back to Github',
|
||||
buildRebaseCommand, handleRebaseCommand);
|
||||
buildRebaseCommand, handleRebaseCommand)
|
||||
.command(CheckoutCommandModule);
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
|
12
dev-infra/pr/common/BUILD.bazel
Normal file
12
dev-infra/pr/common/BUILD.bazel
Normal file
@ -0,0 +1,12 @@
|
||||
load("@npm_bazel_typescript//:index.bzl", "ts_library")
|
||||
|
||||
ts_library(
|
||||
name = "common",
|
||||
srcs = glob(["*.ts"]),
|
||||
visibility = ["//dev-infra:__subpackages__"],
|
||||
deps = [
|
||||
"//dev-infra/utils",
|
||||
"@npm//@types/node",
|
||||
"@npm//typed-graphqlify",
|
||||
],
|
||||
)
|
135
dev-infra/pr/common/checkout-pr.ts
Normal file
135
dev-infra/pr/common/checkout-pr.ts
Normal file
@ -0,0 +1,135 @@
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
import {types as graphQLTypes} from 'typed-graphqlify';
|
||||
import {URL} from 'url';
|
||||
|
||||
import {info} from '../../utils/console';
|
||||
import {GitClient} from '../../utils/git';
|
||||
import {getPr} from '../../utils/github';
|
||||
|
||||
/* GraphQL schema for the response body for a pending PR. */
|
||||
const PR_SCHEMA = {
|
||||
state: graphQLTypes.string,
|
||||
maintainerCanModify: graphQLTypes.boolean,
|
||||
viewerDidAuthor: graphQLTypes.boolean,
|
||||
headRefOid: graphQLTypes.string,
|
||||
headRef: {
|
||||
name: graphQLTypes.string,
|
||||
repository: {
|
||||
url: graphQLTypes.string,
|
||||
nameWithOwner: graphQLTypes.string,
|
||||
},
|
||||
},
|
||||
baseRef: {
|
||||
name: graphQLTypes.string,
|
||||
repository: {
|
||||
url: graphQLTypes.string,
|
||||
nameWithOwner: graphQLTypes.string,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
export class UnexpectedLocalChangesError extends Error {
|
||||
constructor(m: string) {
|
||||
super(m);
|
||||
Object.setPrototypeOf(this, UnexpectedLocalChangesError.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
export class MaintainerModifyAccessError extends Error {
|
||||
constructor(m: string) {
|
||||
super(m);
|
||||
Object.setPrototypeOf(this, MaintainerModifyAccessError.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
/** Options for checking out a PR */
|
||||
export interface PullRequestCheckoutOptions {
|
||||
/** Whether the PR should be checked out if the maintainer cannot modify. */
|
||||
allowIfMaintainerCannotModify?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebase the provided PR onto its merge target branch, and push up the resulting
|
||||
* commit to the PRs repository.
|
||||
*/
|
||||
export async function checkOutPullRequestLocally(
|
||||
prNumber: number, githubToken: string, opts: PullRequestCheckoutOptions = {}) {
|
||||
/** Authenticated Git client for git and Github interactions. */
|
||||
const git = new GitClient(githubToken);
|
||||
|
||||
// In order to preserve local changes, checkouts cannot occur if local changes are present in the
|
||||
// git environment. Checked before retrieving the PR to fail fast.
|
||||
if (git.hasLocalChanges()) {
|
||||
throw new UnexpectedLocalChangesError('Unable to checkout PR due to uncommitted changes.');
|
||||
}
|
||||
|
||||
/**
|
||||
* The branch or revision originally checked out before this method performed
|
||||
* any Git operations that may change the working branch.
|
||||
*/
|
||||
const previousBranchOrRevision = git.getCurrentBranchOrRevision();
|
||||
/* The PR information from Github. */
|
||||
const pr = await getPr(PR_SCHEMA, prNumber, git);
|
||||
/** The branch name of the PR from the repository the PR came from. */
|
||||
const headRefName = pr.headRef.name;
|
||||
/** The full ref for the repository and branch the PR came from. */
|
||||
const fullHeadRef = `${pr.headRef.repository.nameWithOwner}:${headRefName}`;
|
||||
/** The full URL path of the repository the PR came from with github token as authentication. */
|
||||
const headRefUrl = addAuthenticationToUrl(pr.headRef.repository.url, githubToken);
|
||||
// Note: Since we use a detached head for rebasing the PR and therefore do not have
|
||||
// remote-tracking branches configured, we need to set our expected ref and SHA. This
|
||||
// allows us to use `--force-with-lease` for the detached head while ensuring that we
|
||||
// never accidentally override upstream changes that have been pushed in the meanwhile.
|
||||
// See:
|
||||
// https://git-scm.com/docs/git-push#Documentation/git-push.txt---force-with-leaseltrefnamegtltexpectgt
|
||||
/** Flag for a force push with leage back to upstream. */
|
||||
const forceWithLeaseFlag = `--force-with-lease=${headRefName}:${pr.headRefOid}`;
|
||||
|
||||
// If the PR does not allow maintainers to modify it, exit as the rebased PR cannot
|
||||
// be pushed up.
|
||||
if (!pr.maintainerCanModify && !pr.viewerDidAuthor && !opts.allowIfMaintainerCannotModify) {
|
||||
throw new MaintainerModifyAccessError('PR is not set to allow maintainers to modify the PR');
|
||||
}
|
||||
|
||||
try {
|
||||
// Fetch the branch at the commit of the PR, and check it out in a detached state.
|
||||
info(`Checking out PR #${prNumber} from ${fullHeadRef}`);
|
||||
git.run(['fetch', headRefUrl, headRefName]);
|
||||
git.run(['checkout', '--detach', 'FETCH_HEAD']);
|
||||
} catch (e) {
|
||||
git.checkout(previousBranchOrRevision, true);
|
||||
throw e;
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Pushes the current local branch to the PR on the upstream repository.
|
||||
*
|
||||
* @returns true If the command did not fail causing a GitCommandError to be thrown.
|
||||
* @throws GitCommandError Thrown when the push back to upstream fails.
|
||||
*/
|
||||
pushToUpstream: (): true => {
|
||||
git.run(['push', headRefUrl, `HEAD:${headRefName}`, forceWithLeaseFlag]);
|
||||
return true;
|
||||
},
|
||||
/** Restores the state of the local repository to before the PR checkout occured. */
|
||||
resetGitState: (): boolean => {
|
||||
return git.checkout(previousBranchOrRevision, true);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** Adds the provided token as username to the provided url. */
|
||||
function addAuthenticationToUrl(urlString: string, token: string) {
|
||||
const url = new URL(urlString);
|
||||
url.username = token;
|
||||
return url.toString();
|
||||
}
|
@ -12,18 +12,28 @@ import {error} from '../../utils/console';
|
||||
|
||||
import {discoverNewConflictsForPr} from './index';
|
||||
|
||||
/** The options available to the discover-new-conflicts command via CLI. */
|
||||
export interface DiscoverNewConflictsCommandOptions {
|
||||
date: number;
|
||||
'pr-number': number;
|
||||
}
|
||||
|
||||
/** Builds the discover-new-conflicts pull request command. */
|
||||
export function buildDiscoverNewConflictsCommand(yargs: Argv) {
|
||||
return yargs.option('date', {
|
||||
description: 'Only consider PRs updated since provided date',
|
||||
defaultDescription: '30 days ago',
|
||||
coerce: Date.parse,
|
||||
default: getThirtyDaysAgoDate,
|
||||
});
|
||||
export function buildDiscoverNewConflictsCommand(yargs: Argv):
|
||||
Argv<DiscoverNewConflictsCommandOptions> {
|
||||
return yargs
|
||||
.option('date', {
|
||||
description: 'Only consider PRs updated since provided date',
|
||||
defaultDescription: '30 days ago',
|
||||
coerce: (date) => typeof date === 'number' ? date : Date.parse(date),
|
||||
default: getThirtyDaysAgoDate(),
|
||||
})
|
||||
.positional('pr-number', {demandOption: true, type: 'number'});
|
||||
}
|
||||
|
||||
/** Handles the discover-new-conflicts pull request command. */
|
||||
export async function handleDiscoverNewConflictsCommand({prNumber, date}: Arguments) {
|
||||
export async function handleDiscoverNewConflictsCommand(
|
||||
{'pr-number': prNumber, date}: Arguments<DiscoverNewConflictsCommandOptions>) {
|
||||
// If a provided date is not able to be parsed, yargs provides it as NaN.
|
||||
if (isNaN(date)) {
|
||||
error('Unable to parse the value provided via --date flag');
|
||||
@ -33,11 +43,11 @@ export async function handleDiscoverNewConflictsCommand({prNumber, date}: Argume
|
||||
}
|
||||
|
||||
/** Gets a date object 30 days ago from today. */
|
||||
function getThirtyDaysAgoDate(): Date {
|
||||
function getThirtyDaysAgoDate() {
|
||||
const date = new Date();
|
||||
// Set the hours, minutes and seconds to 0 to only consider date.
|
||||
date.setHours(0, 0, 0, 0);
|
||||
// Set the date to 30 days in the past.
|
||||
date.setDate(date.getDate() - 30);
|
||||
return date;
|
||||
return date.getTime();
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ export async function discoverNewConflictsForPr(
|
||||
|
||||
info(`Requesting pending PRs from Github`);
|
||||
/** List of PRs from github currently known as mergable. */
|
||||
const allPendingPRs = (await getPendingPrs(PR_SCHEMA, config.github)).map(processPr);
|
||||
const allPendingPRs = (await getPendingPrs(PR_SCHEMA, git)).map(processPr);
|
||||
/** The PR which is being checked against. */
|
||||
const requestedPr = allPendingPRs.find(pr => pr.number === newPrNumber);
|
||||
if (requestedPr === undefined) {
|
||||
|
@ -12,17 +12,26 @@ import {error, red, yellow} from '../../utils/console';
|
||||
|
||||
import {GITHUB_TOKEN_GENERATE_URL, mergePullRequest} from './index';
|
||||
|
||||
/** The options available to the merge command via CLI. */
|
||||
export interface MergeCommandOptions {
|
||||
'github-token'?: string;
|
||||
'pr-number': number;
|
||||
}
|
||||
|
||||
/** Builds the options for the merge command. */
|
||||
export function buildMergeCommand(yargs: Argv) {
|
||||
return yargs.help().strict().option('github-token', {
|
||||
type: 'string',
|
||||
description: 'Github token. If not set, token is retrieved from the environment variables.'
|
||||
});
|
||||
export function buildMergeCommand(yargs: Argv): Argv<MergeCommandOptions> {
|
||||
return yargs.help()
|
||||
.strict()
|
||||
.positional('pr-number', {demandOption: true, type: 'number'})
|
||||
.option('github-token', {
|
||||
type: 'string',
|
||||
description: 'Github token. If not set, token is retrieved from the environment variables.'
|
||||
});
|
||||
}
|
||||
|
||||
/** Handles the merge command. i.e. performs the merge of a specified pull request. */
|
||||
export async function handleMergeCommand(args: Arguments) {
|
||||
const githubToken = args.githubToken || process.env.GITHUB_TOKEN || process.env.TOKEN;
|
||||
export async function handleMergeCommand(args: Arguments<MergeCommandOptions>) {
|
||||
const githubToken = args['github-token'] || process.env.GITHUB_TOKEN || process.env.TOKEN;
|
||||
if (!githubToken) {
|
||||
error(red('No Github token set. Please set the `GITHUB_TOKEN` environment variable.'));
|
||||
error(red('Alternatively, pass the `--github-token` command line flag.'));
|
||||
@ -30,5 +39,5 @@ export async function handleMergeCommand(args: Arguments) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
await mergePullRequest(args.prNumber, githubToken);
|
||||
await mergePullRequest(args['pr-number'], githubToken);
|
||||
}
|
||||
|
@ -15,17 +15,26 @@ import {rebasePr} from './index';
|
||||
/** URL to the Github page where personal access tokens can be generated. */
|
||||
export const GITHUB_TOKEN_GENERATE_URL = `https://github.com/settings/tokens`;
|
||||
|
||||
/** Builds the rebase pull request command. */
|
||||
export function buildRebaseCommand(yargs: Argv) {
|
||||
return yargs.option('github-token', {
|
||||
type: 'string',
|
||||
description: 'Github token. If not set, token is retrieved from the environment variables.'
|
||||
});
|
||||
/** The options available to the rebase command via CLI. */
|
||||
export interface RebaseCommandOptions {
|
||||
'github-token'?: string;
|
||||
prNumber: number;
|
||||
}
|
||||
|
||||
/** Builds the rebase pull request command. */
|
||||
export function buildRebaseCommand(yargs: Argv): Argv<RebaseCommandOptions> {
|
||||
return yargs
|
||||
.option('github-token', {
|
||||
type: 'string',
|
||||
description: 'Github token. If not set, token is retrieved from the environment variables.'
|
||||
})
|
||||
.positional('prNumber', {type: 'number', demandOption: true});
|
||||
}
|
||||
|
||||
|
||||
/** Handles the rebase pull request command. */
|
||||
export async function handleRebaseCommand(args: Arguments) {
|
||||
const githubToken = args.githubToken || process.env.GITHUB_TOKEN || process.env.TOKEN;
|
||||
export async function handleRebaseCommand(args: Arguments<RebaseCommandOptions>) {
|
||||
const githubToken = args['github-token'] || process.env.GITHUB_TOKEN || process.env.TOKEN;
|
||||
if (!githubToken) {
|
||||
error('No Github token set. Please set the `GITHUB_TOKEN` environment variable.');
|
||||
error('Alternatively, pass the `--github-token` command line flag.');
|
||||
|
@ -55,7 +55,7 @@ export async function rebasePr(
|
||||
*/
|
||||
const previousBranchOrRevision = git.getCurrentBranchOrRevision();
|
||||
/* Get the PR information from Github. */
|
||||
const pr = await getPr(PR_SCHEMA, prNumber, config.github);
|
||||
const pr = await getPr(PR_SCHEMA, prNumber, git);
|
||||
|
||||
const headRefName = pr.headRef.name;
|
||||
const baseRefName = pr.baseRef.name;
|
||||
|
@ -30,20 +30,19 @@ export function tsCircularDependenciesBuilder(localYargs: yargs.Argv) {
|
||||
{type: 'string', demandOption: true, description: 'Path to the configuration file.'})
|
||||
.option('warnings', {type: 'boolean', description: 'Prints all warnings.'})
|
||||
.command(
|
||||
'check', 'Checks if the circular dependencies have changed.', {},
|
||||
(argv: yargs.Arguments) => {
|
||||
'check', 'Checks if the circular dependencies have changed.', args => args,
|
||||
argv => {
|
||||
const {config: configArg, warnings} = argv;
|
||||
const configPath = isAbsolute(configArg) ? configArg : resolve(configArg);
|
||||
const config = loadTestConfig(configPath);
|
||||
process.exit(main(false, config, warnings));
|
||||
process.exit(main(false, config, !!warnings));
|
||||
})
|
||||
.command(
|
||||
'approve', 'Approves the current circular dependencies.', {}, (argv: yargs.Arguments) => {
|
||||
const {config: configArg, warnings} = argv;
|
||||
const configPath = isAbsolute(configArg) ? configArg : resolve(configArg);
|
||||
const config = loadTestConfig(configPath);
|
||||
process.exit(main(true, config, warnings));
|
||||
});
|
||||
.command('approve', 'Approves the current circular dependencies.', args => args, argv => {
|
||||
const {config: configArg, warnings} = argv;
|
||||
const configPath = isAbsolute(configArg) ? configArg : resolve(configArg);
|
||||
const config = loadTestConfig(configPath);
|
||||
process.exit(main(true, config, !!warnings));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -17,6 +17,7 @@ ts_library(
|
||||
"@npm//@types/shelljs",
|
||||
"@npm//chalk",
|
||||
"@npm//inquirer",
|
||||
"@npm//inquirer-autocomplete-prompt",
|
||||
"@npm//shelljs",
|
||||
"@npm//tslib",
|
||||
"@npm//typed-graphqlify",
|
||||
|
@ -7,7 +7,8 @@
|
||||
*/
|
||||
|
||||
import chalk from 'chalk';
|
||||
import {prompt} from 'inquirer';
|
||||
import {createPromptModule, ListChoiceOptions, prompt} from 'inquirer';
|
||||
import * as inquirerAutocomplete from 'inquirer-autocomplete-prompt';
|
||||
|
||||
|
||||
/** Reexport of chalk colors for convenient access. */
|
||||
@ -26,6 +27,52 @@ export async function promptConfirm(message: string, defaultValue = false): Prom
|
||||
.result;
|
||||
}
|
||||
|
||||
/** Prompts the user to select an option from a filterable autocomplete list. */
|
||||
export async function promptAutocomplete(
|
||||
message: string, choices: (string|ListChoiceOptions)[]): Promise<string>;
|
||||
/**
|
||||
* Prompts the user to select an option from a filterable autocomplete list, with an option to
|
||||
* choose no value.
|
||||
*/
|
||||
export async function promptAutocomplete(
|
||||
message: string, choices: (string|ListChoiceOptions)[],
|
||||
noChoiceText?: string): Promise<string|false>;
|
||||
export async function promptAutocomplete(
|
||||
message: string, choices: (string|ListChoiceOptions)[],
|
||||
noChoiceText?: string): Promise<string|false> {
|
||||
// Creates a local prompt module with an autocomplete prompt type.
|
||||
const prompt = createPromptModule({}).registerPrompt('autocomplete', inquirerAutocomplete);
|
||||
if (noChoiceText) {
|
||||
choices = [noChoiceText, ...choices];
|
||||
}
|
||||
// `prompt` must be cast as `any` as the autocomplete typings are not available.
|
||||
const result = (await (prompt as any)({
|
||||
type: 'autocomplete',
|
||||
name: 'result',
|
||||
message,
|
||||
source: (_: any, input: string) => {
|
||||
if (!input) {
|
||||
return Promise.resolve(choices);
|
||||
}
|
||||
return Promise.resolve(choices.filter(choice => {
|
||||
if (typeof choice === 'string') {
|
||||
return choice.includes(input);
|
||||
}
|
||||
return choice.name!.includes(input);
|
||||
}));
|
||||
}
|
||||
})).result;
|
||||
if (result === noChoiceText) {
|
||||
return false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Prompts the user for one line of input. */
|
||||
export async function promptInput(message: string): Promise<string> {
|
||||
return (await prompt<{result: string}>({type: 'input', name: 'result', message})).result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Supported levels for logging functions.
|
||||
*
|
||||
|
@ -26,7 +26,7 @@ export class GithubApiRequestError extends Error {
|
||||
**/
|
||||
export class GithubClient extends Octokit {
|
||||
/** The Github GraphQL (v4) API. */
|
||||
graqhql: GithubGraphqlClient;
|
||||
graphql: GithubGraphqlClient;
|
||||
|
||||
/** The current user based on checking against the Github API. */
|
||||
private _currentUser: string|null = null;
|
||||
@ -42,7 +42,7 @@ export class GithubClient extends Octokit {
|
||||
});
|
||||
|
||||
// Create authenticated graphql client.
|
||||
this.graqhql = new GithubGraphqlClient(token);
|
||||
this.graphql = new GithubGraphqlClient(token);
|
||||
}
|
||||
|
||||
/** Retrieve the login of the current user from Github. */
|
||||
@ -51,7 +51,7 @@ export class GithubClient extends Octokit {
|
||||
if (this._currentUser !== null) {
|
||||
return this._currentUser;
|
||||
}
|
||||
const result = await this.graqhql.query({
|
||||
const result = await this.graphql.query({
|
||||
viewer: {
|
||||
login: types.string,
|
||||
}
|
||||
@ -80,7 +80,7 @@ class GithubGraphqlClient {
|
||||
// Set the default headers to include authorization with the provided token for all
|
||||
// graphQL calls.
|
||||
if (token) {
|
||||
this.graqhql.defaults({headers: {authorization: `token ${token}`}});
|
||||
this.graqhql = this.graqhql.defaults({headers: {authorization: `token ${token}`}});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -147,6 +147,25 @@ export class GitClient {
|
||||
return value.replace(this._githubTokenRegex, '<TOKEN>');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks out a requested branch or revision, optionally cleaning the state of the repository
|
||||
* before attempting the checking. Returns a boolean indicating whether the branch or revision
|
||||
* was cleanly checked out.
|
||||
*/
|
||||
checkout(branchOrRevision: string, cleanState: boolean): boolean {
|
||||
if (cleanState) {
|
||||
// Abort any outstanding ams.
|
||||
this.runGraceful(['am', '--abort'], {stdio: 'ignore'});
|
||||
// Abort any outstanding cherry-picks.
|
||||
this.runGraceful(['cherry-pick', '--abort'], {stdio: 'ignore'});
|
||||
// Abort any outstanding rebases.
|
||||
this.runGraceful(['rebase', '--abort'], {stdio: 'ignore'});
|
||||
// Clear any changes in the current repo.
|
||||
this.runGraceful(['reset', '--hard'], {stdio: 'ignore'});
|
||||
}
|
||||
return this.runGraceful(['checkout', branchOrRevision], {stdio: 'ignore'}).status === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert the GitClient instance is using a token with permissions for the all of the
|
||||
* provided OAuth scopes.
|
||||
|
@ -6,29 +6,15 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {graphql as unauthenticatedGraphql} from '@octokit/graphql';
|
||||
import {params, types} from 'typed-graphqlify';
|
||||
|
||||
import {params, query as graphqlQuery, types} from 'typed-graphqlify';
|
||||
import {NgDevConfig} from './config';
|
||||
|
||||
/** The configuration required for github interactions. */
|
||||
type GithubConfig = NgDevConfig['github'];
|
||||
|
||||
/**
|
||||
* 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}`,
|
||||
}
|
||||
});
|
||||
import {GitClient} from './git';
|
||||
|
||||
/** Get a PR from github */
|
||||
export async function getPr<PrSchema>(
|
||||
prSchema: PrSchema, prNumber: number, {owner, name}: GithubConfig) {
|
||||
export async function getPr<PrSchema>(prSchema: PrSchema, prNumber: number, git: GitClient) {
|
||||
/** The owner and name of the repository */
|
||||
const {owner, name} = git.remoteConfig;
|
||||
/** The GraphQL query object to get a the PR */
|
||||
const PR_QUERY = params(
|
||||
{
|
||||
$number: 'Int!', // The PR number
|
||||
@ -41,14 +27,15 @@ export async function getPr<PrSchema>(
|
||||
})
|
||||
});
|
||||
|
||||
const result =
|
||||
await graphql(graphqlQuery(PR_QUERY), {number: prNumber, owner, name}) as typeof PR_QUERY;
|
||||
const result = (await git.github.graphql.query(PR_QUERY, {number: prNumber, owner, name}));
|
||||
return result.repository.pullRequest;
|
||||
}
|
||||
|
||||
/** Get all pending PRs from github */
|
||||
export async function getPendingPrs<PrSchema>(prSchema: PrSchema, {owner, name}: GithubConfig) {
|
||||
// The GraphQL query object to get a page of pending PRs
|
||||
export async function getPendingPrs<PrSchema>(prSchema: PrSchema, git: GitClient) {
|
||||
/** The owner and name of the repository */
|
||||
const {owner, name} = git.remoteConfig;
|
||||
/** The GraphQL query object to get a page of pending PRs */
|
||||
const PRS_QUERY = params(
|
||||
{
|
||||
$first: 'Int', // How many entries to get with each request
|
||||
@ -73,36 +60,22 @@ export async function getPendingPrs<PrSchema>(prSchema: PrSchema, {owner, name}:
|
||||
}),
|
||||
})
|
||||
});
|
||||
const query = graphqlQuery('members', PRS_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,
|
||||
name,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
// The current cursor
|
||||
/** The current cursor */
|
||||
let cursor: string|undefined;
|
||||
// If an additional page of members is expected
|
||||
/** If an additional page of members is expected */
|
||||
let hasNextPage = true;
|
||||
// Array of pending PRs
|
||||
/** Array of pending PRs */
|
||||
const prs: Array<PrSchema> = [];
|
||||
|
||||
// For each page of the response, get the page and add it to the
|
||||
// list of PRs
|
||||
// For each page of the response, get the page and add it to the list of PRs
|
||||
while (hasNextPage) {
|
||||
const {query, params} = queryBuilder(100, cursor);
|
||||
const results = await graphql(query, params) as typeof PRS_QUERY;
|
||||
|
||||
const params = {
|
||||
after: cursor || null,
|
||||
first: 100,
|
||||
owner,
|
||||
name,
|
||||
};
|
||||
const results = await git.github.graphql.query(PRS_QUERY, params) as typeof PRS_QUERY;
|
||||
prs.push(...results.repository.pullRequests.nodes);
|
||||
hasNextPage = results.repository.pullRequests.pageInfo.hasNextPage;
|
||||
cursor = results.repository.pullRequests.pageInfo.endCursor;
|
||||
|
17
dev-infra/utils/inquirer-autocomplete-typings.d.ts
vendored
Normal file
17
dev-infra/utils/inquirer-autocomplete-typings.d.ts
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
// inquirer-autocomplete-prompt doesn't provide types and no types are made available via
|
||||
// DefinitelyTyped.
|
||||
declare module "inquirer-autocomplete-prompt" {
|
||||
|
||||
import {registerPrompt} from 'inquirer';
|
||||
|
||||
let AutocompletePrompt: Parameters<typeof registerPrompt>[1];
|
||||
export = AutocompletePrompt;
|
||||
}
|
13
package.json
13
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "angular-srcs",
|
||||
"version": "10.0.10",
|
||||
"version": "10.0.11",
|
||||
"private": true,
|
||||
"description": "Angular - a web framework for modern web apps",
|
||||
"homepage": "https://github.com/angular/angular",
|
||||
@ -76,7 +76,7 @@
|
||||
"@types/diff": "^3.5.1",
|
||||
"@types/fs-extra": "4.0.2",
|
||||
"@types/hammerjs": "2.0.35",
|
||||
"@types/inquirer": "^6.5.0",
|
||||
"@types/inquirer": "^7.3.0",
|
||||
"@types/jasmine": "3.5.10",
|
||||
"@types/jasminewd2": "^2.0.8",
|
||||
"@types/minimist": "^1.2.0",
|
||||
@ -87,7 +87,7 @@
|
||||
"@types/shelljs": "^0.8.6",
|
||||
"@types/systemjs": "0.19.32",
|
||||
"@types/yaml": "^1.2.0",
|
||||
"@types/yargs": "^11.1.1",
|
||||
"@types/yargs": "^15.0.5",
|
||||
"@webcomponents/custom-elements": "^1.1.0",
|
||||
"angular": "npm:angular@1.7",
|
||||
"angular-1.5": "npm:angular@1.5",
|
||||
@ -151,7 +151,7 @@
|
||||
"typescript": "~3.9.5",
|
||||
"xhr2": "0.2.0",
|
||||
"yaml": "^1.7.2",
|
||||
"yargs": "15.3.0"
|
||||
"yargs": "^15.4.1"
|
||||
},
|
||||
"// 2": "devDependencies are not used under Bazel. Many can be removed after test.sh is deleted.",
|
||||
"devDependencies": {
|
||||
@ -177,8 +177,9 @@
|
||||
"glob": "7.1.2",
|
||||
"gulp": "3.9.1",
|
||||
"gulp-conventional-changelog": "^2.0.3",
|
||||
"husky": "^4.2.3",
|
||||
"inquirer": "^7.1.0",
|
||||
"husky": "^4.2.5",
|
||||
"inquirer": "^7.3.3",
|
||||
"inquirer-autocomplete-prompt": "^1.0.2",
|
||||
"jpm": "1.3.1",
|
||||
"karma-browserstack-launcher": "^1.3.0",
|
||||
"karma-sauce-launcher": "^2.0.2",
|
||||
|
@ -155,7 +155,7 @@ export class NgForOf<T, U extends NgIterable<T> = NgIterable<T>> implements DoCh
|
||||
* rather than the identity of the object itself.
|
||||
*
|
||||
* The function receives two inputs,
|
||||
* the iteration index and the node object ID.
|
||||
* the iteration index and the associated node data.
|
||||
*/
|
||||
@Input()
|
||||
set ngForTrackBy(fn: TrackByFunction<T>) {
|
||||
|
@ -20,9 +20,10 @@ export function parseCommandLineOptions(args: string[]): NgccOptions {
|
||||
alias: 'source',
|
||||
describe:
|
||||
'A path (relative to the working directory) of the `node_modules` folder to process.',
|
||||
default: './node_modules'
|
||||
default: './node_modules',
|
||||
type: 'string',
|
||||
})
|
||||
.option('f', {alias: 'formats', hidden: true, array: true})
|
||||
.option('f', {alias: 'formats', hidden: true, array: true, type: 'string'})
|
||||
.option('p', {
|
||||
alias: 'properties',
|
||||
array: true,
|
||||
@ -30,7 +31,8 @@ export function parseCommandLineOptions(args: string[]): NgccOptions {
|
||||
'An array of names of properties in package.json to compile (e.g. `module` or `main`)\n' +
|
||||
'Each of these properties should hold the path to a bundle-format.\n' +
|
||||
'If provided, only the specified properties are considered for processing.\n' +
|
||||
'If not provided, all the supported format properties (e.g. fesm2015, fesm5, es2015, esm2015, esm5, main, module) in the package.json are considered.'
|
||||
'If not provided, all the supported format properties (e.g. fesm2015, fesm5, es2015, esm2015, esm5, main, module) in the package.json are considered.',
|
||||
type: 'string',
|
||||
})
|
||||
.option('t', {
|
||||
alias: 'target',
|
||||
@ -38,6 +40,7 @@ export function parseCommandLineOptions(args: string[]): NgccOptions {
|
||||
'A relative path (from the `source` path) to a single entry-point to process (plus its dependencies).\n' +
|
||||
'If this property is provided then `error-on-failed-entry-point` is forced to true.\n' +
|
||||
'This option overrides the `--use-program-dependencies` option.',
|
||||
type: 'string',
|
||||
})
|
||||
.option('use-program-dependencies', {
|
||||
type: 'boolean',
|
||||
@ -48,7 +51,7 @@ export function parseCommandLineOptions(args: string[]): NgccOptions {
|
||||
.option('first-only', {
|
||||
describe:
|
||||
'If specified then only the first matching package.json property will be compiled.',
|
||||
type: 'boolean'
|
||||
type: 'boolean',
|
||||
})
|
||||
.option('create-ivy-entry-points', {
|
||||
describe:
|
||||
@ -79,6 +82,7 @@ export function parseCommandLineOptions(args: string[]): NgccOptions {
|
||||
alias: 'loglevel',
|
||||
describe: 'The lowest severity logging message that should be output.',
|
||||
choices: ['debug', 'info', 'warn', 'error'],
|
||||
type: 'string',
|
||||
})
|
||||
.option('invalidate-entry-point-manifest', {
|
||||
describe:
|
||||
@ -106,7 +110,7 @@ export function parseCommandLineOptions(args: string[]): NgccOptions {
|
||||
.help()
|
||||
.parse(args);
|
||||
|
||||
if (options['f'] && options['f'].length) {
|
||||
if (options.f?.length) {
|
||||
console.error(
|
||||
'The formats option (-f/--formats) has been removed. Consider the properties option (-p/--properties) instead.');
|
||||
process.exit(1);
|
||||
@ -114,12 +118,12 @@ export function parseCommandLineOptions(args: string[]): NgccOptions {
|
||||
|
||||
setFileSystem(new NodeJSFileSystem());
|
||||
|
||||
const baseSourcePath = resolve(options['s'] || './node_modules');
|
||||
const propertiesToConsider: string[] = options['p'];
|
||||
const targetEntryPointPath = options['t'] ? options['t'] : undefined;
|
||||
const baseSourcePath = resolve(options.s || './node_modules');
|
||||
const propertiesToConsider = options.p;
|
||||
const targetEntryPointPath = options.t;
|
||||
const compileAllFormats = !options['first-only'];
|
||||
const createNewEntryPointFormats = options['create-ivy-entry-points'];
|
||||
const logLevel = options['l'] as keyof typeof LogLevel | undefined;
|
||||
const logLevel = options.l as keyof typeof LogLevel | undefined;
|
||||
const enableI18nLegacyMessageIdFormat = options['legacy-message-ids'];
|
||||
const invalidateEntryPointManifest = options['invalidate-entry-point-manifest'];
|
||||
const errorOnFailedEntryPoint = options['error-on-failed-entry-point'];
|
||||
@ -127,7 +131,7 @@ export function parseCommandLineOptions(args: string[]): NgccOptions {
|
||||
// yargs is not so great at mixed string+boolean types, so we have to test tsconfig against a
|
||||
// string "false" to capture the `tsconfig=false` option.
|
||||
// And we have to convert the option to a string to handle `no-tsconfig`, which will be `false`.
|
||||
const tsConfigPath = `${options['tsconfig']}` === 'false' ? null : options['tsconfig'];
|
||||
const tsConfigPath = `${options.tsconfig}` === 'false' ? null : options.tsconfig;
|
||||
|
||||
const logger = logLevel && new ConsoleLogger(LogLevel[logLevel]);
|
||||
|
||||
@ -139,7 +143,7 @@ export function parseCommandLineOptions(args: string[]): NgccOptions {
|
||||
createNewEntryPointFormats,
|
||||
logger,
|
||||
enableI18nLegacyMessageIdFormat,
|
||||
async: options['async'],
|
||||
async: options.async,
|
||||
invalidateEntryPointManifest,
|
||||
errorOnFailedEntryPoint,
|
||||
tsConfigPath,
|
||||
|
@ -30,18 +30,21 @@ if (require.main === module) {
|
||||
required: true,
|
||||
describe:
|
||||
'The root path of the files to translate, either absolute or relative to the current working directory. E.g. `dist/en`.',
|
||||
type: 'string',
|
||||
})
|
||||
.option('s', {
|
||||
alias: 'source',
|
||||
required: true,
|
||||
describe:
|
||||
'A glob pattern indicating what files to translate, relative to the `root` path. E.g. `bundles/**/*`.',
|
||||
type: 'string',
|
||||
})
|
||||
|
||||
.option('l', {
|
||||
alias: 'source-locale',
|
||||
describe:
|
||||
'The source locale of the application. If this is provided then a copy of the application will be created with no translation but just the `$localize` calls stripped out.',
|
||||
type: 'string',
|
||||
})
|
||||
|
||||
.option('t', {
|
||||
@ -54,6 +57,7 @@ if (require.main === module) {
|
||||
'If you want to merge multiple translation files for each locale, then provide the list of files in an array.\n' +
|
||||
'Note that the arrays must be in double quotes if you include any whitespace within the array.\n' +
|
||||
'E.g. `-t "[src/locale/messages.en.xlf, src/locale/messages-2.en.xlf]" [src/locale/messages.fr.xlf,src/locale/messages-2.fr.xlf]`',
|
||||
type: 'string',
|
||||
})
|
||||
|
||||
.option('target-locales', {
|
||||
@ -61,6 +65,7 @@ if (require.main === module) {
|
||||
describe:
|
||||
'A list of target locales for the translation files, which will override any target locale parsed from the translation file.\n' +
|
||||
'E.g. "-t en fr de".',
|
||||
type: 'string',
|
||||
})
|
||||
|
||||
.option('o', {
|
||||
@ -68,7 +73,8 @@ if (require.main === module) {
|
||||
required: true,
|
||||
describe: 'A output path pattern to where the translated files will be written.\n' +
|
||||
'The path must be either absolute or relative to the current working directory.\n' +
|
||||
'The marker `{{LOCALE}}` will be replaced with the target locale. E.g. `dist/{{LOCALE}}`.'
|
||||
'The marker `{{LOCALE}}` will be replaced with the target locale. E.g. `dist/{{LOCALE}}`.',
|
||||
type: 'string',
|
||||
})
|
||||
|
||||
.option('m', {
|
||||
@ -76,6 +82,7 @@ if (require.main === module) {
|
||||
describe: 'How to handle missing translations.',
|
||||
choices: ['error', 'warning', 'ignore'],
|
||||
default: 'warning',
|
||||
type: 'string',
|
||||
})
|
||||
|
||||
.option('d', {
|
||||
@ -83,6 +90,7 @@ if (require.main === module) {
|
||||
describe: 'How to handle duplicate translations.',
|
||||
choices: ['error', 'warning', 'ignore'],
|
||||
default: 'warning',
|
||||
type: 'string',
|
||||
})
|
||||
|
||||
.strict()
|
||||
@ -97,8 +105,8 @@ if (require.main === module) {
|
||||
const translationFilePaths: (string|string[])[] = convertArraysFromArgs(options['t']);
|
||||
const outputPathFn = getOutputPathFn(fs.resolve(options['o']));
|
||||
const diagnostics = new Diagnostics();
|
||||
const missingTranslation: DiagnosticHandlingStrategy = options['m'];
|
||||
const duplicateTranslation: DiagnosticHandlingStrategy = options['d'];
|
||||
const missingTranslation = options['m'] as DiagnosticHandlingStrategy;
|
||||
const duplicateTranslation = options['d'] as DiagnosticHandlingStrategy;
|
||||
const sourceLocale: string|undefined = options['l'];
|
||||
const translationFileLocales: string[] = options['target-locales'] || [];
|
||||
|
||||
|
@ -168,7 +168,7 @@ export class RouterLink implements OnChanges {
|
||||
private preserve!: boolean;
|
||||
|
||||
/** @internal */
|
||||
onChanges = new Subject<void>();
|
||||
onChanges = new Subject<RouterLink>();
|
||||
|
||||
constructor(
|
||||
private router: Router, private route: ActivatedRoute,
|
||||
@ -182,7 +182,7 @@ export class RouterLink implements OnChanges {
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
// This is subscribed to by `RouterLinkActive` so that it knows to update when there are changes
|
||||
// to the RouterLinks it's tracking.
|
||||
this.onChanges.next();
|
||||
this.onChanges.next(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -309,7 +309,7 @@ export class RouterLinkWithHref implements OnChanges, OnDestroy {
|
||||
@HostBinding() href!: string;
|
||||
|
||||
/** @internal */
|
||||
onChanges = new Subject<void>();
|
||||
onChanges = new Subject<RouterLinkWithHref>();
|
||||
|
||||
constructor(
|
||||
private router: Router, private route: ActivatedRoute,
|
||||
@ -351,7 +351,7 @@ export class RouterLinkWithHref implements OnChanges, OnDestroy {
|
||||
/** @nodoc */
|
||||
ngOnChanges(changes: SimpleChanges): any {
|
||||
this.updateTargetUrlAndHref();
|
||||
this.onChanges.next();
|
||||
this.onChanges.next(this);
|
||||
}
|
||||
/** @nodoc */
|
||||
ngOnDestroy(): any {
|
||||
|
@ -119,8 +119,11 @@ export class RouterLinkActive implements OnChanges, OnDestroy, AfterContentInit
|
||||
[...this.links.toArray(), ...this.linksWithHrefs.toArray(), this.link, this.linkWithHref]
|
||||
.filter((link): link is RouterLink|RouterLinkWithHref => !!link)
|
||||
.map(link => link.onChanges);
|
||||
this.linkInputChangesSubscription =
|
||||
from(allLinkChanges).pipe(mergeAll()).subscribe(() => this.update());
|
||||
this.linkInputChangesSubscription = from(allLinkChanges).pipe(mergeAll()).subscribe(link => {
|
||||
if (this.isActive !== this.isLinkActive(this.router)(link)) {
|
||||
this.update();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Input()
|
||||
|
@ -3974,7 +3974,7 @@ describe('Integration', () => {
|
||||
})));
|
||||
});
|
||||
|
||||
describe('routerActiveLink', () => {
|
||||
describe('routerLinkActive', () => {
|
||||
it('should set the class when the link is active (a tag)',
|
||||
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
||||
const fixture = createRoot(router, RootCmp);
|
||||
@ -4124,6 +4124,29 @@ describe('Integration', () => {
|
||||
advance(fixture);
|
||||
expect(paragraph.textContent).toEqual('false');
|
||||
}));
|
||||
|
||||
it('should not trigger change detection when active state has not changed', fakeAsync(() => {
|
||||
@Component({
|
||||
template: `<div id="link" routerLinkActive="active" [routerLink]="link"></div>`,
|
||||
})
|
||||
class LinkComponent {
|
||||
link = 'notactive';
|
||||
}
|
||||
|
||||
@Component({template: ''})
|
||||
class SimpleComponent {
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [RouterTestingModule.withRoutes([{path: '', component: SimpleComponent}])],
|
||||
declarations: [LinkComponent, SimpleComponent]
|
||||
});
|
||||
|
||||
const fixture = createRoot(TestBed.inject(Router), LinkComponent);
|
||||
fixture.componentInstance.link = 'stillnotactive';
|
||||
fixture.detectChanges(false /** checkNoChanges */);
|
||||
expect(TestBed.inject(NgZone).hasPendingMicrotasks).toBe(false);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('lazy loading', () => {
|
||||
|
142
yarn.lock
142
yarn.lock
@ -2198,10 +2198,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/hammerjs/-/hammerjs-2.0.35.tgz#7b7c950c7d54593e23bffc8d2b4feba9866a7277"
|
||||
integrity sha512-4mUIMSZ2U4UOWq1b+iV7XUTE4w+Kr3x+Zb/Qz5ROO6BTZLw2c8/ftjq0aRgluguLs4KRuBnrOy/s389HVn1/zA==
|
||||
|
||||
"@types/inquirer@^6.5.0":
|
||||
version "6.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-6.5.0.tgz#b83b0bf30b88b8be7246d40e51d32fe9d10e09be"
|
||||
integrity sha512-rjaYQ9b9y/VFGOpqBEXRavc3jh0a+e6evAbI31tMda8VlPaSy0AZJfXsvmIe3wklc7W6C3zCSfleuMXR7NOyXw==
|
||||
"@types/inquirer@^7.3.0":
|
||||
version "7.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-7.3.0.tgz#a1233632ea6249f14eb481dae91138e747b85664"
|
||||
integrity sha512-wcPs5jTrZYQBzzPlvUEzBcptzO4We2sijSvkBq8oAKRMJoH8PvrmP6QQnxLB5RScNUmRfujxA+ngxD4gk4xe7Q==
|
||||
dependencies:
|
||||
"@types/through" "*"
|
||||
rxjs "^6.4.0"
|
||||
@ -2347,10 +2347,17 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/yaml/-/yaml-1.2.0.tgz#4ed577fc4ebbd6b829b28734e56d10c9e6984e09"
|
||||
integrity sha512-GW8b9qM+ebgW3/zjzPm0I1NxMvLaz/YKT9Ph6tTb+Fkeyzd9yLTvQ6ciQ2MorTRmb/qXmfjMerRpG4LviixaqQ==
|
||||
|
||||
"@types/yargs@^11.1.1":
|
||||
version "11.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-11.1.5.tgz#8d71dfe4848ac5d714b75eca3df9cac75a4f8dac"
|
||||
integrity sha512-1jmXgoIyzxQSm33lYgEXvegtkhloHbed2I0QGlTN66U2F9/ExqJWSCSmaWC0IB/g1tW+IYSp+tDhcZBYB1ZGog==
|
||||
"@types/yargs-parser@*":
|
||||
version "15.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d"
|
||||
integrity sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==
|
||||
|
||||
"@types/yargs@^15.0.5":
|
||||
version "15.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.5.tgz#947e9a6561483bdee9adffc983e91a6902af8b79"
|
||||
integrity sha512-Dk/IDOPtOgubt/IaevIUbTgV7doaKkoorvOyYM2CMwuDyP89bekI7H4xLIwunNYiK9jhCkmc6pUrJk3cj2AB9w==
|
||||
dependencies:
|
||||
"@types/yargs-parser" "*"
|
||||
|
||||
"@types/yauzl@^2.9.1":
|
||||
version "2.9.1"
|
||||
@ -2749,7 +2756,7 @@ ansi-colors@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf"
|
||||
integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==
|
||||
|
||||
ansi-escapes@^3.1.0, ansi-escapes@^3.2.0:
|
||||
ansi-escapes@^3.0.0, ansi-escapes@^3.1.0, ansi-escapes@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b"
|
||||
integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==
|
||||
@ -4015,6 +4022,14 @@ chalk@^3.0.0:
|
||||
ansi-styles "^4.1.0"
|
||||
supports-color "^7.1.0"
|
||||
|
||||
chalk@^4.0.0, chalk@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a"
|
||||
integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==
|
||||
dependencies:
|
||||
ansi-styles "^4.1.0"
|
||||
supports-color "^7.1.0"
|
||||
|
||||
char-spinner@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/char-spinner/-/char-spinner-1.0.1.tgz#e6ea67bd247e107112983b7ab0479ed362800081"
|
||||
@ -4259,6 +4274,11 @@ cli-width@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
|
||||
integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=
|
||||
|
||||
cli-width@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6"
|
||||
integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==
|
||||
|
||||
cliui@^4.0.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49"
|
||||
@ -4465,7 +4485,7 @@ compare-semver@^1.0.0:
|
||||
dependencies:
|
||||
semver "^5.0.1"
|
||||
|
||||
compare-versions@^3.5.1:
|
||||
compare-versions@^3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.6.0.tgz#1a5689913685e5a87637b8d3ffca75514ec41d62"
|
||||
integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==
|
||||
@ -8049,14 +8069,14 @@ humanize-ms@^1.2.1:
|
||||
dependencies:
|
||||
ms "^2.0.0"
|
||||
|
||||
husky@^4.2.3:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/husky/-/husky-4.2.3.tgz#3b18d2ee5febe99e27f2983500202daffbc3151e"
|
||||
integrity sha512-VxTsSTRwYveKXN4SaH1/FefRJYCtx+wx04sSVcOpD7N2zjoHxa+cEJ07Qg5NmV3HAK+IRKOyNVpi2YBIVccIfQ==
|
||||
husky@^4.2.5:
|
||||
version "4.2.5"
|
||||
resolved "https://registry.yarnpkg.com/husky/-/husky-4.2.5.tgz#2b4f7622673a71579f901d9885ed448394b5fa36"
|
||||
integrity sha512-SYZ95AjKcX7goYVZtVZF2i6XiZcHknw50iXvY7b0MiGoj5RwdgRQNEHdb+gPDPCXKlzwrybjFjkL6FOj8uRhZQ==
|
||||
dependencies:
|
||||
chalk "^3.0.0"
|
||||
chalk "^4.0.0"
|
||||
ci-info "^2.0.0"
|
||||
compare-versions "^3.5.1"
|
||||
compare-versions "^3.6.0"
|
||||
cosmiconfig "^6.0.0"
|
||||
find-versions "^3.2.0"
|
||||
opencollective-postinstall "^2.0.2"
|
||||
@ -8236,7 +8256,17 @@ ini@1.3.5, ini@^1.3.2, ini@^1.3.4, ini@~1.3.0, ini@~1.3.3:
|
||||
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
|
||||
integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
|
||||
|
||||
inquirer@7.1.0, inquirer@^7.1.0:
|
||||
inquirer-autocomplete-prompt@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/inquirer-autocomplete-prompt/-/inquirer-autocomplete-prompt-1.0.2.tgz#3f2548f73dd12f0a541be055ea9c8c7aedeb42bf"
|
||||
integrity sha512-vNmAhhrOQwPnUm4B9kz1UB7P98rVF1z8txnjp53r40N0PBCuqoRWqjg3Tl0yz0UkDg7rEUtZ2OZpNc7jnOU9Zw==
|
||||
dependencies:
|
||||
ansi-escapes "^3.0.0"
|
||||
chalk "^2.0.0"
|
||||
figures "^2.0.0"
|
||||
run-async "^2.3.0"
|
||||
|
||||
inquirer@7.1.0:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.1.0.tgz#1298a01859883e17c7264b82870ae1034f92dd29"
|
||||
integrity sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg==
|
||||
@ -8255,6 +8285,25 @@ inquirer@7.1.0, inquirer@^7.1.0:
|
||||
strip-ansi "^6.0.0"
|
||||
through "^2.3.6"
|
||||
|
||||
inquirer@^7.3.3:
|
||||
version "7.3.3"
|
||||
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003"
|
||||
integrity sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==
|
||||
dependencies:
|
||||
ansi-escapes "^4.2.1"
|
||||
chalk "^4.1.0"
|
||||
cli-cursor "^3.1.0"
|
||||
cli-width "^3.0.0"
|
||||
external-editor "^3.0.3"
|
||||
figures "^3.0.0"
|
||||
lodash "^4.17.19"
|
||||
mute-stream "0.0.8"
|
||||
run-async "^2.4.0"
|
||||
rxjs "^6.6.0"
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
through "^2.3.6"
|
||||
|
||||
inquirer@~6.3.1:
|
||||
version "6.3.1"
|
||||
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.3.1.tgz#7a413b5e7950811013a3db491c61d1f3b776e8e7"
|
||||
@ -9872,6 +9921,11 @@ lodash@^4.0.0, lodash@^4.14.0, lodash@^4.16.6, lodash@^4.17.10, lodash@^4.17.11,
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
|
||||
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
|
||||
|
||||
lodash@^4.17.19:
|
||||
version "4.17.19"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
|
||||
integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==
|
||||
|
||||
lodash@~1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-1.0.2.tgz#8f57560c83b59fc270bd3d561b690043430e2551"
|
||||
@ -13363,6 +13417,11 @@ run-async@^2.2.0, run-async@^2.4.0:
|
||||
dependencies:
|
||||
is-promise "^2.1.0"
|
||||
|
||||
run-async@^2.3.0:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455"
|
||||
integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==
|
||||
|
||||
run-queue@^1.0.0, run-queue@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47"
|
||||
@ -13384,6 +13443,13 @@ rxjs@6.5.5:
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
rxjs@^6.6.0:
|
||||
version "6.6.2"
|
||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.2.tgz#8096a7ac03f2cc4fe5860ef6e572810d9e01c0d2"
|
||||
integrity sha512-BHdBMVoWC2sL26w//BCu3YzKT4s2jip/WhwsGEDmeKYBhKDZeYezVUnHatYB7L85v5xs0BAQmg6BEYJEKxBabg==
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||
@ -16291,10 +16357,10 @@ yargs-parser@^15.0.1:
|
||||
camelcase "^5.0.0"
|
||||
decamelize "^1.2.0"
|
||||
|
||||
yargs-parser@^18.1.0:
|
||||
version "18.1.2"
|
||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.2.tgz#2f482bea2136dbde0861683abea7756d30b504f1"
|
||||
integrity sha512-hlIPNR3IzC1YuL1c2UwwDKpXlNFBqD1Fswwh1khz5+d8Cq/8yc/Mn0i+rQXduu8hcrFKvO7Eryk+09NecTQAAQ==
|
||||
yargs-parser@^18.1.2:
|
||||
version "18.1.3"
|
||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0"
|
||||
integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==
|
||||
dependencies:
|
||||
camelcase "^5.0.0"
|
||||
decamelize "^1.2.0"
|
||||
@ -16306,23 +16372,6 @@ yargs-parser@^9.0.2:
|
||||
dependencies:
|
||||
camelcase "^4.1.0"
|
||||
|
||||
yargs@15.3.0:
|
||||
version "15.3.0"
|
||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.3.0.tgz#403af6edc75b3ae04bf66c94202228ba119f0976"
|
||||
integrity sha512-g/QCnmjgOl1YJjGsnUg2SatC7NUYEiLXJqxNOQU9qSpjzGtGXda9b+OKccr1kLTy8BN9yqEyqfq5lxlwdc13TA==
|
||||
dependencies:
|
||||
cliui "^6.0.0"
|
||||
decamelize "^1.2.0"
|
||||
find-up "^4.1.0"
|
||||
get-caller-file "^2.0.1"
|
||||
require-directory "^2.1.1"
|
||||
require-main-filename "^2.0.0"
|
||||
set-blocking "^2.0.0"
|
||||
string-width "^4.2.0"
|
||||
which-module "^2.0.0"
|
||||
y18n "^4.0.0"
|
||||
yargs-parser "^18.1.0"
|
||||
|
||||
yargs@^11.0.0:
|
||||
version "11.1.1"
|
||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-11.1.1.tgz#5052efe3446a4df5ed669c995886cc0f13702766"
|
||||
@ -16374,6 +16423,23 @@ yargs@^14.2.3:
|
||||
y18n "^4.0.0"
|
||||
yargs-parser "^15.0.1"
|
||||
|
||||
yargs@^15.4.1:
|
||||
version "15.4.1"
|
||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8"
|
||||
integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==
|
||||
dependencies:
|
||||
cliui "^6.0.0"
|
||||
decamelize "^1.2.0"
|
||||
find-up "^4.1.0"
|
||||
get-caller-file "^2.0.1"
|
||||
require-directory "^2.1.1"
|
||||
require-main-filename "^2.0.0"
|
||||
set-blocking "^2.0.0"
|
||||
string-width "^4.2.0"
|
||||
which-module "^2.0.0"
|
||||
y18n "^4.0.0"
|
||||
yargs-parser "^18.1.2"
|
||||
|
||||
yauzl@^2.10.0:
|
||||
version "2.10.0"
|
||||
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
|
||||
|
Reference in New Issue
Block a user