Merge from vscode 718331d6f3ebd1b571530ab499edb266ddd493d5

This commit is contained in:
ADS Merger
2020-02-08 04:50:58 +00:00
parent 8c61538a27
commit 2af13c18d2
752 changed files with 16458 additions and 10063 deletions

View File

@@ -1,21 +1,28 @@
# VS Code Smoke Test
Make sure you are on **Node v10.x**.
Make sure you are on **Node v12.x**.
### Run
```bash
# Install Dependencies and Compile
yarn --cwd test/smoke
yarn --cwd test/automation
# Dev
# Dev (Electron)
yarn smoketest
# Build
yarn smoketest --build PATH_TO_NEW_BUILD_PARENT_FOLDER --stable-build PATH_TO_LAST_STABLE_BUILD_PARENT_FOLDER
# Dev (Web)
yarn smoketest --web --browser <chromium|firefox|webkit>
# Remote
yarn smoketest --build PATH_TO_NEW_BUILD_PARENT_FOLDER --remote
# Build (Electron)
yarn smoketest --build <path latest built version> --stable-build <path to previous stable version>
# Build (Web - read instructions below)
yarn smoketest --build <path to web server folder> --web --browser <chromium|firefox|webkit>
# Remote (Electron)
yarn smoketest --build <path latest built version> --remote
```
### Run for a release
@@ -27,18 +34,33 @@ git checkout release/1.22
yarn --cwd test/smoke
```
#### Electron
In addition to the new build to be released you will need the previous stable build so that the smoketest can test the data migration.
The recommended way to make these builds available for the smoketest is by downloading their archive version (\*.zip) and extracting
them into two folders. Pass the folder paths to the smoketest as follows:
```bash
yarn smoketest --build PATH_TO_NEW_RELEASE_PARENT_FOLDER --stable-build PATH_TO_LAST_STABLE_RELEASE_PARENT_FOLDER
yarn smoketest --build <path latest built version> --stable-build <path to previous stable version>
```
#### Web
**macOS**: if you have downloaded the server with web bits, make sure to run the following command before unzipping it to avoid security issues on startup:
```bash
xattr -d com.apple.quarantine <path to server with web folder zip>
```
There is no support for testing an old version to a new one yet, so simply configure the `--build` command line argument to point to
the web server folder which includes the web client bits (e.g. `vscode-server-darwin-web` for macOS).
**Note**: make sure to point to the server that includes the client bits!
### Debug
- `--verbose` logs all the low level driver calls made to Code;
- `-f PATTERN` filters the tests to be run. You can also use pretty much any mocha argument;
- `-f PATTERN` (alias `-g PATTERN`) filters the tests to be run. You can also use pretty much any mocha argument;
- `--screenshots SCREENSHOT_DIR` captures screenshots when tests fail.
### Develop

View File

@@ -27,7 +27,7 @@
"rimraf": "^2.6.1",
"strip-json-comments": "^2.0.1",
"tmp": "0.0.33",
"typescript": "2.9.2",
"typescript": "3.7.5",
"watch": "^1.0.2"
}
}

View File

@@ -28,9 +28,6 @@ export function setup() {
});
it('verifies that warning becomes an error once setting changed', async function () {
// settings might take a while to update?
this.timeout(40000);
const app = this.app as Application;
await app.workbench.settingsEditor.addUserSetting('css.lint.emptyRules', '"error"');
await app.workbench.quickopen.openFile('style.css');

View File

@@ -1,122 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import * as http from 'http';
import * as path from 'path';
import * as fs from 'fs';
import * as stripJsonComments from 'strip-json-comments';
import { Application } from '../../../../automation';
export function setup() {
describe('Debug', () => {
it('configure launch json', async function () {
const app = this.app as Application;
await app.workbench.debug.openDebugViewlet();
await app.workbench.quickopen.openFile('app.js');
await app.workbench.quickopen.runCommand('Debug: Open launch.json');
const launchJsonPath = path.join(app.workspacePathOrFolder, '.vscode', 'launch.json');
const content = fs.readFileSync(launchJsonPath, 'utf8');
const config = JSON.parse(stripJsonComments(content));
config.configurations[0].protocol = 'inspector';
fs.writeFileSync(launchJsonPath, JSON.stringify(config, undefined, 4), 'utf8');
// force load from disk since file events are sometimes missing
await app.workbench.quickopen.runCommand('File: Revert File');
await app.workbench.editor.waitForEditorContents('launch.json', contents => /"protocol": "inspector"/.test(contents));
assert.equal(config.configurations[0].request, 'launch');
assert.equal(config.configurations[0].type, 'node');
if (process.platform === 'win32') {
assert.equal(config.configurations[0].program, '${workspaceFolder}\\bin\\www');
} else {
assert.equal(config.configurations[0].program, '${workspaceFolder}/bin/www');
}
});
it('breakpoints', async function () {
const app = this.app as Application;
await app.workbench.quickopen.openFile('index.js');
await app.workbench.debug.setBreakpointOnLine(6);
});
let port: number;
it('start debugging', async function () {
const app = this.app as Application;
port = await app.workbench.debug.startDebugging();
await new Promise((c, e) => {
const request = http.get(`http://localhost:${port}`);
request.on('error', e);
app.workbench.debug.waitForStackFrame(sf => /index\.js$/.test(sf.name) && sf.lineNumber === 6, 'looking for index.js and line 6').then(c, e);
});
});
it('focus stack frames and variables', async function () {
const app = this.app as Application;
await app.workbench.debug.waitForVariableCount(4, 5);
await app.workbench.debug.focusStackFrame('layer.js', 'looking for layer.js');
await app.workbench.debug.waitForVariableCount(5, 6);
await app.workbench.debug.focusStackFrame('route.js', 'looking for route.js');
await app.workbench.debug.waitForVariableCount(3, 4);
await app.workbench.debug.focusStackFrame('index.js', 'looking for index.js');
await app.workbench.debug.waitForVariableCount(4, 5);
});
it('stepOver, stepIn, stepOut', async function () {
const app = this.app as Application;
await app.workbench.debug.stepIn();
const first = await app.workbench.debug.waitForStackFrame(sf => /response\.js$/.test(sf.name), 'looking for response.js');
await app.workbench.debug.stepOver();
await app.workbench.debug.waitForStackFrame(sf => /response\.js$/.test(sf.name) && sf.lineNumber === first.lineNumber + 1, `looking for response.js and line ${first.lineNumber + 1}`);
await app.workbench.debug.stepOut();
await app.workbench.debug.waitForStackFrame(sf => /index\.js$/.test(sf.name) && sf.lineNumber === 7, `looking for index.js and line 7`);
});
it('continue', async function () {
const app = this.app as Application;
await app.workbench.debug.continue();
await new Promise((c, e) => {
const request = http.get(`http://localhost:${port}`);
request.on('error', e);
app.workbench.debug.waitForStackFrame(sf => /index\.js$/.test(sf.name) && sf.lineNumber === 6, `looking for index.js and line 6`).then(c, e);
});
});
it('debug console', async function () {
const app = this.app as Application;
await app.workbench.debug.waitForReplCommand('2 + 2', r => r === '4');
});
it('debug console link', async function () {
const app = this.app as Application;
await app.workbench.debug.waitForReplCommand('"./app.js:5:1"', r => r.includes('app.js'));
await app.workbench.debug.waitForLink();
});
it('stop debugging', async function () {
const app = this.app as Application;
await app.workbench.debug.stopDebugging();
});
});
}

