Merge branch 'master' of github.com:Budibase/budibase into component-sdk

This commit is contained in:
Andrew Kingston 2020-11-27 15:59:32 +00:00
commit a0f3b16e51
23 changed files with 156 additions and 213 deletions

View File

@ -60,7 +60,7 @@ context("Create a Table", () => {
}) })
it("deletes a table", () => { it("deletes a table", () => {
cy.contains(".nav-item", "dog").get(".actions").invoke("show").click() cy.get(".actions").first().invoke("show").click()
cy.get("[data-cy=delete-table]").click() cy.get("[data-cy=delete-table]").click()
cy.contains("Delete Table").click() cy.contains("Delete Table").click()
cy.contains("dog").should("not.exist") cy.contains("dog").should("not.exist")

View File

@ -9,9 +9,9 @@ context('Create a User', () => {
// https://on.cypress.io/interacting-with-elements // https://on.cypress.io/interacting-with-elements
it('should create a user', () => { it('should create a user', () => {
cy.createUser('bbuser', 'test', 'POWER_USER') cy.createUser("bbuser", "test", "ADMIN")
// Check to make sure user was created! // // Check to make sure user was created!
cy.get("input[disabled]").should('have.value', 'bbuser') cy.contains("bbuser").should('be.visible')
}) })
}) })

View File

@ -113,24 +113,27 @@ Cypress.Commands.add("addRow", values => {
Cypress.Commands.add("createUser", (username, password, accessLevel) => { Cypress.Commands.add("createUser", (username, password, accessLevel) => {
// Create User // Create User
cy.get(".toprightnav > .settings").click()
cy.contains("Users").click() cy.contains("Users").click()
cy.get("[name=Name]") cy.contains("Create New Row").click()
.first()
.type(username) cy.get(".modal").within(() => {
cy.get("[name=Password]") cy.get("input")
.first() .first()
.type(password) .type(password)
cy.get("input")
.eq(1)
.type(username)
cy.get("select") cy.get("select")
.first() .first()
.select(accessLevel) .select(accessLevel)
// Save // Save
cy.get(".inputs") cy.get(".buttons")
.contains("Create") .contains("Create Row")
.click() .click()
}) })
})
Cypress.Commands.add("addHeadlineComponent", text => { Cypress.Commands.add("addHeadlineComponent", text => {
cy.get(".switcher > :nth-child(2)").click() cy.get(".switcher > :nth-child(2)").click()

View File

@ -1,14 +1,19 @@
<script> <script>
import { Input, Select, Label, DatePicker, Toggle } from "@budibase/bbui" import { Input, Select, Label, DatePicker, Toggle } from "@budibase/bbui"
import { backendUiStore } from "builderStore"
import { TableNames } from "constants"
import Dropzone from "components/common/Dropzone.svelte" import Dropzone from "components/common/Dropzone.svelte"
import { capitalise } from "../../../helpers" import { capitalise } from "../../../helpers"
import LinkedRowSelector from "components/common/LinkedRowSelector.svelte" import LinkedRowSelector from "components/common/LinkedRowSelector.svelte"
export let meta export let meta
export let creating
export let value = meta.type === "boolean" ? false : "" export let value = meta.type === "boolean" ? false : ""
$: type = meta.type $: type = meta.type
$: label = capitalise(meta.name) $: label = capitalise(meta.name)
$: editingUser =
!creating && $backendUiStore.selectedTable?._id === TableNames.USERS
</script> </script>
{#if type === 'options'} {#if type === 'options'}
@ -30,5 +35,11 @@
{:else if type === 'link'} {:else if type === 'link'}
<LinkedRowSelector bind:linkedRows={value} schema={meta} /> <LinkedRowSelector bind:linkedRows={value} schema={meta} />
{:else} {:else}
<Input thin {label} data-cy="{meta.name}-input" {type} bind:value /> <Input
thin
{label}
data-cy="{meta.name}-input"
{type}
bind:value
disabled={editingUser} />
{/if} {/if}

View File

@ -7,8 +7,8 @@ export async function createUser(user) {
} }
export async function saveRow(row, tableId) { export async function saveRow(row, tableId) {
const SAVE_ROWS_URL = `/api/${tableId}/rows` const SAVE_ROW_URL = `/api/${tableId}/rows`
const response = await api.post(SAVE_ROWS_URL, row) const response = await api.post(SAVE_ROW_URL, row)
return await response.json() return await response.json()
} }

View File

@ -2,6 +2,7 @@
import { Input, Button, TextButton, Select, Toggle } from "@budibase/bbui" import { Input, Button, TextButton, Select, Toggle } from "@budibase/bbui"
import { cloneDeep } from "lodash/fp" import { cloneDeep } from "lodash/fp"
import { backendUiStore } from "builderStore" import { backendUiStore } from "builderStore"
import { TableNames, UNEDITABLE_USER_FIELDS } from "constants"
import { FIELDS } from "constants/backend" import { FIELDS } from "constants/backend"
import { notifier } from "builderStore/store/notifications" import { notifier } from "builderStore/store/notifications"
import ValuesList from "components/common/ValuesList.svelte" import ValuesList from "components/common/ValuesList.svelte"
@ -30,6 +31,9 @@
table => table._id !== $backendUiStore.draftTable._id table => table._id !== $backendUiStore.draftTable._id
) )
$: required = !!field?.constraints?.presence || primaryDisplay $: required = !!field?.constraints?.presence || primaryDisplay
$: uneditable =
$backendUiStore.selectedTable?._id === TableNames.USERS &&
UNEDITABLE_USER_FIELDS.includes(field.name)
async function saveColumn() { async function saveColumn() {
backendUiStore.update(state => { backendUiStore.update(state => {
@ -87,7 +91,7 @@
</script> </script>
<div class="actions" class:hidden={deletion}> <div class="actions" class:hidden={deletion}>
<Input label="Name" thin bind:value={field.name} /> <Input label="Name" thin bind:value={field.name} disabled={uneditable} />
<Select <Select
disabled={originalName} disabled={originalName}
@ -101,7 +105,7 @@
{/each} {/each}
</Select> </Select>
{#if field.type !== 'link'} {#if field.type !== 'link' && !uneditable}
<Toggle <Toggle
checked={required} checked={required}
on:change={onChangeRequired} on:change={onChangeRequired}
@ -157,7 +161,7 @@
bind:value={field.fieldName} /> bind:value={field.fieldName} />
{/if} {/if}
<footer class="create-column-options"> <footer class="create-column-options">
{#if originalName} {#if !uneditable && originalName}
<TextButton text on:click={confirmDelete}>Delete Column</TextButton> <TextButton text on:click={confirmDelete}>Delete Column</TextButton>
{/if} {/if}
<Button secondary on:click={onClosed}>Cancel</Button> <Button secondary on:click={onClosed}>Cancel</Button>

View File

@ -1,5 +1,6 @@
<script> <script>
import { backendUiStore } from "builderStore" import { backendUiStore } from "builderStore"
import { TableNames } from "constants"
import { notifier } from "builderStore/store/notifications" import { notifier } from "builderStore/store/notifications"
import RowFieldControl from "../RowFieldControl.svelte" import RowFieldControl from "../RowFieldControl.svelte"
import * as api from "../api" import * as api from "../api"
@ -21,9 +22,10 @@
{ ...row, tableId: table._id }, { ...row, tableId: table._id },
table._id table._id
) )
if (rowResponse.errors) { if (rowResponse.errors) {
errors = Object.keys(rowResponse.errors) errors = Object.entries(rowResponse.errors)
.map(k => ({ dataPath: k, message: rowResponse.errors[k] })) .map(([key, error]) => ({ dataPath: key, message: error }))
.flat() .flat()
// Prevent modal closing if there were errors // Prevent modal closing if there were errors
return false return false
@ -38,9 +40,15 @@
confirmText={creating ? 'Create Row' : 'Save Row'} confirmText={creating ? 'Create Row' : 'Save Row'}
onConfirm={saveRow}> onConfirm={saveRow}>
<ErrorsBox {errors} /> <ErrorsBox {errors} />
{#if creating && table._id === TableNames.USERS}
<RowFieldControl
{creating}
meta={{ name: 'password', type: 'password' }}
bind:value={row.password} />
{/if}
{#each tableSchema as [key, meta]} {#each tableSchema as [key, meta]}
<div> <div>
<RowFieldControl {meta} bind:value={row[key]} /> <RowFieldControl {meta} bind:value={row[key]} {creating} />
</div> </div>
{/each} {/each}
</ModalContent> </ModalContent>

View File

@ -1,6 +1,7 @@
<script> <script>
import { goto } from "@sveltech/routify" import { goto } from "@sveltech/routify"
import { backendUiStore } from "builderStore" import { backendUiStore } from "builderStore"
import { TableNames } from "constants"
import ListItem from "./ListItem.svelte" import ListItem from "./ListItem.svelte"
import CreateTableModal from "./modals/CreateTableModal.svelte" import CreateTableModal from "./modals/CreateTableModal.svelte"
import EditTablePopover from "./popovers/EditTablePopover.svelte" import EditTablePopover from "./popovers/EditTablePopover.svelte"
@ -42,7 +43,7 @@
{#each $backendUiStore.tables as table, idx} {#each $backendUiStore.tables as table, idx}
<NavItem <NavItem
border={idx > 0} border={idx > 0}
icon="ri-table-line" icon={`ri-${table._id === TableNames.USERS ? 'user' : 'table'}-line`}
text={table.name} text={table.name}
selected={selectedView === `all_${table._id}`} selected={selectedView === `all_${table._id}`}
on:click={() => selectTable(table)}> on:click={() => selectTable(table)}>

View File

@ -39,7 +39,7 @@
async function deleteTable() { async function deleteTable() {
await backendUiStore.actions.tables.delete(table) await backendUiStore.actions.tables.delete(table)
store.store.actions.screens.delete(templateScreens) store.actions.screens.delete(templateScreens)
await backendUiStore.actions.tables.fetch() await backendUiStore.actions.tables.fetch()
notifier.success("Table deleted") notifier.success("Table deleted")
hideEditor() hideEditor()

View File

@ -1,5 +1,5 @@
<script> <script>
import { General, Users, DangerZone, APIKeys } from "./tabs" import { General, DangerZone, APIKeys } from "./tabs"
import { Switcher, ModalContent } from "@budibase/bbui" import { Switcher, ModalContent } from "@budibase/bbui"
const tabs = [ const tabs = [
@ -8,11 +8,6 @@
key: "GENERAL", key: "GENERAL",
component: General, component: General,
}, },
{
title: "Users",
key: "USERS",
component: Users,
},
{ {
title: "API Keys", title: "API Keys",
key: "API_KEYS", key: "API_KEYS",

View File

@ -1,43 +0,0 @@
<script>
import { createEventDispatcher } from "svelte"
const dispatch = createEventDispatcher()
import { Input, Select, Button } from "@budibase/bbui"
export let user
let editMode = false
</script>
<div class="inputs">
<Input
disabled
thin
bind:value={user.username}
name="Name"
placeholder="Username" />
<Select disabled={!editMode} bind:value={user.accessLevelId} thin secondary>
<option value="">Choose an option</option>
<option value="ADMIN">Admin</option>
<option value="POWER_USER">Power User</option>
</Select>
{#if editMode}
<Button
blue
on:click={() => {
dispatch('save', user)
editMode = false
}}>
Save
</Button>
{:else}
<Button secondary on:click={() => (editMode = true)}>Edit</Button>
{/if}
</div>
<style>
.inputs {
display: grid;
justify-items: stretch;
grid-gap: var(--spacing-m);
grid-template-columns: 1fr 1fr 140px;
}
</style>

View File

@ -1,114 +0,0 @@
<script>
import { Input, Select, Button, Label } from "@budibase/bbui"
import UserRow from "../UserRow.svelte"
import { store, backendUiStore } from "builderStore"
import api from "builderStore/api"
// import * as api from "../api"
let username = ""
let password = ""
let accessLevelId = "ADMIN"
$: valid = username && password && accessLevelId
$: appId = $store.appId
// Create user!
async function createUser() {
if (valid) {
const user = { name: username, username, password, accessLevelId }
const response = await api.post(`/api/users`, user)
const json = await response.json()
backendUiStore.actions.users.create(json)
fetchUsersPromise = fetchUsers()
}
}
// Update user!
async function updateUser(event) {
let data = event.detail
delete data.password
const response = await api.put(`/api/users`, data)
const users = await response.json()
backendUiStore.update(state => {
state.users = users
return state
})
fetchUsersPromise = fetchUsers()
}
// Get users
async function fetchUsers() {
const response = await api.get(`/api/users`)
const users = await response.json()
backendUiStore.update(state => {
state.users = users
return state
})
return users
}
let fetchUsersPromise = fetchUsers()
</script>
<div class="container">
<div>
<Label extraSmall grey>Create New User</Label>
<div class="inputs">
<Input thin bind:value={username} name="Name" placeholder="Username" />
<Input
thin
type="password"
bind:value={password}
name="Password"
placeholder="Password" />
<Select secondary bind:value={accessLevelId} thin>
<option value="">Choose an option</option>
<option value="ADMIN">Admin</option>
<option value="POWER_USER">Power User</option>
</Select>
<Button on:click={createUser} primary>Create</Button>
</div>
</div>
<div>
<Label extraSmall grey>Current Users</Label>
{#await fetchUsersPromise}
Loading...
{:then users}
<ul>
{#each users as user}
<li>
<UserRow {user} on:save={updateUser} />
</li>
{:else}
<li>No Users found</li>
{/each}
</ul>
{:catch err}
Something went wrong when trying to fetch users. Please refresh (CMD + R /
CTRL + R) the page and try again.
{/await}
</div>
</div>
<style>
.container {
display: grid;
grid-gap: var(--spacing-xl);
}
.inputs {
display: grid;
justify-items: stretch;
grid-gap: var(--spacing-m);
grid-template-columns: 1fr 1fr 1fr 140px;
}
ul {
list-style: none;
padding: 0;
display: grid;
grid-gap: var(--spacing-m);
margin: 0;
}
</style>

View File

@ -1,6 +1,5 @@
export { default as General } from "./General.svelte" export { default as General } from "./General.svelte"
export { default as Integrations } from "./Integrations.svelte" export { default as Integrations } from "./Integrations.svelte"
export { default as Permissions } from "./Permissions.svelte" export { default as Permissions } from "./Permissions.svelte"
export { default as Users } from "./Users.svelte"
export { default as APIKeys } from "./APIKeys.svelte" export { default as APIKeys } from "./APIKeys.svelte"
export { default as DangerZone } from "./DangerZone.svelte" export { default as DangerZone } from "./DangerZone.svelte"

View File

@ -1,3 +1,10 @@
export const TableNames = {
USERS: "ta_users",
}
// fields on the user table that cannot be edited
export const UNEDITABLE_USER_FIELDS = ["username", "password", "accessLevelId"]
export const DEFAULT_PAGES_OBJECT = { export const DEFAULT_PAGES_OBJECT = {
main: { main: {
props: { props: {

View File

@ -68,16 +68,11 @@
<div class="toprightnav"> <div class="toprightnav">
<ThemeEditor /> <ThemeEditor />
<FeedbackNavLink /> <FeedbackNavLink />
<div class="topnavitemright">
<a target="_blank" href="https://docs.budibase.com">
<i class="ri-question-line" />
</a>
</div>
<div class="topnavitemright"> <div class="topnavitemright">
<a <a
target="_blank" target="_blank"
href="https://github.com/Budibase/budibase/discussions"> href="https://github.com/Budibase/budibase/discussions">
<i class="ri-discuss-line" /> <i class="ri-question-line" />
</a> </a>
</div> </div>
<SettingsLink /> <SettingsLink />

View File

@ -81,6 +81,7 @@
"lodash": "^4.17.13", "lodash": "^4.17.13",
"mustache": "^4.0.1", "mustache": "^4.0.1",
"node-fetch": "^2.6.0", "node-fetch": "^2.6.0",
"open": "^7.3.0",
"pino-pretty": "^4.0.0", "pino-pretty": "^4.0.0",
"pouchdb": "^7.2.1", "pouchdb": "^7.2.1",
"pouchdb-all-dbs": "^1.0.2", "pouchdb-all-dbs": "^1.0.2",

View File

@ -26,6 +26,7 @@ const {
const { MAIN, UNAUTHENTICATED, PageTypes } = require("../../constants/pages") const { MAIN, UNAUTHENTICATED, PageTypes } = require("../../constants/pages")
const { HOME_SCREEN } = require("../../constants/screens") const { HOME_SCREEN } = require("../../constants/screens")
const { cloneDeep } = require("lodash/fp") const { cloneDeep } = require("lodash/fp")
const { USERS_TABLE_SCHEMA } = require("../../constants")
const APP_PREFIX = DocumentTypes.APP + SEPARATOR const APP_PREFIX = DocumentTypes.APP + SEPARATOR
@ -67,6 +68,9 @@ async function createInstance(template) {
if (!ok) { if (!ok) {
throw "Error loading database dump from template." throw "Error loading database dump from template."
} }
} else {
// create the users table
await db.put(USERS_TABLE_SCHEMA)
} }
return { _id: appId } return { _id: appId }

View File

@ -6,7 +6,9 @@ const {
generateRowID, generateRowID,
DocumentTypes, DocumentTypes,
SEPARATOR, SEPARATOR,
ViewNames,
} = require("../../db/utils") } = require("../../db/utils")
const usersController = require("./user")
const { cloneDeep } = require("lodash") const { cloneDeep } = require("lodash")
const TABLE_VIEW_BEGINS_WITH = `all${SEPARATOR}${DocumentTypes.TABLE}${SEPARATOR}` const TABLE_VIEW_BEGINS_WITH = `all${SEPARATOR}${DocumentTypes.TABLE}${SEPARATOR}`
@ -118,6 +120,16 @@ exports.save = async function(ctx) {
table, table,
}) })
// Creation of a new user goes to the user controller
if (!existingRow && row.tableId === ViewNames.USERS) {
try {
await usersController.create(ctx)
} catch (err) {
ctx.body = { errors: [err.message] }
}
return
}
if (existingRow) { if (existingRow) {
const response = await db.put(row) const response = await db.put(row)
row._rev = response.rev row._rev = response.rev
@ -315,8 +327,8 @@ exports.fetchEnrichedRow = async function(ctx) {
ctx.status = 200 ctx.status = 200
} }
function coerceRowValues(rec, table) { function coerceRowValues(record, table) {
const row = cloneDeep(rec) const row = cloneDeep(record)
for (let [key, value] of Object.entries(row)) { for (let [key, value] of Object.entries(row)) {
const field = table.schema[key] const field = table.schema[key]
if (!field) continue if (!field) continue

View File

@ -1,6 +1,6 @@
const CouchDB = require("../../db") const CouchDB = require("../../db")
const bcrypt = require("../../utilities/bcrypt") const bcrypt = require("../../utilities/bcrypt")
const { generateUserID, getUserParams } = require("../../db/utils") const { generateUserID, getUserParams, ViewNames } = require("../../db/utils")
const { const {
BUILTIN_LEVEL_ID_ARRAY, BUILTIN_LEVEL_ID_ARRAY,
} = require("../../utilities/security/accessLevels") } = require("../../utilities/security/accessLevels")
@ -11,7 +11,7 @@ const {
exports.fetch = async function(ctx) { exports.fetch = async function(ctx) {
const database = new CouchDB(ctx.user.appId) const database = new CouchDB(ctx.user.appId)
const data = await database.allDocs( const data = await database.allDocs(
getUserParams(null, { getUserParams("", {
include_docs: true, include_docs: true,
}) })
) )
@ -44,6 +44,7 @@ exports.create = async function(ctx) {
type: "user", type: "user",
accessLevelId, accessLevelId,
permissions: permissions || [BUILTIN_PERMISSION_NAMES.POWER], permissions: permissions || [BUILTIN_PERMISSION_NAMES.POWER],
tableId: ViewNames.USERS,
} }
try { try {

View File

@ -28,7 +28,7 @@ module.exports.definition = {
accessLevelId: { accessLevelId: {
type: "string", type: "string",
title: "Access Level", title: "Access Level",
enum: accessLevels.BUILTIN_LEVEL_IDS, enum: accessLevels.BUILTIN_LEVEL_ID_ARRAY,
pretty: accessLevels.BUILTIN_LEVEL_NAME_ARRAY, pretty: accessLevels.BUILTIN_LEVEL_NAME_ARRAY,
}, },
}, },

View File

@ -1,7 +1,42 @@
const { BUILTIN_LEVEL_IDS } = require("../utilities/security/accessLevels")
const AuthTypes = { const AuthTypes = {
APP: "app", APP: "app",
BUILDER: "builder", BUILDER: "builder",
EXTERNAL: "external", EXTERNAL: "external",
} }
const USERS_TABLE_SCHEMA = {
_id: "ta_users",
type: "table",
views: {},
name: "Users",
schema: {
username: {
type: "string",
constraints: {
type: "string",
length: {
maximum: "",
},
presence: true,
},
fieldName: "username",
name: "username",
},
accessLevelId: {
fieldName: "accessLevelId",
name: "accessLevelId",
type: "options",
constraints: {
type: "string",
presence: false,
inclusion: Object.keys(BUILTIN_LEVEL_IDS),
},
},
},
primaryDisplay: "username",
}
exports.AuthTypes = AuthTypes exports.AuthTypes = AuthTypes
exports.USERS_TABLE_SCHEMA = USERS_TABLE_SCHEMA

View File

@ -20,6 +20,7 @@ const DocumentTypes = {
const ViewNames = { const ViewNames = {
LINK: "by_link", LINK: "by_link",
ROUTING: "screen_routes", ROUTING: "screen_routes",
USERS: "ta_users",
} }
exports.ViewNames = ViewNames exports.ViewNames = ViewNames
@ -80,13 +81,12 @@ exports.generateTableID = () => {
exports.getRowParams = (tableId = null, rowId = null, otherProps = {}) => { exports.getRowParams = (tableId = null, rowId = null, otherProps = {}) => {
if (tableId == null) { if (tableId == null) {
return getDocParams(DocumentTypes.ROW, null, otherProps) return getDocParams(DocumentTypes.ROW, null, otherProps)
} else {
const endOfKey =
rowId == null
? `${tableId}${SEPARATOR}`
: `${tableId}${SEPARATOR}${rowId}`
return getDocParams(DocumentTypes.ROW, endOfKey, otherProps)
} }
const endOfKey =
rowId == null ? `${tableId}${SEPARATOR}` : `${tableId}${SEPARATOR}${rowId}`
return getDocParams(DocumentTypes.ROW, endOfKey, otherProps)
} }
/** /**
@ -101,8 +101,12 @@ exports.generateRowID = tableId => {
/** /**
* Gets parameters for retrieving users, this is a utility function for the getDocParams function. * Gets parameters for retrieving users, this is a utility function for the getDocParams function.
*/ */
exports.getUserParams = (username = null, otherProps = {}) => { exports.getUserParams = (username = "", otherProps = {}) => {
return getDocParams(DocumentTypes.USER, username, otherProps) return getDocParams(
DocumentTypes.ROW,
`${ViewNames.USERS}${SEPARATOR}${DocumentTypes.USER}${SEPARATOR}${username}`,
otherProps
)
} }
/** /**
@ -111,7 +115,7 @@ exports.getUserParams = (username = null, otherProps = {}) => {
* @returns {string} The new user ID which the user doc can be stored under. * @returns {string} The new user ID which the user doc can be stored under.
*/ */
exports.generateUserID = username => { exports.generateUserID = username => {
return `${DocumentTypes.USER}${SEPARATOR}${username}` return `${DocumentTypes.ROW}${SEPARATOR}${ViewNames.USERS}${SEPARATOR}${DocumentTypes.USER}${SEPARATOR}${username}`
} }
/** /**

View File

@ -3868,6 +3868,11 @@ is-descriptor@^1.0.0, is-descriptor@^1.0.2:
is-data-descriptor "^1.0.0" is-data-descriptor "^1.0.0"
kind-of "^6.0.2" kind-of "^6.0.2"
is-docker@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.1.1.tgz#4125a88e44e450d384e09047ede71adc2d144156"
integrity sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==
is-extendable@^0.1.0, is-extendable@^0.1.1: is-extendable@^0.1.0, is-extendable@^0.1.1:
version "0.1.1" version "0.1.1"
resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89"
@ -4027,6 +4032,13 @@ is-wsl@^1.1.0:
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d"
integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=
is-wsl@^2.1.1:
version "2.2.0"
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==
dependencies:
is-docker "^2.0.0"
is-yarn-global@^0.3.0: is-yarn-global@^0.3.0:
version "0.3.0" version "0.3.0"
resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232"
@ -5648,6 +5660,14 @@ only@~0.0.2:
resolved "https://registry.yarnpkg.com/only/-/only-0.0.2.tgz#2afde84d03e50b9a8edc444e30610a70295edfb4" resolved "https://registry.yarnpkg.com/only/-/only-0.0.2.tgz#2afde84d03e50b9a8edc444e30610a70295edfb4"
integrity sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q= integrity sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q=
open@^7.3.0:
version "7.3.0"
resolved "https://registry.yarnpkg.com/open/-/open-7.3.0.tgz#45461fdee46444f3645b6e14eb3ca94b82e1be69"
integrity sha512-mgLwQIx2F/ye9SmbrUkurZCnkoXyXyu9EbHtJZrICjVAJfyMArdHp3KkixGdZx1ZHFPNIwl0DDM1dFFqXbTLZw==
dependencies:
is-docker "^2.0.0"
is-wsl "^2.1.1"
optionator@^0.8.1, optionator@^0.8.3: optionator@^0.8.1, optionator@^0.8.3:
version "0.8.3" version "0.8.3"
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495"