Co-authored-by: spalger <spalger@users.noreply.github.com> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
e50af74664
commit
1282cb6d19
27 changed files with 1745 additions and 3 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -52,3 +52,7 @@ npm-debug.log*
|
|||
# apm plugin
|
||||
/x-pack/plugins/apm/tsconfig.json
|
||||
apm.tsconfig.json
|
||||
|
||||
# release notes script output
|
||||
report.csv
|
||||
report.asciidoc
|
||||
|
|
|
@ -301,6 +301,7 @@
|
|||
"@kbn/expect": "1.0.0",
|
||||
"@kbn/optimizer": "1.0.0",
|
||||
"@kbn/plugin-generator": "1.0.0",
|
||||
"@kbn/release-notes": "1.0.0",
|
||||
"@kbn/test": "1.0.0",
|
||||
"@kbn/utility-types": "1.0.0",
|
||||
"@microsoft/api-documenter": "7.7.2",
|
||||
|
|
23
packages/kbn-release-notes/package.json
Normal file
23
packages/kbn-release-notes/package.json
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"name": "@kbn/release-notes",
|
||||
"version": "1.0.0",
|
||||
"license": "Apache-2.0",
|
||||
"main": "target/index.js",
|
||||
"scripts": {
|
||||
"kbn:bootstrap": "tsc",
|
||||
"kbn:watch": "tsc --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@kbn/dev-utils": "1.0.0",
|
||||
"axios": "^0.19.2",
|
||||
"cheerio": "0.22.0",
|
||||
"dedent": "^0.7.0",
|
||||
"graphql": "^14.0.0",
|
||||
"graphql-tag": "^2.10.3",
|
||||
"terminal-link": "^2.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"markdown-it": "^10.0.0",
|
||||
"typescript": "3.9.5"
|
||||
}
|
||||
}
|
162
packages/kbn-release-notes/src/cli.ts
Normal file
162
packages/kbn-release-notes/src/cli.ts
Normal file
|
@ -0,0 +1,162 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import Fs from 'fs';
|
||||
import Path from 'path';
|
||||
import { inspect } from 'util';
|
||||
|
||||
import { run, createFlagError, createFailError, REPO_ROOT } from '@kbn/dev-utils';
|
||||
|
||||
import { FORMATS, SomeFormat } from './formats';
|
||||
import {
|
||||
iterRelevantPullRequests,
|
||||
getPr,
|
||||
Version,
|
||||
ClassifiedPr,
|
||||
streamFromIterable,
|
||||
asyncPipeline,
|
||||
IrrelevantPrSummary,
|
||||
isPrRelevant,
|
||||
classifyPr,
|
||||
} from './lib';
|
||||
|
||||
const rootPackageJson = JSON.parse(
|
||||
Fs.readFileSync(Path.resolve(REPO_ROOT, 'package.json'), 'utf8')
|
||||
);
|
||||
const extensions = FORMATS.map((f) => f.extension);
|
||||
|
||||
export function runReleaseNotesCli() {
|
||||
run(
|
||||
async ({ flags, log }) => {
|
||||
const token = flags.token;
|
||||
if (!token || typeof token !== 'string') {
|
||||
throw createFlagError('--token must be defined');
|
||||
}
|
||||
|
||||
const version = Version.fromFlag(flags.version);
|
||||
if (!version) {
|
||||
throw createFlagError('unable to parse --version, use format "v{major}.{minor}.{patch}"');
|
||||
}
|
||||
|
||||
const includeVersions = Version.fromFlags(flags.include || []);
|
||||
if (!includeVersions) {
|
||||
throw createFlagError('unable to parse --include, use format "v{major}.{minor}.{patch}"');
|
||||
}
|
||||
|
||||
const Formats: SomeFormat[] = [];
|
||||
for (const flag of Array.isArray(flags.format) ? flags.format : [flags.format]) {
|
||||
const Format = FORMATS.find((F) => F.extension === flag);
|
||||
if (!Format) {
|
||||
throw createFlagError(`--format must be one of "${extensions.join('", "')}"`);
|
||||
}
|
||||
Formats.push(Format);
|
||||
}
|
||||
|
||||
const filename = flags.filename;
|
||||
if (!filename || typeof filename !== 'string') {
|
||||
throw createFlagError('--filename must be a string');
|
||||
}
|
||||
|
||||
if (flags['debug-pr']) {
|
||||
const number = parseInt(String(flags['debug-pr']), 10);
|
||||
if (Number.isNaN(number)) {
|
||||
throw createFlagError('--debug-pr must be a pr number when specified');
|
||||
}
|
||||
|
||||
const summary = new IrrelevantPrSummary(log);
|
||||
const pr = await getPr(token, number);
|
||||
log.success(
|
||||
inspect(
|
||||
{
|
||||
version: version.label,
|
||||
includeVersions: includeVersions.map((v) => v.label),
|
||||
isPrRelevant: isPrRelevant(pr, version, includeVersions, summary),
|
||||
...classifyPr(pr, log),
|
||||
pr,
|
||||
},
|
||||
{ depth: 100 }
|
||||
)
|
||||
);
|
||||
summary.logStats();
|
||||
return;
|
||||
}
|
||||
|
||||
log.info(`Loading all PRs with label [${version.label}] to build release notes...`);
|
||||
|
||||
const summary = new IrrelevantPrSummary(log);
|
||||
const prsToReport: ClassifiedPr[] = [];
|
||||
const prIterable = iterRelevantPullRequests(token, version, log);
|
||||
for await (const pr of prIterable) {
|
||||
if (!isPrRelevant(pr, version, includeVersions, summary)) {
|
||||
continue;
|
||||
}
|
||||
prsToReport.push(classifyPr(pr, log));
|
||||
}
|
||||
summary.logStats();
|
||||
|
||||
if (!prsToReport.length) {
|
||||
throw createFailError(
|
||||
`All PRs with label [${version.label}] were filtered out by the config. Run again with --debug for more info.`
|
||||
);
|
||||
}
|
||||
|
||||
log.info(`Found ${prsToReport.length} prs to report on`);
|
||||
|
||||
for (const Format of Formats) {
|
||||
const format = new Format(version, prsToReport, log);
|
||||
const outputPath = Path.resolve(`${filename}.${Format.extension}`);
|
||||
await asyncPipeline(streamFromIterable(format.print()), Fs.createWriteStream(outputPath));
|
||||
log.success(`[${Format.extension}] report written to ${outputPath}`);
|
||||
}
|
||||
},
|
||||
{
|
||||
usage: `node scripts/release_notes --token {token} --version {version}`,
|
||||
flags: {
|
||||
alias: {
|
||||
version: 'v',
|
||||
include: 'i',
|
||||
},
|
||||
string: ['token', 'version', 'format', 'filename', 'include', 'debug-pr'],
|
||||
default: {
|
||||
filename: 'report',
|
||||
version: rootPackageJson.version,
|
||||
format: extensions,
|
||||
},
|
||||
help: `
|
||||
--token (required) The Github access token to use for requests
|
||||
--version, -v The version to fetch PRs by, PRs with version labels prior to
|
||||
this one will be ignored (see --include-version) (default ${
|
||||
rootPackageJson.version
|
||||
})
|
||||
--include, -i A version that is before --version but shouldn't be considered
|
||||
"released" and cause PRs with a matching label to be excluded from
|
||||
release notes. Use this when PRs are labeled with a version that
|
||||
is less that --version and is expected to be released after
|
||||
--version, can be specified multiple times.
|
||||
--format Only produce a certain format, options: "${extensions.join('", "')}"
|
||||
--filename Output filename, defaults to "report"
|
||||
--debug-pr Fetch and print the details for a single PR, disabling reporting
|
||||
`,
|
||||
},
|
||||
description: `
|
||||
Fetch details from Github PRs for generating release notes
|
||||
`,
|
||||
}
|
||||
);
|
||||
}
|
84
packages/kbn-release-notes/src/formats/asciidoc.ts
Normal file
84
packages/kbn-release-notes/src/formats/asciidoc.ts
Normal file
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import dedent from 'dedent';
|
||||
|
||||
import { Format } from './format';
|
||||
import {
|
||||
ASCIIDOC_SECTIONS,
|
||||
UNKNOWN_ASCIIDOC_SECTION,
|
||||
AREAS,
|
||||
UNKNOWN_AREA,
|
||||
} from '../release_notes_config';
|
||||
|
||||
function* lines(body: string) {
|
||||
for (const line of dedent(body).split('\n')) {
|
||||
yield `${line}\n`;
|
||||
}
|
||||
}
|
||||
|
||||
export class AsciidocFormat extends Format {
|
||||
static extension = 'asciidoc';
|
||||
|
||||
*print() {
|
||||
const sortedAreas = [
|
||||
...AREAS.slice().sort((a, b) => a.title.localeCompare(b.title)),
|
||||
UNKNOWN_AREA,
|
||||
];
|
||||
|
||||
yield* lines(`
|
||||
[[release-notes-${this.version.label}]]
|
||||
== ${this.version.label} Release Notes
|
||||
|
||||
Also see <<breaking-changes-${this.version.major}.${this.version.minor}>>.
|
||||
`);
|
||||
|
||||
for (const section of [...ASCIIDOC_SECTIONS, UNKNOWN_ASCIIDOC_SECTION]) {
|
||||
const prsInSection = this.prs.filter((pr) => pr.asciidocSection === section);
|
||||
if (!prsInSection.length) {
|
||||
continue;
|
||||
}
|
||||
|
||||
yield '\n';
|
||||
yield* lines(`
|
||||
[float]
|
||||
[[${section.id}-${this.version.label}]]
|
||||
=== ${section.title}
|
||||
`);
|
||||
|
||||
for (const area of sortedAreas) {
|
||||
const prsInArea = prsInSection.filter((pr) => pr.area === area);
|
||||
|
||||
if (!prsInArea.length) {
|
||||
continue;
|
||||
}
|
||||
|
||||
yield `${area.title}::\n`;
|
||||
for (const pr of prsInArea) {
|
||||
const fixes = pr.fixes.length ? `[Fixes ${pr.fixes.join(', ')}] ` : '';
|
||||
const strippedTitle = pr.title.replace(/^\s*\[[^\]]+\]\s*/, '');
|
||||
yield `* ${fixes}${strippedTitle} {pull}${pr.number}[#${pr.number}]\n`;
|
||||
if (pr.note) {
|
||||
yield ` - ${pr.note}\n`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
74
packages/kbn-release-notes/src/formats/csv.ts
Normal file
74
packages/kbn-release-notes/src/formats/csv.ts
Normal file
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { Format } from './format';
|
||||
|
||||
/**
|
||||
* Escape a value to conform to field and header encoding defined at https://tools.ietf.org/html/rfc4180
|
||||
*/
|
||||
function esc(value: string | number) {
|
||||
if (typeof value === 'number') {
|
||||
return String(value);
|
||||
}
|
||||
|
||||
if (!value.includes(',') && !value.includes('\n') && !value.includes('"')) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return `"${value.split('"').join('""')}"`;
|
||||
}
|
||||
|
||||
function row(...fields: Array<string | number>) {
|
||||
return fields.map(esc).join(',') + '\r\n';
|
||||
}
|
||||
|
||||
export class CsvFormat extends Format {
|
||||
static extension = 'csv';
|
||||
|
||||
*print() {
|
||||
// columns
|
||||
yield row(
|
||||
'areas',
|
||||
'versions',
|
||||
'user',
|
||||
'title',
|
||||
'number',
|
||||
'url',
|
||||
'date',
|
||||
'fixes',
|
||||
'labels',
|
||||
'state'
|
||||
);
|
||||
|
||||
for (const pr of this.prs) {
|
||||
yield row(
|
||||
pr.area.title,
|
||||
pr.versions.map((v) => v.label).join(', '),
|
||||
pr.user.name || pr.user.login,
|
||||
pr.title,
|
||||
pr.number,
|
||||
pr.url,
|
||||
pr.mergedAt,
|
||||
pr.fixes.join(', '),
|
||||
pr.labels.join(', '),
|
||||
pr.state
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
34
packages/kbn-release-notes/src/formats/format.ts
Normal file
34
packages/kbn-release-notes/src/formats/format.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { ToolingLog } from '@kbn/dev-utils';
|
||||
|
||||
import { Version, ClassifiedPr } from '../lib';
|
||||
|
||||
export abstract class Format {
|
||||
static extension: string;
|
||||
|
||||
constructor(
|
||||
protected readonly version: Version,
|
||||
protected readonly prs: ClassifiedPr[],
|
||||
protected readonly log: ToolingLog
|
||||
) {}
|
||||
|
||||
abstract print(): Iterator<string>;
|
||||
}
|
25
packages/kbn-release-notes/src/formats/index.ts
Normal file
25
packages/kbn-release-notes/src/formats/index.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { ArrayItem } from '../lib';
|
||||
import { AsciidocFormat } from './asciidoc';
|
||||
import { CsvFormat } from './csv';
|
||||
|
||||
export const FORMATS = [CsvFormat, AsciidocFormat] as const;
|
||||
export type SomeFormat = ArrayItem<typeof FORMATS>;
|
20
packages/kbn-release-notes/src/index.ts
Normal file
20
packages/kbn-release-notes/src/index.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export * from './cli';
|
66
packages/kbn-release-notes/src/lib/classify_pr.ts
Normal file
66
packages/kbn-release-notes/src/lib/classify_pr.ts
Normal file
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { ToolingLog } from '@kbn/dev-utils';
|
||||
|
||||
import {
|
||||
Area,
|
||||
AREAS,
|
||||
UNKNOWN_AREA,
|
||||
AsciidocSection,
|
||||
ASCIIDOC_SECTIONS,
|
||||
UNKNOWN_ASCIIDOC_SECTION,
|
||||
} from '../release_notes_config';
|
||||
import { PullRequest } from './pull_request';
|
||||
|
||||
export interface ClassifiedPr extends PullRequest {
|
||||
area: Area;
|
||||
asciidocSection: AsciidocSection;
|
||||
}
|
||||
|
||||
export function classifyPr(pr: PullRequest, log: ToolingLog): ClassifiedPr {
|
||||
const filter = (a: Area | AsciidocSection) =>
|
||||
a.labels.some((test) =>
|
||||
typeof test === 'string' ? pr.labels.includes(test) : pr.labels.some((l) => l.match(test))
|
||||
);
|
||||
|
||||
const areas = AREAS.filter(filter);
|
||||
const asciidocSections = ASCIIDOC_SECTIONS.filter(filter);
|
||||
|
||||
const pickOne = <T extends Area | AsciidocSection>(name: string, options: T[]) => {
|
||||
if (options.length > 1) {
|
||||
const matches = options.map((o) => o.title).join(', ');
|
||||
log.warning(`[${pr.terminalLink}] ambiguous ${name}, mulitple match [${matches}]`);
|
||||
return options[0];
|
||||
}
|
||||
|
||||
if (options.length === 0) {
|
||||
log.error(`[${pr.terminalLink}] unable to determine ${name} because none match`);
|
||||
return;
|
||||
}
|
||||
|
||||
return options[0];
|
||||
};
|
||||
|
||||
return {
|
||||
...pr,
|
||||
area: pickOne('area', areas) || UNKNOWN_AREA,
|
||||
asciidocSection: pickOne('asciidoc section', asciidocSections) || UNKNOWN_ASCIIDOC_SECTION,
|
||||
};
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { getFixReferences } from './get_fix_references';
|
||||
|
||||
it('returns all fixed issue mentions in the PR text', () => {
|
||||
expect(
|
||||
getFixReferences(`
|
||||
clOses #1
|
||||
closes: #2
|
||||
clOse #3
|
||||
close: #4
|
||||
clOsed #5
|
||||
closed: #6
|
||||
fiX #7
|
||||
fix: #8
|
||||
fiXes #9
|
||||
fixes: #10
|
||||
fiXed #11
|
||||
fixed: #12
|
||||
reSolve #13
|
||||
resolve: #14
|
||||
reSolves #15
|
||||
resolves: #16
|
||||
reSolved #17
|
||||
resolved: #18
|
||||
fixed
|
||||
#19
|
||||
`)
|
||||
).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"#1",
|
||||
"#2",
|
||||
"#3",
|
||||
"#4",
|
||||
"#5",
|
||||
"#6",
|
||||
"#7",
|
||||
"#8",
|
||||
"#9",
|
||||
"#10",
|
||||
"#11",
|
||||
"#12",
|
||||
"#13",
|
||||
"#14",
|
||||
"#15",
|
||||
"#16",
|
||||
"#17",
|
||||
"#18",
|
||||
]
|
||||
`);
|
||||
});
|
29
packages/kbn-release-notes/src/lib/get_fix_references.ts
Normal file
29
packages/kbn-release-notes/src/lib/get_fix_references.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
const FIXES_RE = /(?:closes|close|closed|fix|fixes|fixed|resolve|resolves|resolved)[ :]*(#\d*)/gi;
|
||||
|
||||
export function getFixReferences(prText: string) {
|
||||
const fixes: string[] = [];
|
||||
let match;
|
||||
while ((match = FIXES_RE.exec(prText))) {
|
||||
fixes.push(match[1]);
|
||||
}
|
||||
return fixes;
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import MarkdownIt from 'markdown-it';
|
||||
import dedent from 'dedent';
|
||||
|
||||
import { getNoteFromDescription } from './get_note_from_description';
|
||||
|
||||
it('extracts expected components from html', () => {
|
||||
const mk = new MarkdownIt();
|
||||
|
||||
expect(
|
||||
getNoteFromDescription(
|
||||
mk.render(dedent`
|
||||
My PR description
|
||||
|
||||
Fixes: #1234
|
||||
|
||||
## Release Note:
|
||||
|
||||
Checkout this feature
|
||||
`)
|
||||
)
|
||||
).toMatchInlineSnapshot(`"Checkout this feature"`);
|
||||
|
||||
expect(
|
||||
getNoteFromDescription(
|
||||
mk.render(dedent`
|
||||
My PR description
|
||||
|
||||
Fixes: #1234
|
||||
|
||||
#### Release Note:
|
||||
|
||||
We fixed an issue
|
||||
`)
|
||||
)
|
||||
).toMatchInlineSnapshot(`"We fixed an issue"`);
|
||||
|
||||
expect(
|
||||
getNoteFromDescription(
|
||||
mk.render(dedent`
|
||||
My PR description
|
||||
|
||||
Fixes: #1234
|
||||
|
||||
Release note: Checkout feature foo
|
||||
`)
|
||||
)
|
||||
).toMatchInlineSnapshot(`"Checkout feature foo"`);
|
||||
|
||||
expect(
|
||||
getNoteFromDescription(
|
||||
mk.render(dedent`
|
||||
# Summary
|
||||
|
||||
My PR description
|
||||
|
||||
release note : bar
|
||||
`)
|
||||
)
|
||||
).toMatchInlineSnapshot(`"bar"`);
|
||||
});
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import cheerio from 'cheerio';
|
||||
|
||||
export function getNoteFromDescription(descriptionHtml: string) {
|
||||
const $ = cheerio.load(descriptionHtml);
|
||||
for (const el of $('p,h1,h2,h3,h4,h5').toArray()) {
|
||||
const text = $(el).text();
|
||||
const match = text.match(/^(\s*release note(?:s)?\s*:?\s*)/i);
|
||||
|
||||
if (!match) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const note = text.replace(match[1], '').trim();
|
||||
return note || $(el).next().text().trim();
|
||||
}
|
||||
}
|
26
packages/kbn-release-notes/src/lib/index.ts
Normal file
26
packages/kbn-release-notes/src/lib/index.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export * from './pull_request';
|
||||
export * from './version';
|
||||
export * from './is_pr_relevant';
|
||||
export * from './streams';
|
||||
export * from './type_helpers';
|
||||
export * from './irrelevant_pr_summary';
|
||||
export * from './classify_pr';
|
61
packages/kbn-release-notes/src/lib/irrelevant_pr_summary.ts
Normal file
61
packages/kbn-release-notes/src/lib/irrelevant_pr_summary.ts
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { ToolingLog } from '@kbn/dev-utils';
|
||||
|
||||
import { PullRequest } from './pull_request';
|
||||
import { Version } from './version';
|
||||
|
||||
export class IrrelevantPrSummary {
|
||||
private readonly stats = {
|
||||
'skipped by label': new Map<string, number>(),
|
||||
'skipped by label regexp': new Map<string, number>(),
|
||||
'skipped by version': new Map<string, number>(),
|
||||
};
|
||||
|
||||
constructor(private readonly log: ToolingLog) {}
|
||||
|
||||
skippedByLabel(pr: PullRequest, label: string) {
|
||||
this.log.debug(`${pr.terminalLink} skipped, label [${label}] is ignored`);
|
||||
this.increment('skipped by label', label);
|
||||
}
|
||||
|
||||
skippedByLabelRegExp(pr: PullRequest, regexp: RegExp, label: string) {
|
||||
this.log.debug(`${pr.terminalLink} skipped, label [${label}] matches regexp [${regexp}]`);
|
||||
this.increment('skipped by label regexp', `${regexp}`);
|
||||
}
|
||||
|
||||
skippedByVersion(pr: PullRequest, earliestVersion: Version) {
|
||||
this.log.debug(`${pr.terminalLink} skipped, earliest version is [${earliestVersion.label}]`);
|
||||
this.increment('skipped by version', earliestVersion.label);
|
||||
}
|
||||
|
||||
private increment(stat: keyof IrrelevantPrSummary['stats'], key: string) {
|
||||
const n = this.stats[stat].get(key) || 0;
|
||||
this.stats[stat].set(key, n + 1);
|
||||
}
|
||||
|
||||
logStats() {
|
||||
for (const [description, stats] of Object.entries(this.stats)) {
|
||||
for (const [key, count] of stats) {
|
||||
this.log.warning(`${count} ${count === 1 ? 'pr was' : 'prs were'} ${description} [${key}]`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
61
packages/kbn-release-notes/src/lib/is_pr_relevant.ts
Normal file
61
packages/kbn-release-notes/src/lib/is_pr_relevant.ts
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { Version } from './version';
|
||||
import { PullRequest } from './pull_request';
|
||||
import { IGNORE_LABELS } from '../release_notes_config';
|
||||
import { IrrelevantPrSummary } from './irrelevant_pr_summary';
|
||||
|
||||
export function isPrRelevant(
|
||||
pr: PullRequest,
|
||||
version: Version,
|
||||
includeVersions: Version[],
|
||||
summary: IrrelevantPrSummary
|
||||
) {
|
||||
for (const label of IGNORE_LABELS) {
|
||||
if (typeof label === 'string') {
|
||||
if (pr.labels.includes(label)) {
|
||||
summary.skippedByLabel(pr, label);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (label instanceof RegExp) {
|
||||
const matching = pr.labels.find((l) => label.test(l));
|
||||
if (matching) {
|
||||
summary.skippedByLabelRegExp(pr, label, matching);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const [earliestVersion] = Version.sort(
|
||||
// filter out `includeVersions` so that they won't be considered the "earliest version", only
|
||||
// versions which are actually before the current `version` or the `version` itself are eligible
|
||||
pr.versions.filter((v) => !includeVersions.includes(v)),
|
||||
'asc'
|
||||
);
|
||||
|
||||
if (version !== earliestVersion) {
|
||||
summary.skippedByVersion(pr, earliestVersion);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
206
packages/kbn-release-notes/src/lib/pull_request.ts
Normal file
206
packages/kbn-release-notes/src/lib/pull_request.ts
Normal file
|
@ -0,0 +1,206 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { inspect } from 'util';
|
||||
|
||||
import Axios from 'axios';
|
||||
import gql from 'graphql-tag';
|
||||
import * as GraphqlPrinter from 'graphql/language/printer';
|
||||
import { DocumentNode } from 'graphql/language/ast';
|
||||
import makeTerminalLink from 'terminal-link';
|
||||
import { ToolingLog } from '@kbn/dev-utils';
|
||||
|
||||
import { Version } from './version';
|
||||
import { getFixReferences } from './get_fix_references';
|
||||
import { getNoteFromDescription } from './get_note_from_description';
|
||||
|
||||
const PrNodeFragment = gql`
|
||||
fragment PrNode on PullRequest {
|
||||
number
|
||||
url
|
||||
title
|
||||
bodyText
|
||||
bodyHTML
|
||||
mergedAt
|
||||
baseRefName
|
||||
state
|
||||
author {
|
||||
login
|
||||
... on User {
|
||||
name
|
||||
}
|
||||
}
|
||||
labels(first: 100) {
|
||||
nodes {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export interface PullRequest {
|
||||
number: number;
|
||||
url: string;
|
||||
title: string;
|
||||
targetBranch: string;
|
||||
mergedAt: string;
|
||||
state: string;
|
||||
labels: string[];
|
||||
fixes: string[];
|
||||
user: {
|
||||
name: string;
|
||||
login: string;
|
||||
};
|
||||
versions: Version[];
|
||||
terminalLink: string;
|
||||
note?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a single request to the Github v4 GraphQL API
|
||||
*/
|
||||
async function gqlRequest(
|
||||
token: string,
|
||||
query: DocumentNode,
|
||||
variables: Record<string, unknown> = {}
|
||||
) {
|
||||
const resp = await Axios.request({
|
||||
url: 'https://api.github.com/graphql',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'user-agent': '@kbn/release-notes',
|
||||
authorization: `bearer ${token}`,
|
||||
},
|
||||
data: {
|
||||
query: GraphqlPrinter.print(query),
|
||||
variables,
|
||||
},
|
||||
});
|
||||
|
||||
return resp.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the Github API response into the structure used by this tool
|
||||
*
|
||||
* @param node A GraphQL response from Github using the PrNode fragment
|
||||
*/
|
||||
function parsePullRequestNode(node: any): PullRequest {
|
||||
const terminalLink = makeTerminalLink(`#${node.number}`, node.url);
|
||||
|
||||
const labels: string[] = node.labels.nodes.map((l: { name: string }) => l.name);
|
||||
|
||||
return {
|
||||
number: node.number,
|
||||
url: node.url,
|
||||
terminalLink,
|
||||
title: node.title,
|
||||
targetBranch: node.baseRefName,
|
||||
state: node.state,
|
||||
mergedAt: node.mergedAt,
|
||||
labels,
|
||||
fixes: getFixReferences(node.bodyText),
|
||||
user: {
|
||||
login: node.author?.login || 'deleted user',
|
||||
name: node.author?.name,
|
||||
},
|
||||
versions: labels
|
||||
.map((l) => Version.fromLabel(l))
|
||||
.filter((v): v is Version => v instanceof Version),
|
||||
note: getNoteFromDescription(node.bodyHTML),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate all of the PRs which have the `version` label
|
||||
*/
|
||||
export async function* iterRelevantPullRequests(token: string, version: Version, log: ToolingLog) {
|
||||
let nextCursor: string | undefined;
|
||||
let hasNextPage = true;
|
||||
|
||||
while (hasNextPage) {
|
||||
const resp = await gqlRequest(
|
||||
token,
|
||||
gql`
|
||||
query($cursor: String, $labels: [String!]) {
|
||||
repository(owner: "elastic", name: "kibana") {
|
||||
pullRequests(first: 100, after: $cursor, labels: $labels, states: MERGED) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
endCursor
|
||||
}
|
||||
nodes {
|
||||
...PrNode
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${PrNodeFragment}
|
||||
`,
|
||||
{
|
||||
cursor: nextCursor,
|
||||
labels: [version.label],
|
||||
}
|
||||
);
|
||||
|
||||
const pullRequests = resp.data?.repository?.pullRequests;
|
||||
if (!pullRequests) {
|
||||
throw new Error(`unexpected github response, unable to fetch PRs: ${inspect(resp)}`);
|
||||
}
|
||||
|
||||
hasNextPage = pullRequests.pageInfo?.hasNextPage;
|
||||
nextCursor = pullRequests.pageInfo?.endCursor;
|
||||
|
||||
if (hasNextPage === undefined || (hasNextPage && !nextCursor)) {
|
||||
throw new Error(
|
||||
`github response does not include valid pagination information: ${inspect(resp)}`
|
||||
);
|
||||
}
|
||||
|
||||
for (const node of pullRequests.nodes) {
|
||||
yield parsePullRequestNode(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPr(token: string, number: number) {
|
||||
const resp = await gqlRequest(
|
||||
token,
|
||||
gql`
|
||||
query($number: Int!) {
|
||||
repository(owner: "elastic", name: "kibana") {
|
||||
pullRequest(number: $number) {
|
||||
...PrNode
|
||||
}
|
||||
}
|
||||
}
|
||||
${PrNodeFragment}
|
||||
`,
|
||||
{
|
||||
number,
|
||||
}
|
||||
);
|
||||
|
||||
const node = resp.data?.repository?.pullRequest;
|
||||
if (!node) {
|
||||
throw new Error(`unexpected github response, unable to fetch PR: ${inspect(resp)}`);
|
||||
}
|
||||
|
||||
return parsePullRequestNode(node);
|
||||
}
|
34
packages/kbn-release-notes/src/lib/streams.ts
Normal file
34
packages/kbn-release-notes/src/lib/streams.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { promisify } from 'util';
|
||||
import { Readable, pipeline } from 'stream';
|
||||
|
||||
/**
|
||||
* @types/node still doesn't have this method that was added
|
||||
* in 10.17.0 https://nodejs.org/api/stream.html#stream_stream_readable_from_iterable_options
|
||||
*/
|
||||
export function streamFromIterable(
|
||||
iter: Iterable<string | Buffer> | AsyncIterable<string | Buffer>
|
||||
): Readable {
|
||||
// @ts-ignore
|
||||
return Readable.from(iter);
|
||||
}
|
||||
|
||||
export const asyncPipeline = promisify(pipeline);
|
20
packages/kbn-release-notes/src/lib/type_helpers.ts
Normal file
20
packages/kbn-release-notes/src/lib/type_helpers.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export type ArrayItem<T extends readonly any[]> = T extends ReadonlyArray<infer X> ? X : never;
|
146
packages/kbn-release-notes/src/lib/version.test.ts
Normal file
146
packages/kbn-release-notes/src/lib/version.test.ts
Normal file
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { Version } from './version';
|
||||
|
||||
it('parses version labels, returns null on failure', () => {
|
||||
expect(Version.fromLabel('v1.0.2')).toMatchInlineSnapshot(`
|
||||
Version {
|
||||
"label": "v1.0.2",
|
||||
"major": 1,
|
||||
"minor": 0,
|
||||
"patch": 2,
|
||||
"tag": undefined,
|
||||
"tagNum": undefined,
|
||||
"tagOrder": Infinity,
|
||||
}
|
||||
`);
|
||||
expect(Version.fromLabel('v1.0.0')).toMatchInlineSnapshot(`
|
||||
Version {
|
||||
"label": "v1.0.0",
|
||||
"major": 1,
|
||||
"minor": 0,
|
||||
"patch": 0,
|
||||
"tag": undefined,
|
||||
"tagNum": undefined,
|
||||
"tagOrder": Infinity,
|
||||
}
|
||||
`);
|
||||
expect(Version.fromLabel('v9.0.2')).toMatchInlineSnapshot(`
|
||||
Version {
|
||||
"label": "v9.0.2",
|
||||
"major": 9,
|
||||
"minor": 0,
|
||||
"patch": 2,
|
||||
"tag": undefined,
|
||||
"tagNum": undefined,
|
||||
"tagOrder": Infinity,
|
||||
}
|
||||
`);
|
||||
expect(Version.fromLabel('v9.0.2-alpha0')).toMatchInlineSnapshot(`
|
||||
Version {
|
||||
"label": "v9.0.2-alpha0",
|
||||
"major": 9,
|
||||
"minor": 0,
|
||||
"patch": 2,
|
||||
"tag": "alpha",
|
||||
"tagNum": 0,
|
||||
"tagOrder": 1,
|
||||
}
|
||||
`);
|
||||
expect(Version.fromLabel('v9.0.2-beta1')).toMatchInlineSnapshot(`
|
||||
Version {
|
||||
"label": "v9.0.2-beta1",
|
||||
"major": 9,
|
||||
"minor": 0,
|
||||
"patch": 2,
|
||||
"tag": "beta",
|
||||
"tagNum": 1,
|
||||
"tagOrder": 2,
|
||||
}
|
||||
`);
|
||||
expect(Version.fromLabel('v9.0')).toMatchInlineSnapshot(`undefined`);
|
||||
expect(Version.fromLabel('some:area')).toMatchInlineSnapshot(`undefined`);
|
||||
});
|
||||
|
||||
it('sorts versions in ascending order', () => {
|
||||
const versions = [
|
||||
'v1.7.3',
|
||||
'v1.7.0',
|
||||
'v1.5.0',
|
||||
'v2.7.0',
|
||||
'v7.0.0-beta2',
|
||||
'v7.0.0-alpha1',
|
||||
'v2.0.0',
|
||||
'v0.0.0',
|
||||
'v7.0.0-beta1',
|
||||
'v7.0.0',
|
||||
].map((l) => Version.fromLabel(l)!);
|
||||
|
||||
const sorted = Version.sort(versions);
|
||||
|
||||
expect(sorted.map((v) => v.label)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"v0.0.0",
|
||||
"v1.5.0",
|
||||
"v1.7.0",
|
||||
"v1.7.3",
|
||||
"v2.0.0",
|
||||
"v2.7.0",
|
||||
"v7.0.0-alpha1",
|
||||
"v7.0.0-beta1",
|
||||
"v7.0.0-beta2",
|
||||
"v7.0.0",
|
||||
]
|
||||
`);
|
||||
|
||||
// ensure versions was not mutated
|
||||
expect(sorted).not.toEqual(versions);
|
||||
});
|
||||
|
||||
it('sorts versions in decending order', () => {
|
||||
const versions = [
|
||||
'v1.7.3',
|
||||
'v1.7.0',
|
||||
'v1.5.0',
|
||||
'v7.0.0-beta1',
|
||||
'v2.7.0',
|
||||
'v2.0.0',
|
||||
'v0.0.0',
|
||||
'v7.0.0',
|
||||
].map((l) => Version.fromLabel(l)!);
|
||||
|
||||
const sorted = Version.sort(versions, 'desc');
|
||||
|
||||
expect(sorted.map((v) => v.label)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"v7.0.0",
|
||||
"v7.0.0-beta1",
|
||||
"v2.7.0",
|
||||
"v2.0.0",
|
||||
"v1.7.3",
|
||||
"v1.7.0",
|
||||
"v1.5.0",
|
||||
"v0.0.0",
|
||||
]
|
||||
`);
|
||||
|
||||
// ensure versions was not mutated
|
||||
expect(sorted).not.toEqual(versions);
|
||||
});
|
123
packages/kbn-release-notes/src/lib/version.ts
Normal file
123
packages/kbn-release-notes/src/lib/version.ts
Normal file
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
const LABEL_RE = /^v(\d+)\.(\d+)\.(\d+)(?:-(alpha|beta)(\d+))?$/;
|
||||
|
||||
const versionCache = new Map<string, Version>();
|
||||
|
||||
const multiCompare = (...diffs: number[]) => {
|
||||
for (const diff of diffs) {
|
||||
if (diff !== 0) {
|
||||
return diff;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
export class Version {
|
||||
static fromFlag(flag: string | string[] | boolean | undefined) {
|
||||
if (typeof flag !== 'string') {
|
||||
return;
|
||||
}
|
||||
|
||||
return Version.fromLabel(flag) || Version.fromLabel(`v${flag}`);
|
||||
}
|
||||
|
||||
static fromFlags(flag: string | string[] | boolean | undefined) {
|
||||
const flags = Array.isArray(flag) ? flag : [flag];
|
||||
const versions: Version[] = [];
|
||||
|
||||
for (const f of flags) {
|
||||
const version = Version.fromFlag(f);
|
||||
if (!version) {
|
||||
return;
|
||||
}
|
||||
versions.push(version);
|
||||
}
|
||||
|
||||
return versions;
|
||||
}
|
||||
|
||||
static fromLabel(label: string) {
|
||||
const match = label.match(LABEL_RE);
|
||||
if (!match) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cached = versionCache.get(label);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const [, major, minor, patch, tag, tagNum] = match;
|
||||
const version = new Version(
|
||||
parseInt(major, 10),
|
||||
parseInt(minor, 10),
|
||||
parseInt(patch, 10),
|
||||
tag as 'alpha' | 'beta' | undefined,
|
||||
tagNum ? parseInt(tagNum, 10) : undefined
|
||||
);
|
||||
|
||||
versionCache.set(label, version);
|
||||
return version;
|
||||
}
|
||||
|
||||
static sort(versions: Version[], dir: 'asc' | 'desc' = 'asc') {
|
||||
const order = dir === 'asc' ? 1 : -1;
|
||||
|
||||
return versions.slice().sort((a, b) => a.compare(b) * order);
|
||||
}
|
||||
|
||||
public readonly label = `v${this.major}.${this.minor}.${this.patch}${
|
||||
this.tag ? `-${this.tag}${this.tagNum}` : ''
|
||||
}`;
|
||||
private readonly tagOrder: number;
|
||||
|
||||
constructor(
|
||||
public readonly major: number,
|
||||
public readonly minor: number,
|
||||
public readonly patch: number,
|
||||
public readonly tag: 'alpha' | 'beta' | undefined,
|
||||
public readonly tagNum: number | undefined
|
||||
) {
|
||||
switch (tag) {
|
||||
case undefined:
|
||||
this.tagOrder = Infinity;
|
||||
break;
|
||||
case 'alpha':
|
||||
this.tagOrder = 1;
|
||||
break;
|
||||
case 'beta':
|
||||
this.tagOrder = 2;
|
||||
break;
|
||||
default:
|
||||
throw new Error('unexpected tag');
|
||||
}
|
||||
}
|
||||
|
||||
compare(other: Version) {
|
||||
return multiCompare(
|
||||
this.major - other.major,
|
||||
this.minor - other.minor,
|
||||
this.patch - other.patch,
|
||||
this.tagOrder - other.tagOrder,
|
||||
(this.tagNum ?? 0) - (other.tagNum ?? 0)
|
||||
);
|
||||
}
|
||||
}
|
294
packages/kbn-release-notes/src/release_notes_config.ts
Normal file
294
packages/kbn-release-notes/src/release_notes_config.ts
Normal file
|
@ -0,0 +1,294 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Exclude any PR from release notes that has a matching label. String
|
||||
* labels must match exactly, for more complicated use a RegExp
|
||||
*/
|
||||
export const IGNORE_LABELS: Array<RegExp | string> = [
|
||||
'Team:Docs',
|
||||
':KibanaApp/fix-it-week',
|
||||
'reverted',
|
||||
/^test/,
|
||||
'non-issue',
|
||||
'jenkins',
|
||||
'build',
|
||||
'chore',
|
||||
'backport',
|
||||
'release_note:skip',
|
||||
'release_note:dev_docs',
|
||||
];
|
||||
|
||||
/**
|
||||
* Define areas that are used to categorize changes in the release notes
|
||||
* based on the labels a PR has. the `labels` array can contain strings, which
|
||||
* are matched exactly, or regular expressions. The first area, in definition
|
||||
* order, which has a `label` which matches and label on a PR is the area
|
||||
* assigned to that PR.
|
||||
*/
|
||||
|
||||
export interface Area {
|
||||
title: string;
|
||||
labels: Array<string | RegExp>;
|
||||
}
|
||||
|
||||
export const AREAS: Area[] = [
|
||||
{
|
||||
title: 'Design',
|
||||
labels: ['Team:Design', 'Project:Accessibility'],
|
||||
},
|
||||
{
|
||||
title: 'Logstash',
|
||||
labels: ['App:Logstash', 'Feature:Logstash Pipelines'],
|
||||
},
|
||||
{
|
||||
title: 'Management',
|
||||
labels: [
|
||||
'Feature:license',
|
||||
'Feature:Console',
|
||||
'Feature:Search Profiler',
|
||||
'Feature:watcher',
|
||||
'Feature:Index Patterns',
|
||||
'Feature:Kibana Management',
|
||||
'Feature:Dev Tools',
|
||||
'Feature:Inspector',
|
||||
'Feature:Index Management',
|
||||
'Feature:Snapshot and Restore',
|
||||
'Team:Elasticsearch UI',
|
||||
'Feature:FieldFormatters',
|
||||
'Feature:CCR',
|
||||
'Feature:ILM',
|
||||
'Feature:Transforms',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Monitoring',
|
||||
labels: ['Team:Monitoring', 'Feature:Telemetry', 'Feature:Stack Monitoring'],
|
||||
},
|
||||
{
|
||||
title: 'Operations',
|
||||
labels: ['Team:Operations', 'Feature:License'],
|
||||
},
|
||||
{
|
||||
title: 'Kibana UI',
|
||||
labels: ['Kibana UI', 'Team:Core UI', 'Feature:Header'],
|
||||
},
|
||||
{
|
||||
title: 'Platform',
|
||||
labels: [
|
||||
'Team:Platform',
|
||||
'Feature:Plugins',
|
||||
'Feature:New Platform',
|
||||
'Project:i18n',
|
||||
'Feature:ExpressionLanguage',
|
||||
'Feature:Saved Objects',
|
||||
'Team:Stack Services',
|
||||
'Feature:NP Migration',
|
||||
'Feature:Task Manager',
|
||||
'Team:Pulse',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Machine Learning',
|
||||
labels: [
|
||||
':ml',
|
||||
'Feature:Anomaly Detection',
|
||||
'Feature:Data Frames',
|
||||
'Feature:File Data Viz',
|
||||
'Feature:ml-results',
|
||||
'Feature:Data Frame Analytics',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Maps',
|
||||
labels: ['Team:Geo'],
|
||||
},
|
||||
{
|
||||
title: 'Canvas',
|
||||
labels: ['Team:Canvas'],
|
||||
},
|
||||
{
|
||||
title: 'QA',
|
||||
labels: ['Team:QA'],
|
||||
},
|
||||
{
|
||||
title: 'Security',
|
||||
labels: [
|
||||
'Team:Security',
|
||||
'Feature:Security/Spaces',
|
||||
'Feature:users and roles',
|
||||
'Feature:Security/Authentication',
|
||||
'Feature:Security/Authorization',
|
||||
'Feature:Security/Feature Controls',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Dashboard',
|
||||
labels: ['Feature:Dashboard', 'Feature:Drilldowns'],
|
||||
},
|
||||
{
|
||||
title: 'Discover',
|
||||
labels: ['Feature:Discover'],
|
||||
},
|
||||
{
|
||||
title: 'Kibana Home & Add Data',
|
||||
labels: ['Feature:Add Data', 'Feature:Home'],
|
||||
},
|
||||
{
|
||||
title: 'Querying & Filtering',
|
||||
labels: [
|
||||
'Feature:Query Bar',
|
||||
'Feature:Courier',
|
||||
'Feature:Filters',
|
||||
'Feature:Timepicker',
|
||||
'Feature:Highlight',
|
||||
'Feature:KQL',
|
||||
'Feature:Rollups',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Reporting',
|
||||
labels: ['Feature:Reporting', 'Team:Reporting Services'],
|
||||
},
|
||||
{
|
||||
title: 'Sharing',
|
||||
labels: ['Feature:Embedding', 'Feature:SharingURLs'],
|
||||
},
|
||||
{
|
||||
title: 'Visualizations',
|
||||
labels: [
|
||||
'Feature:Timelion',
|
||||
'Feature:TSVB',
|
||||
'Feature:Coordinate Map',
|
||||
'Feature:Region Map',
|
||||
'Feature:Vega',
|
||||
'Feature:Gauge Vis',
|
||||
'Feature:Tagcloud',
|
||||
'Feature:Vis Loader',
|
||||
'Feature:Vislib',
|
||||
'Feature:Vis Editor',
|
||||
'Feature:Aggregations',
|
||||
'Feature:Input Control',
|
||||
'Feature:Visualizations',
|
||||
'Feature:Markdown',
|
||||
'Feature:Data Table',
|
||||
'Feature:Heatmap',
|
||||
'Feature:Pie Chart',
|
||||
'Feature:XYAxis',
|
||||
'Feature:Graph',
|
||||
'Feature:New Feature',
|
||||
'Feature:MetricVis',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'SIEM',
|
||||
labels: ['Team:SIEM'],
|
||||
},
|
||||
{
|
||||
title: 'Code',
|
||||
labels: ['Team:Code'],
|
||||
},
|
||||
{
|
||||
title: 'Infrastructure',
|
||||
labels: ['App:Infrastructure', 'Feature:Infra UI', 'Feature:Service Maps'],
|
||||
},
|
||||
{
|
||||
title: 'Logs',
|
||||
labels: ['App:Logs', 'Feature:Logs UI'],
|
||||
},
|
||||
{
|
||||
title: 'Uptime',
|
||||
labels: ['App:Uptime', 'Feature:Uptime', 'Team:uptime'],
|
||||
},
|
||||
{
|
||||
title: 'Beats Management',
|
||||
labels: ['App:Beats', 'Feature:beats-cm', 'Team:Beats'],
|
||||
},
|
||||
{
|
||||
title: 'APM',
|
||||
labels: ['Team:apm', /^apm[:\-]/],
|
||||
},
|
||||
{
|
||||
title: 'Lens',
|
||||
labels: ['App:Lens', 'Feature:Lens'],
|
||||
},
|
||||
{
|
||||
title: 'Alerting',
|
||||
labels: ['App:Alerting', 'Feature:Alerting', 'Team:Alerting Services', 'Feature:Actions'],
|
||||
},
|
||||
{
|
||||
title: 'Metrics',
|
||||
labels: ['App:Metrics', 'Feature:Metrics UI', 'Team:logs-metrics-ui'],
|
||||
},
|
||||
{
|
||||
title: 'Data ingest',
|
||||
labels: ['Ingest', 'Feature:Ingest Node Pipelines'],
|
||||
},
|
||||
];
|
||||
|
||||
export const UNKNOWN_AREA: Area = {
|
||||
title: 'Unknown',
|
||||
labels: [],
|
||||
};
|
||||
|
||||
/**
|
||||
* Define the sections that will be assigned to PRs when generating the
|
||||
* asciidoc formatted report. The order of the sections determines the
|
||||
* order they will be rendered in the report
|
||||
*/
|
||||
|
||||
export interface AsciidocSection {
|
||||
title: string;
|
||||
labels: Array<string | RegExp>;
|
||||
id: string;
|
||||
}
|
||||
|
||||
export const ASCIIDOC_SECTIONS: AsciidocSection[] = [
|
||||
{
|
||||
id: 'enhancement',
|
||||
title: 'Enhancements',
|
||||
labels: ['release_note:enhancement'],
|
||||
},
|
||||
{
|
||||
id: 'bug',
|
||||
title: 'Bug fixes',
|
||||
labels: ['release_note:fix'],
|
||||
},
|
||||
{
|
||||
id: 'roadmap',
|
||||
title: 'Roadmap',
|
||||
labels: ['release_note:roadmap'],
|
||||
},
|
||||
{
|
||||
id: 'deprecation',
|
||||
title: 'Deprecations',
|
||||
labels: ['release_note:deprecation'],
|
||||
},
|
||||
{
|
||||
id: 'breaking',
|
||||
title: 'Breaking Changes',
|
||||
labels: ['release_note:breaking'],
|
||||
},
|
||||
];
|
||||
|
||||
export const UNKNOWN_ASCIIDOC_SECTION: AsciidocSection = {
|
||||
id: 'unknown',
|
||||
title: 'Unknown',
|
||||
labels: [],
|
||||
};
|
12
packages/kbn-release-notes/tsconfig.json
Normal file
12
packages/kbn-release-notes/tsconfig.json
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./target",
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"target": "ES2019"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
]
|
||||
}
|
1
packages/kbn-release-notes/yarn.lock
Symbolic link
1
packages/kbn-release-notes/yarn.lock
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../yarn.lock
|
21
scripts/release_notes.js
Normal file
21
scripts/release_notes.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
require('../src/setup_node_env/prebuilt_dev_only_entry');
|
||||
require('@kbn/release-notes').runReleaseNotesCli();
|
39
yarn.lock
39
yarn.lock
|
@ -5306,9 +5306,9 @@
|
|||
"@types/node" "*"
|
||||
|
||||
"@types/node@*", "@types/node@8.10.54", "@types/node@>=10.17.17 <10.20.0", "@types/node@>=8.9.0", "@types/node@^12.0.2":
|
||||
version "10.17.17"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.17.tgz#7a183163a9e6ff720d86502db23ba4aade5999b8"
|
||||
integrity sha512-gpNnRnZP3VWzzj5k3qrpRC6Rk3H/uclhAVo1aIvwzK5p5cOrs9yEyQ8H/HBsBY0u5rrWxXEiVPQ0dEB6pkjE8Q==
|
||||
version "10.17.26"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.26.tgz#a8a119960bff16b823be4c617da028570779bcfd"
|
||||
integrity sha512-myMwkO2Cr82kirHY8uknNRHEVtn0wV3DTQfkrjx17jmkstDRZ24gNUdl8AHXVyVclTYI/bNjgTPTAWvWLqXqkw==
|
||||
|
||||
"@types/nodemailer@^6.2.1":
|
||||
version "6.2.1"
|
||||
|
@ -16093,6 +16093,11 @@ graphql-tag@2.10.1, graphql-tag@^2.9.2:
|
|||
resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.10.1.tgz#10aa41f1cd8fae5373eaf11f1f67260a3cad5e02"
|
||||
integrity sha512-jApXqWBzNXQ8jYa/HLkZJaVw9jgwNqZkywa2zfFn16Iv1Zb7ELNHkJaXHR7Quvd5SIGsy6Ny7SUKATgnu05uEg==
|
||||
|
||||
graphql-tag@^2.10.3:
|
||||
version "2.10.3"
|
||||
resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.10.3.tgz#ea1baba5eb8fc6339e4c4cf049dabe522b0edf03"
|
||||
integrity sha512-4FOv3ZKfA4WdOKJeHdz6B3F/vxBLSgmBcGeAFPf4n1F64ltJUvOOerNj0rsJxONQGdhUMynQIvd6LzB+1J5oKA==
|
||||
|
||||
graphql-toolkit@0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/graphql-toolkit/-/graphql-toolkit-0.2.0.tgz#91364b69911d51bc915269a37963f4ea2d5f335c"
|
||||
|
@ -16139,6 +16144,13 @@ graphql@^0.13.2:
|
|||
dependencies:
|
||||
iterall "^1.2.1"
|
||||
|
||||
graphql@^14.0.0:
|
||||
version "14.6.0"
|
||||
resolved "https://registry.yarnpkg.com/graphql/-/graphql-14.6.0.tgz#57822297111e874ea12f5cd4419616930cd83e49"
|
||||
integrity sha512-VKzfvHEKybTKjQVpTFrA5yUq2S9ihcZvfJAtsDBBCuV6wauPu1xl/f9ehgVf0FcEJJs4vz6ysb/ZMkGigQZseg==
|
||||
dependencies:
|
||||
iterall "^1.2.2"
|
||||
|
||||
graphviz@^0.0.8:
|
||||
version "0.0.8"
|
||||
resolved "https://registry.yarnpkg.com/graphviz/-/graphviz-0.0.8.tgz#e599e40733ef80e1653bfe89a5f031ecf2aa4aaa"
|
||||
|
@ -18627,6 +18639,11 @@ iterall@^1.1.3, iterall@^1.2.1:
|
|||
resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.2.2.tgz#92d70deb8028e0c39ff3164fdbf4d8b088130cd7"
|
||||
integrity sha512-yynBb1g+RFUPY64fTrFv7nsjRrENBQJaX2UL+2Szc9REFrSNm1rpSXHGzhmAy7a9uv3vlvgBlXnf9RqmPH1/DA==
|
||||
|
||||
iterall@^1.2.2:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.3.0.tgz#afcb08492e2915cbd8a0884eb93a8c94d0d72fea"
|
||||
integrity sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==
|
||||
|
||||
jest-changed-files@^24.9.0:
|
||||
version "24.9.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-24.9.0.tgz#08d8c15eb79a7fa3fc98269bc14b451ee82f8039"
|
||||
|
@ -29064,6 +29081,14 @@ supports-hyperlinks@^1.0.1:
|
|||
has-flag "^2.0.0"
|
||||
supports-color "^5.0.0"
|
||||
|
||||
supports-hyperlinks@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.1.0.tgz#f663df252af5f37c5d49bbd7eeefa9e0b9e59e47"
|
||||
integrity sha512-zoE5/e+dnEijk6ASB6/qrK+oYdm2do1hjoLWrqUC/8WEIW1gbxFcKuBof7sW8ArN6e+AYvsE8HBGiVRWL/F5CA==
|
||||
dependencies:
|
||||
has-flag "^4.0.0"
|
||||
supports-color "^7.0.0"
|
||||
|
||||
suricata-sid-db@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/suricata-sid-db/-/suricata-sid-db-1.0.2.tgz#96ceda4db117a9f1282c8f9d785285e5ccf342b1"
|
||||
|
@ -29416,6 +29441,14 @@ term-size@^2.1.0:
|
|||
resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.0.tgz#1f16adedfe9bdc18800e1776821734086fcc6753"
|
||||
integrity sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw==
|
||||
|
||||
terminal-link@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994"
|
||||
integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==
|
||||
dependencies:
|
||||
ansi-escapes "^4.2.1"
|
||||
supports-hyperlinks "^2.0.0"
|
||||
|
||||
terser-webpack-plugin@^1.2.4, terser-webpack-plugin@^1.4.3:
|
||||
version "1.4.4"
|
||||
resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.4.tgz#2c63544347324baafa9a56baaddf1634c8abfc2f"
|
||||
|
|
Loading…
Reference in a new issue