View File

@@ -1,80 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as cp from 'child_process';
import { Application } from '../../../../automation';
const DIFF_EDITOR_LINE_INSERT = '.monaco-diff-editor .editor.modified .line-insert';
const SYNC_STATUSBAR = 'div[id="workbench.parts.statusbar"] .statusbar-item[title$="Synchronize Changes"]';
export function setup() {
describe('Git', () => {
before(async function () {
const app = this.app as Application;
cp.execSync('git config user.name testuser', { cwd: app.workspacePathOrFolder });
cp.execSync('git config user.email monacotools@microsoft.com', { cwd: app.workspacePathOrFolder });
});
it('reflects working tree changes', async function () {
const app = this.app as Application;
await app.workbench.scm.openSCMViewlet();
await app.workbench.quickopen.openFile('app.js');
await app.workbench.editor.waitForTypeInEditor('app.js', '.foo{}');
await app.workbench.editors.saveOpenedFile();
await app.workbench.quickopen.openFile('index.pug');
await app.workbench.editor.waitForTypeInEditor('index.pug', 'hello world');
await app.workbench.editors.saveOpenedFile();
await app.workbench.scm.refreshSCMViewlet();
await app.workbench.scm.waitForChange('app.js', 'Modified');
await app.workbench.scm.waitForChange('index.pug', 'Modified');
});
it('opens diff editor', async function () {
const app = this.app as Application;
await app.workbench.scm.openSCMViewlet();
await app.workbench.scm.openChange('app.js');
await app.code.waitForElement(DIFF_EDITOR_LINE_INSERT);
});
it('stages correctly', async function () {
const app = this.app as Application;
await app.workbench.scm.openSCMViewlet();
await app.workbench.scm.waitForChange('app.js', 'Modified');
await app.workbench.scm.stage('app.js');
await app.workbench.scm.openChange('app.js');
await new Promise(c => setTimeout(c, 1000));
await app.workbench.scm.unstage('app.js');
});
it(`stages, commits changes and verifies outgoing change`, async function () {
const app = this.app as Application;
await app.workbench.scm.openSCMViewlet();
await app.workbench.scm.waitForChange('app.js', 'Modified');
await app.workbench.scm.openChange('app.js');
await app.workbench.scm.stage('app.js');
await app.workbench.scm.commit('first commit');
await app.code.waitForTextContent(SYNC_STATUSBAR, ' 0↓ 1↑');
await app.workbench.quickopen.runCommand('Git: Stage All Changes');
await app.workbench.scm.waitForChange('index.pug', 'Index Modified');
await app.workbench.scm.commit('second commit');
await app.code.waitForTextContent(SYNC_STATUSBAR, ' 0↓ 2↑');
cp.execSync('git reset --hard origin/master', { cwd: app.workspacePathOrFolder });
});
});
}

