diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000000..e0bcfe01fb --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v14.19.3 diff --git a/.python-version b/.python-version new file mode 100644 index 0000000000..371cfe355d --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.11.1 diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000000..8a1af3c071 --- /dev/null +++ b/.tool-versions @@ -0,0 +1,2 @@ +nodejs 14.19.3 +python 3.11.1 \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000000..03d0aa4411 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,6 @@ +{ + "recommendations": [ + "esbenp.prettier-vscode", + "svelte.svelte-vscode" + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index d48458fbd7..ece537efac 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,22 +1,28 @@ { - "editor.formatOnSave": true, - "editor.codeActionsOnSave": { - "source.fixAll": true - }, - "editor.defaultFormatter": "svelte.svelte-vscode", - "[json]": { - "editor.defaultFormatter": "vscode.json-language-features" - }, - "[javascript]": { - "editor.defaultFormatter": "vscode.typescript-language-features" - }, - "debug.javascript.terminalOptions": { - "skipFiles": [ - "${workspaceFolder}/packages/backend-core/node_modules/**", - "/**" - ] - }, - "[typescript]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll": true + }, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "[json]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "debug.javascript.terminalOptions": { + "skipFiles": [ + "${workspaceFolder}/packages/backend-core/node_modules/**", + "/**" + ] + }, + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[dockercompose]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[svelte]": { + "editor.defaultFormatter": "svelte.svelte-vscode" + } } diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index fb0848596c..6e667d23a8 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -9,7 +9,6 @@ From opening a bug report to creating a pull request: every contribution is appr - [Glossary of Terms](#glossary-of-terms) - [Contributing to Budibase](#contributing-to-budibase) - ## Not Sure Where to Start? Budibase is a low-code web application builder that creates svelte-based web applications. @@ -22,7 +21,7 @@ Budibase is a monorepo managed by [lerna](https://github.com/lerna/lerna). Lerna - **packages/server** - The budibase server. This [Koa](https://koajs.com/) app is responsible for serving the JS for the builder and budibase apps, as well as providing the API for interaction with the database and file system. -- **packages/worker** - This [Koa](https://koajs.com/) app is responsible for providing global apis for managing your budibase installation. Authentication, Users, Email, Org and Auth configs are all provided by the worker. +- **packages/worker** - This [Koa](https://koajs.com/) app is responsible for providing global apis for managing your budibase installation. Authentication, Users, Email, Org and Auth configs are all provided by the worker. ## Contributor License Agreement (CLA) @@ -45,7 +44,7 @@ A client represents a single budibase customer. Each budibase client will have 1 ### App -A client can have one or more budibase applications. Budibase applications would be things like "Developer Inventory Management" or "Goat Herder CRM". Think of a budibase application as a tree. +A client can have one or more budibase applications. Budibase applications would be things like "Developer Inventory Management" or "Goat Herder CRM". Think of a budibase application as a tree. ### Database @@ -73,28 +72,55 @@ A component is the basic frontend building block of a budibase app. ### Component Library -Component libraries are collections of components as well as the definition of their props contained in a file called `components.json`. +Component libraries are collections of components as well as the definition of their props contained in a file called `components.json`. ## Contributing to Budibase -* Please maintain the existing code style. +- Please maintain the existing code style. -* Please try to keep your commits small and focused. +- Please try to keep your commits small and focused. -* Please write tests. +- Please write tests. -* If the project diverges from your branch, please rebase instead of merging. This makes the commit graph easier to read. +- If the project diverges from your branch, please rebase instead of merging. This makes the commit graph easier to read. -* Once your work is completed, please raise a PR against the `develop` branch with some information about what has changed and why. +- Once your work is completed, please raise a PR against the `develop` branch with some information about what has changed and why. ### Getting Started For Contributors -#### 1. Prerequisites -NodeJS Version `14.x.x` +#### 1. Prerequisites -*yarn -* `npm install -g yarn` +- NodeJS version `14.x.x` +- Python version `3.x` -*jest* - `npm install -g jest` +### Using asdf (recommended) + +Asdf is a package manager that allows managing multiple dependencies. + +You can install them following any of the steps described below: + +- Install using script (only for mac users): + +`./scripts/install-contributor-dependencies.sh` + +- Or, manually: + + - Installation steps: https://asdf-vm.com/guide/getting-started.html + - asdf plugin add nodejs + - asdf plugin add python + - npm install -g yarn + +### Using NVM and pyenv + +- NVM: + - Install: https://github.com/nvm-sh/nvm#installing-and-updating + - Setup: `nvm use` +- Pyenv: + + - Install: https://github.com/pyenv/pyenv#installation + - Setup: `pyenv install -v 3.7.2` + +- _yarn -_ `npm install -g yarn` #### 2. Clone this repository @@ -102,7 +128,7 @@ NodeJS Version `14.x.x` then `cd ` into your local copy. -#### 3. Install and Build +#### 3. Install and Build | **NOTE**: On Windows, all yarn commands must be executed on a bash shell (e.g. git bash) @@ -134,9 +160,9 @@ This will enable watch mode for both the builder app, server, client library and #### 5. Debugging using VS Code -To debug the budibase server and worker a VS Code launch configuration has been provided. +To debug the budibase server and worker a VS Code launch configuration has been provided. -Visit the debug window and select `Budibase Server` or `Budibase Worker` to debug the respective component. +Visit the debug window and select `Budibase Server` or `Budibase Worker` to debug the respective component. Alternatively to start both components simultaneously select `Start Budibase`. In addition to the above, the remaining budibase components may be run in dev mode using: `yarn dev:noserver`. @@ -156,11 +182,11 @@ For the backend we run [Redis](https://redis.io/), [CouchDB](https://couchdb.apa When you are running locally, budibase stores data on disk using docker volumes. The volumes and the types of data associated with each are: -- `redis_data` +- `redis_data` - Sessions, email tokens -- `couchdb3_data` +- `couchdb3_data` - Global and app databases -- `minio_data` +- `minio_data` - App manifest, budibase client, static assets ### Development Modes @@ -172,34 +198,42 @@ A combination of environment variables controls the mode budibase runs in. Yarn commands can be used to mimic the different modes as described in the sections below: #### Self Hosted -The default mode. A single tenant installation with no usage restrictions. + +The default mode. A single tenant installation with no usage restrictions. To enable this mode, use: + ``` yarn mode:self ``` #### Cloud -The cloud mode, with account portal turned off. + +The cloud mode, with account portal turned off. To enable this mode, use: + ``` yarn mode:cloud ``` -#### Cloud & Account -The cloud mode, with account portal turned on. This is a replica of the mode that runs at https://budibase.app +#### Cloud & Account + +The cloud mode, with account portal turned on. This is a replica of the mode that runs at https://budibase.app To enable this mode, use: + ``` yarn mode:account ``` + ### CI - An overview of the CI pipelines can be found [here](../.github/workflows/README.md) + +An overview of the CI pipelines can be found [here](../.github/workflows/README.md) ### Pro -@budibase/pro is the closed source package that supports licensed features in budibase. By default the package will be pulled from NPM and will not normally need to be touched in local development. If you require to update code inside the pro package it can be cloned to the same root level as budibase, e.g. +@budibase/pro is the closed source package that supports licensed features in budibase. By default the package will be pulled from NPM and will not normally need to be touched in local development. If you require to update code inside the pro package it can be cloned to the same root level as budibase, e.g. ``` . @@ -207,13 +241,14 @@ yarn mode:account |_ budibase-pro ``` -Note that only budibase maintainers will be able to access the pro repo. +Note that only budibase maintainers will be able to access the pro repo. -The `yarn bootstrap` command can be used to replace the NPM supplied dependency with the local source aware version. This is achieved using the `yarn link` command. To see specifically how dependencies are linked see [scripts/link-dependencies.sh](../scripts/link-dependencies.sh). The same link script is used to link dependencies to account-portal in local dev. +The `yarn bootstrap` command can be used to replace the NPM supplied dependency with the local source aware version. This is achieved using the `yarn link` command. To see specifically how dependencies are linked see [scripts/link-dependencies.sh](../scripts/link-dependencies.sh). The same link script is used to link dependencies to account-portal in local dev. ### Troubleshooting Sometimes, things go wrong. This can be due to incompatible updates on the budibase platform. To clear down your development environment and start again follow **Step 6. Cleanup**, then proceed from **Step 3. Install and Build** in the setup guide above to create a fresh Budibase installation. + ### Running tests #### End-to-end Tests @@ -226,12 +261,11 @@ yarn test:e2e Or if you are in the builder you can run `yarn cy:test`. - ### Other Useful Information -* The contributors are listed in [AUTHORS.md](https://github.com/Budibase/budibase/blob/master/.github/AUTHORS.md) (add yourself). +- The contributors are listed in [AUTHORS.md](https://github.com/Budibase/budibase/blob/master/.github/AUTHORS.md) (add yourself). -* This project uses a modified version of the MPLv2 license, see [LICENSE](https://github.com/budibase/server/blob/master/LICENSE). +- This project uses a modified version of the MPLv2 license, see [LICENSE](https://github.com/budibase/server/blob/master/LICENSE). -* We use the [C4 (Collective Code Construction Contract)](https://rfc.zeromq.org/spec:42/C4/) process for contributions. +- We use the [C4 (Collective Code Construction Contract)](https://rfc.zeromq.org/spec:42/C4/) process for contributions. Please read this if you are unfamiliar with it. diff --git a/lerna.json b/lerna.json index 30afd475d4..ae8f83a74b 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.2.12-alpha.68", + "version": "2.2.12-alpha.70", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/packages/backend-core/package.json b/packages/backend-core/package.json index 96f28879d4..8265e144a8 100644 --- a/packages/backend-core/package.json +++ b/packages/backend-core/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/backend-core", - "version": "2.2.12-alpha.68", + "version": "2.2.12-alpha.70", "description": "Budibase backend core libraries used in server and worker", "main": "dist/src/index.js", "types": "dist/src/index.d.ts", @@ -23,7 +23,7 @@ }, "dependencies": { "@budibase/nano": "10.1.1", - "@budibase/types": "2.2.12-alpha.68", + "@budibase/types": "2.2.12-alpha.70", "@shopify/jest-koa-mocks": "5.0.1", "@techpass/passport-openidconnect": "0.3.2", "aws-cloudfront-sign": "2.2.0", diff --git a/packages/bbui/package.json b/packages/bbui/package.json index 2864a8ad1e..db800c847b 100644 --- a/packages/bbui/package.json +++ b/packages/bbui/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/bbui", "description": "A UI solution used in the different Budibase projects.", - "version": "2.2.12-alpha.68", + "version": "2.2.12-alpha.70", "license": "MPL-2.0", "svelte": "src/index.js", "module": "dist/bbui.es.js", @@ -38,7 +38,7 @@ ], "dependencies": { "@adobe/spectrum-css-workflow-icons": "1.2.1", - "@budibase/string-templates": "2.2.12-alpha.68", + "@budibase/string-templates": "2.2.12-alpha.70", "@spectrum-css/accordion": "3.0.24", "@spectrum-css/actionbutton": "1.0.1", "@spectrum-css/actiongroup": "1.0.1", diff --git a/packages/builder/package.json b/packages/builder/package.json index 3164654702..9b0ae17224 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/builder", - "version": "2.2.12-alpha.68", + "version": "2.2.12-alpha.70", "license": "GPL-3.0", "private": true, "scripts": { @@ -58,10 +58,10 @@ } }, "dependencies": { - "@budibase/bbui": "2.2.12-alpha.68", - "@budibase/client": "2.2.12-alpha.68", - "@budibase/frontend-core": "2.2.12-alpha.68", - "@budibase/string-templates": "2.2.12-alpha.68", + "@budibase/bbui": "2.2.12-alpha.70", + "@budibase/client": "2.2.12-alpha.70", + "@budibase/frontend-core": "2.2.12-alpha.70", + "@budibase/string-templates": "2.2.12-alpha.70", "@fortawesome/fontawesome-svg-core": "^6.2.1", "@fortawesome/free-brands-svg-icons": "^6.2.1", "@fortawesome/free-solid-svg-icons": "^6.2.1", diff --git a/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/PlusConfigForm.svelte b/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/PlusConfigForm.svelte index b510cc0967..ab5b3ccee0 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/PlusConfigForm.svelte +++ b/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/PlusConfigForm.svelte @@ -82,7 +82,7 @@ let displayString if (throughTableName) { - displayString = `${fromTableName} through ${throughTableName} → ${toTableName}` + displayString = `${fromTableName} ↔ ${toTableName}` } else { displayString = `${fromTableName} → ${toTableName}` } diff --git a/packages/builder/src/components/backend/Datasources/CreateEditRelationship.svelte b/packages/builder/src/components/backend/Datasources/CreateEditRelationship.svelte index e43437d756..4a3c4f6c60 100644 --- a/packages/builder/src/components/backend/Datasources/CreateEditRelationship.svelte +++ b/packages/builder/src/components/backend/Datasources/CreateEditRelationship.svelte @@ -10,17 +10,17 @@ } from "@budibase/bbui" import { tables } from "stores/backend" import { Helpers } from "@budibase/bbui" + import { RelationshipErrorChecker } from "./relationshipErrors" + import { onMount } from "svelte" export let save export let datasource export let plusTables = [] export let fromRelationship = {} export let toRelationship = {} + export let selectedFromTable export let close - const colNotSet = "Please specify a column name" - const relationshipAlreadyExists = - "A relationship between these tables already exists." const relationshipTypes = [ { label: "One to Many", @@ -42,63 +42,28 @@ ) let tableOptions + let errorChecker = new RelationshipErrorChecker( + invalidThroughTable, + relationshipExists + ) let errors = {} - let hasClickedSave = !!fromRelationship.relationshipType - let fromPrimary, - fromForeign, - fromTable, - toTable, - throughTable, - fromColumn, - toColumn + let fromPrimary, fromForeign, fromColumn, toColumn let fromId, toId, throughId, throughToKey, throughFromKey let isManyToMany, isManyToOne, relationshipType - - $: { - if (!fromPrimary) { - fromPrimary = fromRelationship.foreignKey - fromForeign = toRelationship.foreignKey - } - if (!fromColumn && !errors.fromColumn) { - fromColumn = toRelationship.name - } - if (!toColumn && !errors.toColumn) { - toColumn = fromRelationship.name - } - if (!fromId) { - fromId = toRelationship.tableId - } - if (!toId) { - toId = fromRelationship.tableId - } - if (!throughId) { - throughId = fromRelationship.through - throughFromKey = fromRelationship.throughFrom - throughToKey = fromRelationship.throughTo - } - if (!relationshipType) { - relationshipType = fromRelationship.relationshipType - } - } + let hasValidated = false $: tableOptions = plusTables.map(table => ({ label: table.name, value: table._id, })) - $: valid = getErrorCount(errors) === 0 || !hasClickedSave - + $: valid = getErrorCount(errors) === 0 && allRequiredAttributesSet() $: isManyToMany = relationshipType === RelationshipTypes.MANY_TO_MANY $: isManyToOne = relationshipType === RelationshipTypes.MANY_TO_ONE - $: fromTable = plusTables.find(table => table._id === fromId) - $: toTable = plusTables.find(table => table._id === toId) - $: throughTable = plusTables.find(table => table._id === throughId) - $: toRelationship.relationshipType = fromRelationship?.relationshipType - const getErrorCount = errors => - Object.entries(errors) - .filter(entry => !!entry[1]) - .map(entry => entry[0]).length + function getTable(id) { + return plusTables.find(table => table._id === id) + } function invalidThroughTable() { // need to know the foreign key columns to check error @@ -116,93 +81,103 @@ } return false } - - function validate() { - const isMany = relationshipType === RelationshipTypes.MANY_TO_MANY - const tableNotSet = "Please specify a table" - const foreignKeyNotSet = "Please pick a foreign key" - const errObj = {} - if (!relationshipType) { - errObj.relationshipType = "Please specify a relationship type" - } - if (!fromTable) { - errObj.fromTable = tableNotSet - } - if (!toTable) { - errObj.toTable = tableNotSet - } - if (isMany && !throughTable) { - errObj.throughTable = tableNotSet - } - if (isMany && !throughFromKey) { - errObj.throughFromKey = foreignKeyNotSet - } - if (isMany && !throughToKey) { - errObj.throughToKey = foreignKeyNotSet - } - if (invalidThroughTable()) { - errObj.throughTable = - "Ensure non-key columns are nullable or auto-generated" - } - if (!isMany && !fromForeign) { - errObj.fromForeign = foreignKeyNotSet - } - if (!fromColumn) { - errObj.fromColumn = colNotSet - } - if (!toColumn) { - errObj.toColumn = colNotSet - } - if (!isMany && !fromPrimary) { - errObj.fromPrimary = "Please pick the primary key" - } - if (isMany && relationshipExists()) { - errObj.fromTable = relationshipAlreadyExists - errObj.toTable = relationshipAlreadyExists - } - - // currently don't support relationships back onto the table itself, needs to relate out - const tableError = "From/to/through tables must be different" - if (fromTable && (fromTable === toTable || fromTable === throughTable)) { - errObj.fromTable = tableError - } - if (toTable && (toTable === fromTable || toTable === throughTable)) { - errObj.toTable = tableError - } + function relationshipExists() { if ( - throughTable && - (throughTable === fromTable || throughTable === toTable) + originalFromTable && + originalToTable && + originalFromTable === getTable(fromId) && + originalToTable === getTable(toId) ) { - errObj.throughTable = tableError - } - const colError = "Column name cannot be an existing column" - if (isColumnNameBeingUsed(toTable, fromColumn, originalFromColumnName)) { - errObj.fromColumn = colError - } - if (isColumnNameBeingUsed(fromTable, toColumn, originalToColumnName)) { - errObj.toColumn = colError - } - - let fromType, toType - if (fromPrimary && fromForeign) { - fromType = fromTable?.schema[fromPrimary]?.type - toType = toTable?.schema[fromForeign]?.type - } - if (fromType && toType && fromType !== toType) { - errObj.fromForeign = - "Column type of the foreign key must match the primary key" - } - - errors = errObj - return getErrorCount(errors) === 0 - } - - function isColumnNameBeingUsed(table, columnName, originalName) { - if (!table || !columnName || columnName === originalName) { return false } - const keys = Object.keys(table.schema).map(key => key.toLowerCase()) - return keys.indexOf(columnName.toLowerCase()) !== -1 + let fromThroughLinks = Object.values( + datasource.entities[getTable(fromId).name].schema + ).filter(value => value.through) + let toThroughLinks = Object.values( + datasource.entities[getTable(toId).name].schema + ).filter(value => value.through) + + const matchAgainstUserInput = (fromTableId, toTableId) => + (fromTableId === fromId && toTableId === toId) || + (fromTableId === toId && toTableId === fromId) + + return !!fromThroughLinks.find(from => + toThroughLinks.find( + to => + from.through === to.through && + matchAgainstUserInput(from.tableId, to.tableId) + ) + ) + } + + function getErrorCount(errors) { + return Object.entries(errors).filter(entry => !!entry[1]).length + } + + function allRequiredAttributesSet() { + const base = getTable(fromId) && getTable(toId) && fromColumn && toColumn + if (relationshipType === RelationshipTypes.MANY_TO_ONE) { + return base && fromPrimary && fromForeign + } else { + return base && getTable(throughId) && throughFromKey && throughToKey + } + } + + function validate() { + if (!allRequiredAttributesSet() && !hasValidated) { + return + } + hasValidated = true + errorChecker.setType(relationshipType) + const fromTable = getTable(fromId), + toTable = getTable(toId), + throughTable = getTable(throughId) + errors = { + relationshipType: errorChecker.relationshipTypeSet(relationshipType), + fromTable: + errorChecker.tableSet(fromTable) || + errorChecker.doesRelationshipExists() || + errorChecker.differentTables(fromId, toId, throughId), + toTable: + errorChecker.tableSet(toTable) || + errorChecker.doesRelationshipExists() || + errorChecker.differentTables(toId, fromId, throughId), + throughTable: + errorChecker.throughTableSet(throughTable) || + errorChecker.throughIsNullable() || + errorChecker.differentTables(throughId, fromId, toId), + throughFromKey: + errorChecker.manyForeignKeySet(throughFromKey) || + errorChecker.manyTypeMismatch( + fromTable, + throughTable, + fromTable.primary[0], + throughFromKey + ), + throughToKey: + errorChecker.manyForeignKeySet(throughToKey) || + errorChecker.manyTypeMismatch( + toTable, + throughTable, + toTable.primary[0], + throughToKey + ), + fromForeign: + errorChecker.foreignKeySet(fromForeign) || + errorChecker.typeMismatch(fromTable, toTable, fromPrimary, fromForeign), + fromPrimary: errorChecker.primaryKeySet(fromPrimary), + fromColumn: errorChecker.columnBeingUsed( + toTable, + fromColumn, + originalFromColumnName + ), + toColumn: errorChecker.columnBeingUsed( + fromTable, + toColumn, + originalToColumnName + ), + } + return getErrorCount(errors) === 0 } function buildRelationships() { @@ -243,13 +218,13 @@ if (manyToMany) { relateFrom = { ...relateFrom, - through: throughTable._id, - fieldName: toTable.primary[0], + through: getTable(throughId)._id, + fieldName: getTable(toId).primary[0], } relateTo = { ...relateTo, - through: throughTable._id, - fieldName: fromTable.primary[0], + through: getTable(throughId)._id, + fieldName: getTable(fromId).primary[0], throughFrom: relateFrom.throughTo, throughTo: relateFrom.throughFrom, } @@ -277,35 +252,6 @@ toRelationship = relateTo } - function relationshipExists() { - if ( - originalFromTable && - originalToTable && - originalFromTable === fromTable && - originalToTable === toTable - ) { - return false - } - let fromThroughLinks = Object.values( - datasource.entities[fromTable.name].schema - ).filter(value => value.through) - let toThroughLinks = Object.values( - datasource.entities[toTable.name].schema - ).filter(value => value.through) - - const matchAgainstUserInput = (fromTableId, toTableId) => - (fromTableId === fromId && toTableId === toId) || - (fromTableId === toId && toTableId === fromId) - - return !!fromThroughLinks.find(from => - toThroughLinks.find( - to => - from.through === to.through && - matchAgainstUserInput(from.tableId, to.tableId) - ) - ) - } - function removeExistingRelationship() { if (originalFromTable && originalFromColumnName) { delete datasource.entities[originalFromTable.name].schema[ @@ -320,7 +266,6 @@ } async function saveRelationship() { - hasClickedSave = true if (!validate()) { return false } @@ -328,10 +273,10 @@ removeExistingRelationship() // source of relationship - datasource.entities[fromTable.name].schema[fromRelationship.name] = + datasource.entities[getTable(fromId).name].schema[fromRelationship.name] = fromRelationship // save other side of relationship in the other schema - datasource.entities[toTable.name].schema[toRelationship.name] = + datasource.entities[getTable(toId).name].schema[toRelationship.name] = toRelationship await save() @@ -342,6 +287,36 @@ await tables.fetch() close() } + + function changed(fn) { + if (typeof fn === "function") { + fn() + } + validate() + } + + onMount(() => { + if (fromRelationship) { + fromPrimary = fromRelationship.foreignKey + toId = fromRelationship.tableId + throughId = fromRelationship.through + throughFromKey = fromRelationship.throughFrom + throughToKey = fromRelationship.throughTo + toColumn = fromRelationship.name + } + if (toRelationship) { + fromForeign = toRelationship.foreignKey + fromId = toRelationship.tableId + fromColumn = toRelationship.name + } + relationshipType = + fromRelationship.relationshipType || RelationshipTypes.MANY_TO_ONE + if (selectedFromTable) { + fromId = selectedFromTable._id + fromColumn = selectedFromTable.name + fromPrimary = selectedFromTable?.primary[0] || null + } + }) (errors.relationshipType = null)} + on:change={() => + changed(() => { + hasValidated = false + })} />
Tables
- + changed(() => { + const table = plusTables.find(tbl => tbl._id === e.detail) + fromColumn = table?.name || "" + fromPrimary = table?.primary?.[0] + })} + /> + {/if} + {#if isManyToOne && fromId} + { - toColumn = tableOptions.find(opt => opt.value === e.detail)?.label || "" - if (errors.toTable === relationshipAlreadyExists) { - errors.fromColumn = null - } - errors.toTable = null - errors.toColumn = null - errors.fromTable = null - errors.throughTable = null - }} + on:change={e => + changed(() => { + const table = plusTables.find(tbl => tbl._id === e.detail) + toColumn = table.name || "" + fromForeign = null + })} /> {#if isManyToMany} { - if (throughFromKey === e.detail) { - throughFromKey = null - } - errors.throughToKey = null - }} + on:change={e => + changed(() => { + if (throughFromKey === e.detail) { + throughFromKey = null + } + })} /> (errors.fromForeign = null)} + on:change={changed} /> {/if}
@@ -459,15 +431,13 @@ label="From table column" bind:value={fromColumn} bind:error={errors.fromColumn} - on:change={e => { - errors.fromColumn = e.detail?.length > 0 ? null : colNotSet - }} + on:change={changed} /> (errors.toColumn = e.detail?.length > 0 ? null : colNotSet)} + on:change={changed} />
{#if originalFromColumnName != null} diff --git a/packages/builder/src/components/backend/Datasources/relationshipErrors.js b/packages/builder/src/components/backend/Datasources/relationshipErrors.js new file mode 100644 index 0000000000..0dc9b264b9 --- /dev/null +++ b/packages/builder/src/components/backend/Datasources/relationshipErrors.js @@ -0,0 +1,103 @@ +import { RelationshipTypes } from "constants/backend" + +const typeMismatch = "Column type of the foreign key must match the primary key" +const columnBeingUsed = "Column name cannot be an existing column" +const mustBeDifferentTables = "From/to/through tables must be different" +const primaryKeyNotSet = "Please pick the primary key" +const throughNotNullable = + "Ensure non-key columns are nullable or auto-generated" +const noRelationshipType = "Please specify a relationship type" +const tableNotSet = "Please specify a table" +const foreignKeyNotSet = "Please pick a foreign key" +const relationshipAlreadyExists = + "A relationship between these tables already exists" + +function isColumnNameBeingUsed(table, columnName, originalName) { + if (!table || !columnName || columnName === originalName) { + return false + } + const keys = Object.keys(table.schema).map(key => key.toLowerCase()) + return keys.indexOf(columnName.toLowerCase()) !== -1 +} + +function typeMismatchCheck(fromTable, toTable, primary, foreign) { + let fromType, toType + if (primary && foreign) { + fromType = fromTable?.schema[primary]?.type + toType = toTable?.schema[foreign]?.type + } + return fromType && toType && fromType !== toType ? typeMismatch : null +} + +export class RelationshipErrorChecker { + constructor(invalidThroughTableFn, relationshipExistsFn) { + this.invalidThroughTable = invalidThroughTableFn + this.relationshipExists = relationshipExistsFn + } + + setType(type) { + this.type = type + } + + isMany() { + return this.type === RelationshipTypes.MANY_TO_MANY + } + + relationshipTypeSet(type) { + return !type ? noRelationshipType : null + } + + tableSet(table) { + return !table ? tableNotSet : null + } + + throughTableSet(table) { + return this.isMany() && !table ? tableNotSet : null + } + + manyForeignKeySet(key) { + return this.isMany() && !key ? foreignKeyNotSet : null + } + + foreignKeySet(key) { + return !this.isMany() && !key ? foreignKeyNotSet : null + } + + primaryKeySet(key) { + return !this.isMany() && !key ? primaryKeyNotSet : null + } + + throughIsNullable() { + return this.invalidThroughTable() ? throughNotNullable : null + } + + doesRelationshipExists() { + return this.isMany() && this.relationshipExists() + ? relationshipAlreadyExists + : null + } + + differentTables(table1, table2, table3) { + // currently don't support relationships back onto the table itself, needs to relate out + const error = table1 && (table1 === table2 || (table3 && table1 === table3)) + return error ? mustBeDifferentTables : null + } + + columnBeingUsed(table, column, ogName) { + return isColumnNameBeingUsed(table, column, ogName) ? columnBeingUsed : null + } + + typeMismatch(fromTable, toTable, primary, foreign) { + if (this.isMany()) { + return null + } + return typeMismatchCheck(fromTable, toTable, primary, foreign) + } + + manyTypeMismatch(table, throughTable, primary, foreign) { + if (!this.isMany()) { + return null + } + return typeMismatchCheck(table, throughTable, primary, foreign) + } +} diff --git a/packages/builder/src/components/design/settings/componentSettings.js b/packages/builder/src/components/design/settings/componentSettings.js index f9f4295c17..65a21f368d 100644 --- a/packages/builder/src/components/design/settings/componentSettings.js +++ b/packages/builder/src/components/design/settings/componentSettings.js @@ -56,7 +56,7 @@ const componentMap = { "field/link": FormFieldSelect, "field/array": FormFieldSelect, "field/json": FormFieldSelect, - "field/barcode/qr": FormFieldSelect, + "field/barcodeqr": FormFieldSelect, // Some validation types are the same as others, so not all types are // explicitly listed here. e.g. options uses string validation "validation/string": ValidationEditor, diff --git a/packages/builder/src/components/design/settings/controls/FormFieldSelect.svelte b/packages/builder/src/components/design/settings/controls/FormFieldSelect.svelte index a02ea41099..806abc4e92 100644 --- a/packages/builder/src/components/design/settings/controls/FormFieldSelect.svelte +++ b/packages/builder/src/components/design/settings/controls/FormFieldSelect.svelte @@ -25,7 +25,7 @@ const getOptions = (schema, type) => { let entries = Object.entries(schema ?? {}) let types = [] - if (type === "field/options" || type === "field/barcode/qr") { + if (type === "field/options") { // allow options to be used on both options and string fields types = [type, "field/string"] } else { @@ -35,6 +35,7 @@ types = types.map(type => type.slice(type.indexOf("/") + 1)) entries = entries.filter(entry => types.includes(entry[1].type)) + return entries.map(entry => entry[0]) } diff --git a/packages/cli/package.json b/packages/cli/package.json index 4aa0d5fab7..1060280589 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/cli", - "version": "2.2.12-alpha.68", + "version": "2.2.12-alpha.70", "description": "Budibase CLI, for developers, self hosting and migrations.", "main": "src/index.js", "bin": { @@ -26,9 +26,9 @@ "outputPath": "build" }, "dependencies": { - "@budibase/backend-core": "2.2.12-alpha.68", - "@budibase/string-templates": "2.2.12-alpha.68", - "@budibase/types": "2.2.12-alpha.68", + "@budibase/backend-core": "2.2.12-alpha.70", + "@budibase/string-templates": "2.2.12-alpha.70", + "@budibase/types": "2.2.12-alpha.70", "axios": "0.21.2", "chalk": "4.1.0", "cli-progress": "3.11.2", diff --git a/packages/client/manifest.json b/packages/client/manifest.json index d1898a82c1..e24fa3a68a 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -3241,7 +3241,7 @@ }, "settings": [ { - "type": "field/barcode/qr", + "type": "field/barcodeqr", "label": "Field", "key": "field", "required": true diff --git a/packages/client/package.json b/packages/client/package.json index 423fa18357..85a192345e 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/client", - "version": "2.2.12-alpha.68", + "version": "2.2.12-alpha.70", "license": "MPL-2.0", "module": "dist/budibase-client.js", "main": "dist/budibase-client.js", @@ -19,9 +19,9 @@ "dev:builder": "rollup -cw" }, "dependencies": { - "@budibase/bbui": "2.2.12-alpha.68", - "@budibase/frontend-core": "2.2.12-alpha.68", - "@budibase/string-templates": "2.2.12-alpha.68", + "@budibase/bbui": "2.2.12-alpha.70", + "@budibase/frontend-core": "2.2.12-alpha.70", + "@budibase/string-templates": "2.2.12-alpha.70", "@spectrum-css/button": "^3.0.3", "@spectrum-css/card": "^3.0.3", "@spectrum-css/divider": "^1.0.3", diff --git a/packages/frontend-core/package.json b/packages/frontend-core/package.json index be62fae760..5ce042533f 100644 --- a/packages/frontend-core/package.json +++ b/packages/frontend-core/package.json @@ -1,12 +1,12 @@ { "name": "@budibase/frontend-core", - "version": "2.2.12-alpha.68", + "version": "2.2.12-alpha.70", "description": "Budibase frontend core libraries used in builder and client", "author": "Budibase", "license": "MPL-2.0", "svelte": "src/index.js", "dependencies": { - "@budibase/bbui": "2.2.12-alpha.68", + "@budibase/bbui": "2.2.12-alpha.70", "lodash": "^4.17.21", "svelte": "^3.46.2" } diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 6b015d6ea2..4c6b356f3c 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/sdk", - "version": "2.2.12-alpha.68", + "version": "2.2.12-alpha.70", "description": "Budibase Public API SDK", "author": "Budibase", "license": "MPL-2.0", diff --git a/packages/server/package.json b/packages/server/package.json index 570044d56c..d46064fed9 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/server", "email": "hi@budibase.com", - "version": "2.2.12-alpha.68", + "version": "2.2.12-alpha.70", "description": "Budibase Web Server", "main": "src/index.ts", "repository": { @@ -43,11 +43,11 @@ "license": "GPL-3.0", "dependencies": { "@apidevtools/swagger-parser": "10.0.3", - "@budibase/backend-core": "2.2.12-alpha.68", - "@budibase/client": "2.2.12-alpha.68", - "@budibase/pro": "2.2.12-alpha.68", - "@budibase/string-templates": "2.2.12-alpha.68", - "@budibase/types": "2.2.12-alpha.68", + "@budibase/backend-core": "2.2.12-alpha.70", + "@budibase/client": "2.2.12-alpha.70", + "@budibase/pro": "2.2.12-alpha.70", + "@budibase/string-templates": "2.2.12-alpha.70", + "@budibase/types": "2.2.12-alpha.70", "@bull-board/api": "3.7.0", "@bull-board/koa": "3.9.4", "@elastic/elasticsearch": "7.10.0", diff --git a/packages/server/yarn.lock b/packages/server/yarn.lock index 52cfea9ce4..9ad655e561 100644 --- a/packages/server/yarn.lock +++ b/packages/server/yarn.lock @@ -1278,13 +1278,13 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@budibase/backend-core@2.2.12-alpha.68": - version "2.2.12-alpha.68" - resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.2.12-alpha.68.tgz#9efda78dfa35d3c431da188b4e3a0011b734c6b2" - integrity sha512-k+Edcvz3XcddlJv9YR+TuoYs7e583lpf9nRCu6WOO889s9e8QS+zzfkC9++Vx8aH16JTizibPDY9oNeRrMQALw== +"@budibase/backend-core@2.2.12-alpha.70": + version "2.2.12-alpha.70" + resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.2.12-alpha.70.tgz#9894e8d676cfa25f8eb163c623eda003a43a2016" + integrity sha512-1nZzkZ6kQyrfL9IvNp09JUrdpCzxtE59CAW26SbXNwId5qEJLSa7zVvZCTNo2K644XNM6qCJzA5LwCwW67AL3A== dependencies: "@budibase/nano" "10.1.1" - "@budibase/types" "2.2.12-alpha.68" + "@budibase/types" "2.2.12-alpha.70" "@shopify/jest-koa-mocks" "5.0.1" "@techpass/passport-openidconnect" "0.3.2" aws-cloudfront-sign "2.2.0" @@ -1379,13 +1379,13 @@ qs "^6.11.0" tough-cookie "^4.1.2" -"@budibase/pro@2.2.12-alpha.68": - version "2.2.12-alpha.68" - resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.2.12-alpha.68.tgz#ee992a6451ecaabdb71b5c4d5f9a269710524529" - integrity sha512-jp+gYg03Q39kc9PIEREC/3QikTzW9mavGrpnWQNcaFyELwmmRbI5tDZkxRmK38TNuW/1ArqKricd9uCVRb3UGA== +"@budibase/pro@2.2.12-alpha.70": + version "2.2.12-alpha.70" + resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.2.12-alpha.70.tgz#067fce5e023817ead1452f21b7abe0ab3585d1d3" + integrity sha512-V3EwQd4r/wztKPzbeLbTHJGWvYUqDELbx0HPTSbI24ee9ZFwF/Wjaapg1I6VHyku1rWgzny4/Y0+H4GvwzzEeg== dependencies: - "@budibase/backend-core" "2.2.12-alpha.68" - "@budibase/types" "2.2.12-alpha.68" + "@budibase/backend-core" "2.2.12-alpha.70" + "@budibase/types" "2.2.12-alpha.70" "@koa/router" "8.0.8" bull "4.10.1" joi "17.6.0" @@ -1411,10 +1411,10 @@ svelte-apexcharts "^1.0.2" svelte-flatpickr "^3.1.0" -"@budibase/types@2.2.12-alpha.68": - version "2.2.12-alpha.68" - resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.2.12-alpha.68.tgz#6284f083aceca49a8de52ebc15c9da8c8416586e" - integrity sha512-xNl/L6M8X+qcVytBgdPSWNM7CYk7Rr2I+ubx5+3u4Z8tF3IWoqk6pj7hMMCORAYAGK7ZdjG7xx+tvXiKK8v1NQ== +"@budibase/types@2.2.12-alpha.70": + version "2.2.12-alpha.70" + resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.2.12-alpha.70.tgz#c86f43b953d3b15c6f286903ac94091ba9c6cef3" + integrity sha512-+6f3uc6fCviRCeDNoKftcg8iuXksj2F+teCPXVmGNAs+tfwqYjX/32s5DB9EjdE3qHC70XAX+lvTWIrrNchDDA== "@bull-board/api@3.7.0": version "3.7.0" diff --git a/packages/string-templates/package.json b/packages/string-templates/package.json index a35efd87c5..5629a78a68 100644 --- a/packages/string-templates/package.json +++ b/packages/string-templates/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/string-templates", - "version": "2.2.12-alpha.68", + "version": "2.2.12-alpha.70", "description": "Handlebars wrapper for Budibase templating.", "main": "src/index.cjs", "module": "dist/bundle.mjs", diff --git a/packages/types/package.json b/packages/types/package.json index 7a89209e80..dc1e5e21b2 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/types", - "version": "2.2.12-alpha.68", + "version": "2.2.12-alpha.70", "description": "Budibase types", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/worker/package.json b/packages/worker/package.json index 9771604c01..50482dca5a 100644 --- a/packages/worker/package.json +++ b/packages/worker/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/worker", "email": "hi@budibase.com", - "version": "2.2.12-alpha.68", + "version": "2.2.12-alpha.70", "description": "Budibase background service", "main": "src/index.ts", "repository": { @@ -36,10 +36,10 @@ "author": "Budibase", "license": "GPL-3.0", "dependencies": { - "@budibase/backend-core": "2.2.12-alpha.68", - "@budibase/pro": "2.2.12-alpha.68", - "@budibase/string-templates": "2.2.12-alpha.68", - "@budibase/types": "2.2.12-alpha.68", + "@budibase/backend-core": "2.2.12-alpha.70", + "@budibase/pro": "2.2.12-alpha.70", + "@budibase/string-templates": "2.2.12-alpha.70", + "@budibase/types": "2.2.12-alpha.70", "@koa/router": "8.0.8", "@sentry/node": "6.17.7", "@techpass/passport-openidconnect": "0.3.2", diff --git a/packages/worker/yarn.lock b/packages/worker/yarn.lock index da216f1b0f..755cc78a3b 100644 --- a/packages/worker/yarn.lock +++ b/packages/worker/yarn.lock @@ -475,13 +475,13 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@budibase/backend-core@2.2.12-alpha.68": - version "2.2.12-alpha.68" - resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.2.12-alpha.68.tgz#9efda78dfa35d3c431da188b4e3a0011b734c6b2" - integrity sha512-k+Edcvz3XcddlJv9YR+TuoYs7e583lpf9nRCu6WOO889s9e8QS+zzfkC9++Vx8aH16JTizibPDY9oNeRrMQALw== +"@budibase/backend-core@2.2.12-alpha.70": + version "2.2.12-alpha.70" + resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.2.12-alpha.70.tgz#9894e8d676cfa25f8eb163c623eda003a43a2016" + integrity sha512-1nZzkZ6kQyrfL9IvNp09JUrdpCzxtE59CAW26SbXNwId5qEJLSa7zVvZCTNo2K644XNM6qCJzA5LwCwW67AL3A== dependencies: "@budibase/nano" "10.1.1" - "@budibase/types" "2.2.12-alpha.68" + "@budibase/types" "2.2.12-alpha.70" "@shopify/jest-koa-mocks" "5.0.1" "@techpass/passport-openidconnect" "0.3.2" aws-cloudfront-sign "2.2.0" @@ -526,13 +526,13 @@ qs "^6.11.0" tough-cookie "^4.1.2" -"@budibase/pro@2.2.12-alpha.68": - version "2.2.12-alpha.68" - resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.2.12-alpha.68.tgz#ee992a6451ecaabdb71b5c4d5f9a269710524529" - integrity sha512-jp+gYg03Q39kc9PIEREC/3QikTzW9mavGrpnWQNcaFyELwmmRbI5tDZkxRmK38TNuW/1ArqKricd9uCVRb3UGA== +"@budibase/pro@2.2.12-alpha.70": + version "2.2.12-alpha.70" + resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.2.12-alpha.70.tgz#067fce5e023817ead1452f21b7abe0ab3585d1d3" + integrity sha512-V3EwQd4r/wztKPzbeLbTHJGWvYUqDELbx0HPTSbI24ee9ZFwF/Wjaapg1I6VHyku1rWgzny4/Y0+H4GvwzzEeg== dependencies: - "@budibase/backend-core" "2.2.12-alpha.68" - "@budibase/types" "2.2.12-alpha.68" + "@budibase/backend-core" "2.2.12-alpha.70" + "@budibase/types" "2.2.12-alpha.70" "@koa/router" "8.0.8" bull "4.10.1" joi "17.6.0" @@ -540,10 +540,10 @@ lru-cache "^7.14.1" node-fetch "^2.6.1" -"@budibase/types@2.2.12-alpha.68": - version "2.2.12-alpha.68" - resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.2.12-alpha.68.tgz#6284f083aceca49a8de52ebc15c9da8c8416586e" - integrity sha512-xNl/L6M8X+qcVytBgdPSWNM7CYk7Rr2I+ubx5+3u4Z8tF3IWoqk6pj7hMMCORAYAGK7ZdjG7xx+tvXiKK8v1NQ== +"@budibase/types@2.2.12-alpha.70": + version "2.2.12-alpha.70" + resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.2.12-alpha.70.tgz#c86f43b953d3b15c6f286903ac94091ba9c6cef3" + integrity sha512-+6f3uc6fCviRCeDNoKftcg8iuXksj2F+teCPXVmGNAs+tfwqYjX/32s5DB9EjdE3qHC70XAX+lvTWIrrNchDDA== "@cspotcode/source-map-support@^0.8.0": version "0.8.1" diff --git a/scripts/install-contributor-dependencies.sh b/scripts/install-contributor-dependencies.sh new file mode 100755 index 0000000000..923dedd804 --- /dev/null +++ b/scripts/install-contributor-dependencies.sh @@ -0,0 +1,73 @@ +function getDistro { + if [ -f /etc/os-release ]; then + # freedesktop.org and systemd + . /etc/os-release + OS=$NAME + VER=$VERSION_ID +elif type lsb_release >/dev/null 2>&1; then + # linuxbase.org + OS=$(lsb_release -si) + VER=$(lsb_release -sr) +elif [ -f /etc/lsb-release ]; then + # For some versions of Debian/Ubuntu without lsb_release command + . /etc/lsb-release + OS=$DISTRIB_ID + VER=$DISTRIB_RELEASE +elif [ -f /etc/debian_version ]; then + # Older Debian/Ubuntu/etc. + OS=Debian + VER=$(cat /etc/debian_version) +elif [ -f /etc/SuSe-release ]; then + # Older SuSE/etc. + : +elif [ -f /etc/redhat-release ]; then + # Older Red Hat, CentOS, etc. + VER=$( cat /etc/redhat-release | cut -d" " -f3 | cut -d "." -f1) + d=$( cat /etc/redhat-release | cut -d" " -f1 | cut -d "." -f1) + if [[ $d == "CentOS" ]]; then + OS="CentOS Linux" + fi +else + # Fall back to uname, e.g. "Linux ", also works for BSD, etc. + OS=$(uname -s) + VER=$(uname -r) +fi +} + +getDistro + +if [[ $OS == "Darwin" ]]; +then + echo "This script is not setup for your machine type:" $OS + echo "Please use the manual steps described in https://github.com/Budibase/budibase/blob/develop/docs/CONTRIBUTING.md#getting-started-for-contributors" + exit 1 +fi + + +# Install brew +if ! command -v brew &> /dev/null +then + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" +fi + +# Install and setup asdf +if ! command -v asdf &> /dev/null +then + brew install asdf + + if test -f ~/.bashrc; then + echo -e "\n. $(brew --prefix asdf)/asdf.sh" >> ~/.bashrc + fi + + if test -f ~/.zshrc; then + echo -e "\n. $(brew --prefix asdf)/asdf.sh" >> ~/.zshrc + fi +fi + +# Install ASDF Plugins +asdf plugin add nodejs +asdf plugin add python + +asdf install + +npm install -g yarn \ No newline at end of file