View File

@@ -12,7 +12,7 @@ export function setup() {
await app.workbench.editors.newUntitledFile();
const untitled = 'Untitled-1';
const textToTypeInUntitled = 'Hello, Untitled Code';
const textToTypeInUntitled = 'Hello from Untitled';
await app.workbench.editor.waitForTypeInEditor(untitled, textToTypeInUntitled);
const readmeMd = 'readme.md';
@@ -25,8 +25,8 @@ export function setup() {
await app.workbench.editors.waitForActiveTab(readmeMd, true);
await app.workbench.editor.waitForEditorContents(readmeMd, c => c.indexOf(textToType) > -1);
await app.workbench.editors.waitForTab(untitled, true);
await app.workbench.editors.selectTab(untitled, true);
await app.workbench.editors.waitForTab(untitled);
await app.workbench.editors.selectTab(untitled);
await app.workbench.editor.waitForEditorContents(untitled, c => c.indexOf(textToTypeInUntitled) > -1);
});
});

View File

@@ -8,7 +8,6 @@ import { join } from 'path';
export function setup(stableCodePath: string, testDataPath: string) {
describe('Data Migration: This test MUST run before releasing by providing the --stable-build command line argument', () => {
it(`verifies opened editors are restored`, async function () {
if (!stableCodePath) {
@@ -66,7 +65,7 @@ export function setup(stableCodePath: string, testDataPath: string) {
await stableApp.workbench.editors.newUntitledFile();
const untitled = 'Untitled-1';
const textToTypeInUntitled = 'Hello, Untitled Code';
const textToTypeInUntitled = 'Hello from Untitled';
await stableApp.workbench.editor.waitForTypeInEditor(untitled, textToTypeInUntitled);
const readmeMd = 'readme.md';
@@ -86,7 +85,7 @@ export function setup(stableCodePath: string, testDataPath: string) {
await insidersApp.workbench.editor.waitForEditorContents(readmeMd, c => c.indexOf(textToType) > -1);
await insidersApp.workbench.editors.waitForTab(untitled, true);
await insidersApp.workbench.editors.selectTab(untitled, true);
await insidersApp.workbench.editors.selectTab(untitled);
await insidersApp.workbench.editor.waitForEditorContents(untitled, c => c.indexOf(textToTypeInUntitled) > -1);
await insidersApp.stop();

View File

@@ -34,8 +34,6 @@ import { setup as setupDataPreferencesTests } from './areas/preferences/preferen
import { setup as setupDataSearchTests } from './areas/search/search.test';
import { setup as setupDataCSSTests } from './areas/css/css.test';
import { setup as setupDataEditorTests } from './areas/editor/editor.test';
import { setup as setupDataDebugTests } from './areas/debug/debug.test';
import { setup as setupDataGitTests } from './areas/git/git.test';
import { setup as setupDataStatusbarTests } from './areas/statusbar/statusbar.test';
import { setup as setupDataExtensionTests } from './areas/extensions/extensions.test';
import { setup as setupTerminalTests } from './areas/terminal/terminal.test';
@@ -43,8 +41,8 @@ import { setup as setupDataMultirootTests } from './areas/multiroot/multiroot.te
import { setup as setupDataLocalizationTests } from './areas/workbench/localization.test';
import { setup as setupLaunchTests } from './areas/workbench/launch.test';*///{{END}}
if (!/^v10/.test(process.version)) {
console.error('Error: Smoketest must be run using Node 10. Currently running', process.version);
if (!/^v10/.test(process.version) && !/^v12/.test(process.version)) {
console.error('Error: Smoketest must be run using Node 10/12. Currently running', process.version);
process.exit(1);
}
@@ -55,6 +53,7 @@ process.once('exit', () => rimraf.sync(testDataPath));
const [, , ...args] = process.argv;
const opts = minimist(args, {
string: [
'browser',
'build',
'stable-build',
'wait-time',
@@ -66,7 +65,8 @@ const opts = minimist(args, {
'verbose',
'remote',
'web',
'headless'
'headless',
'ci'
],
default: {
verbose: false
@@ -79,7 +79,6 @@ const extensionsPath = path.join(testDataPath, 'extensions-dir');
mkdirp.sync(extensionsPath);
const screenshotsPath = opts.screenshots ? path.resolve(opts.screenshots) : null;
if (screenshotsPath) {
mkdirp.sync(screenshotsPath);
}
@@ -89,83 +88,109 @@ function fail(errorMessage): void {
process.exit(1);
}
if (parseInt(process.version.substr(1)) < 6) {
fail('Please update your Node version to greater than 6 to run the smoke test.');
}
const repoPath = path.join(__dirname, '..', '..', '..');
function getDevElectronPath(): string {
const buildPath = path.join(repoPath, '.build');
const product = require(path.join(repoPath, 'product.json'));
let quality: Quality;
switch (process.platform) {
case 'darwin':
return path.join(buildPath, 'electron', `${product.nameLong}.app`, 'Contents', 'MacOS', 'Electron');
case 'linux':
return path.join(buildPath, 'electron', `${product.applicationName}`);
case 'win32':
return path.join(buildPath, 'electron', `${product.nameShort}.exe`);
default:
throw new Error('Unsupported platform.');
}
}
//
// #### Electron Smoke Tests ####
//
if (!opts.web) {
function getBuildElectronPath(root: string): string {
switch (process.platform) {
case 'darwin':
return path.join(root, 'Contents', 'MacOS', 'Electron');
case 'linux': {
const product = require(path.join(root, 'resources', 'app', 'product.json'));
return path.join(root, product.applicationName);
function getDevElectronPath(): string {
const buildPath = path.join(repoPath, '.build');
const product = require(path.join(repoPath, 'product.json'));
switch (process.platform) {
case 'darwin':
return path.join(buildPath, 'electron', `${product.nameLong}.app`, 'Contents', 'MacOS', 'Electron');
case 'linux':
return path.join(buildPath, 'electron', `${product.applicationName}`);
case 'win32':
return path.join(buildPath, 'electron', `${product.nameShort}.exe`);
default:
throw new Error('Unsupported platform.');
}
case 'win32': {
const product = require(path.join(root, 'resources', 'app', 'product.json'));
return path.join(root, `${product.nameShort}.exe`);
}
function getBuildElectronPath(root: string): string {
switch (process.platform) {
case 'darwin':
return path.join(root, 'Contents', 'MacOS', 'Electron');
case 'linux': {
const product = require(path.join(root, 'resources', 'app', 'product.json'));
return path.join(root, product.applicationName);
}
case 'win32': {
const product = require(path.join(root, 'resources', 'app', 'product.json'));
return path.join(root, `${product.nameShort}.exe`);
}
default:
throw new Error('Unsupported platform.');
}
default:
throw new Error('Unsupported platform.');
}
let testCodePath = opts.build;
let stableCodePath = opts['stable-build'];
let electronPath: string;
let stablePath: string | undefined = undefined;
if (testCodePath) {
electronPath = getBuildElectronPath(testCodePath);
if (stableCodePath) {
stablePath = getBuildElectronPath(stableCodePath);
}
} else {
testCodePath = getDevElectronPath();
electronPath = testCodePath;
process.env.VSCODE_REPOSITORY = repoPath;
process.env.VSCODE_DEV = '1';
process.env.VSCODE_CLI = '1';
}
if (!fs.existsSync(electronPath || '')) {
fail(`Can't find VSCode at ${electronPath}.`);
}
if (typeof stablePath === 'string' && !fs.existsSync(stablePath)) {
fail(`Can't find Stable VSCode at ${stablePath}.`);
}
if (process.env.VSCODE_DEV === '1') {
quality = Quality.Dev;
} else if (electronPath.indexOf('Code - Insiders') >= 0 /* macOS/Windows */ || electronPath.indexOf('code-insiders') /* Linux */ >= 0) {
quality = Quality.Insiders;
} else {
quality = Quality.Stable;
}
}
let testCodePath = opts.build;
let stableCodePath = opts['stable-build'];
let electronPath: string;
let stablePath: string | undefined = undefined;
//
// #### Web Smoke Tests ####
//
else {
const testCodeServerPath = opts.build || process.env.VSCODE_REMOTE_SERVER_PATH;
if (testCodePath) {
electronPath = getBuildElectronPath(testCodePath);
if (stableCodePath) {
stablePath = getBuildElectronPath(stableCodePath);
if (typeof testCodeServerPath === 'string' && !fs.existsSync(testCodeServerPath)) {
fail(`Can't find Code server at ${testCodeServerPath}.`);
}
} else {
testCodePath = getDevElectronPath();
electronPath = testCodePath;
process.env.VSCODE_REPOSITORY = repoPath;
process.env.VSCODE_DEV = '1';
process.env.VSCODE_CLI = '1';
}
if (!opts.web && !fs.existsSync(electronPath || '')) {
fail(`Can't find Code at ${electronPath}.`);
}
if (!testCodeServerPath) {
process.env.VSCODE_REPOSITORY = repoPath;
process.env.VSCODE_DEV = '1';
process.env.VSCODE_CLI = '1';
}
if (typeof stablePath === 'string' && !fs.existsSync(stablePath)) {
fail(`Can't find Stable Code at ${stablePath}.`);
if (process.env.VSCODE_DEV === '1') {
quality = Quality.Dev;
} else {
quality = Quality.Insiders;
}
}
const userDataDir = path.join(testDataPath, 'd');
let quality: Quality;
if (process.env.VSCODE_DEV === '1') {
quality = Quality.Dev;
} else if (electronPath.indexOf('Code - Insiders') >= 0 /* macOS/Windows */ || electronPath.indexOf('code-insiders') /* Linux */ >= 0) {
quality = Quality.Insiders;
} else {
quality = Quality.Stable;
}
async function setupRepository(): Promise<void> {
if (opts['test-repo']) {
console.log('*** Copying test project repository:', opts['test-repo']);
@@ -228,13 +253,13 @@ function createOptions(): ApplicationOptions {
screenshotsPath,
remote: opts.remote,
web: opts.web,
browser: opts.browser,
headless: opts.headless
};
}
before(async function () {
// allow two minutes for setup
this.timeout(2 * 60 * 1000);
this.timeout(2 * 60 * 1000); // allow two minutes for setup
await setup();
this.defaultOptions = createOptions();
});
@@ -250,12 +275,8 @@ after(async function () {
await new Promise((c, e) => rimraf(testDataPath, { maxBusyTries: 10 }, err => err ? e(err) : c()));
});
/*//{{SQL CARBON EDIT}}
if (!opts.web) {
setupDataMigrationTests(stableCodePath, testDataPath);
}*/
describe('Running Code', () => {
describe(`VSCode Smoke Tests (${opts.web ? 'Web' : 'Electron'})`, () => {
before(async function () {
const app = new Application(this.defaultOptions);
await app!.start(opts.web ? false : undefined);
@@ -300,27 +321,29 @@ describe('Running Code', () => {
app.logger.log('*** Test start:', title);
});
}
//{{SQL CARBON EDIT}}
runProfilerTests();
runQueryEditorTests();
/*
if (!opts.web) { setupDataLossTests(); }
setupDataExplorerTests();
if (!opts.web) { setupDataPreferencesTests(); }
setupDataSearchTests();
setupDataCSSTests();
setupDataEditorTests();
if (!opts.web) { setupDataDebugTests(); }
setupDataGitTests();
setupDataStatusbarTests(!!opts.web);
setupDataExtensionTests();
setupTerminalTests();
if (!opts.web) { setupDataMultirootTests(); }
setupDataLocalizationTests();
*/
//{{END}}
// CI only tests (must be reliable)
if (opts.ci) {
// TODO@Ben figure out tests that can run continously and reliably
}
// Non-CI execution (all tests)
else {
/*if (!opts.web) { setupDataMigrationTests(opts['stable-build'], testDataPath); } {{SQL CARBON EDIT}} comment out tests
if (!opts.web) { setupDataLossTests(); }
setupDataExplorerTests();
if (!opts.web) { setupDataPreferencesTests(); }
setupDataSearchTests();
setupDataCSSTests();
setupDataEditorTests();
setupDataStatusbarTests(!!opts.web);
if (!opts.web) { setupDataExtensionTests(); }
setupTerminalTests();
if (!opts.web) { setupDataMultirootTests(); }
if (!opts.web) { setupDataLocalizationTests(); }
if (!opts.web) { setupLaunchTests(); }*/
runProfilerTests(); // {{SQL CARBON EDIT}} add our tests
runQueryEditorTests(); // {{SQL CARBON EDIT}} add our tests
}
});
/*//{{SQL CARBON EDIT}}
if (!opts.web) {
setupLaunchTests();
}*/

View File

@@ -11,18 +11,14 @@ const suite = 'Smoke Tests';
const [, , ...args] = process.argv;
const opts = minimist(args, {
string: [
'f'
]
string: ['f', 'g']
});
const options = {
useColors: true,
//{{SQL CARBON EDIT}}
timeout: 60000 * 2,
//{{END}}
color: true,
timeout: 60000,
slow: 30000,
grep: opts['f']
grep: opts['f'] || opts['g']
};
if (process.env.BUILD_ARTIFACTSTAGINGDIRECTORY) {
@@ -38,4 +34,4 @@ if (process.env.BUILD_ARTIFACTSTAGINGDIRECTORY) {
const mocha = new Mocha(options);
mocha.addFile('out/main.js');
mocha.run(failures => process.exit(failures ? -1 : 0));
mocha.run(failures => process.exit(failures ? -1 : 0));

View File

@@ -2122,10 +2122,10 @@ tree-kill@^1.1.0:
resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.0.tgz#5846786237b4239014f05db156b643212d4c6f36"
integrity sha512-DlX6dR0lOIRDFxI0mjL9IYg6OTncLm/Zt+JiBhE5OlFcAR8yc9S7FFXU9so0oda47frdM/JFsk7UjNt9vscKcg==
typescript@2.9.2:
version "2.9.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.2.tgz#1cbf61d05d6b96269244eb6a3bce4bd914e0f00c"
integrity sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==
typescript@3.7.5:
version "3.7.5"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.5.tgz#0692e21f65fd4108b9330238aac11dd2e177a1ae"
integrity sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==
union-value@^1.0.0:
version "1.0.1"