Merge branch 'rc-56'

This commit is contained in:
Day Matchullis
2026-02-26 12:28:24 -07:00
24 changed files with 424 additions and 192 deletions

View File

@@ -105,8 +105,12 @@ jobs:
# Why does it fill up zfs? I don't understand what it does at all
- name: Free Up ZFS Space
run: |
sudo zfs list -H -o name -t snapshot | grep "snapcraft" | xargs -I{} sudo zfs destroy {}
sudo zfs list -H -o name | grep "snapcraft" | xargs -I{} sudo zfs destroy {}
# Force unmount any snapcraft-related ZFS datasets that are busy
sudo zfs list -H -o name | grep "snapcraft" | sort -r | xargs -I{} sudo zfs unmount -f {} 2>/dev/null || true
# Destroy datasets (clones) first, deepest children first
sudo zfs list -H -o name | grep "snapcraft" | sort -r | xargs -I{} sudo zfs destroy -rR {} 2>/dev/null || true
# Then destroy any remaining snapshots
sudo zfs list -H -o name -t snapshot | grep "snapcraft" | xargs -I{} sudo zfs destroy -rR {} 2>/dev/null || true
if: matrix.os.clean_zfs
- name: Install flatpak tools

34
.github/workflows/ui-kit-publish.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
name: UI Kit - Build & Publish
on:
push:
tags:
- "ui-kit:v*"
jobs:
publish:
runs-on: ubuntu-22.04
steps:
- name: Check out Git repository
uses: actions/checkout@v1
- name: Install Node.js, NPM and Yarn
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
cache: yarn
registry-url: 'https://registry.npmjs.org'
- name: yarn install
run: yarn install --frozen-lockfile --network-timeout 100000
- name: Run tests
run: yarn workspace @beekeeperstudio/ui-kit run test
- name: Build
run: yarn workspace @beekeeperstudio/ui-kit run build
- name: Publish to npm
run: yarn workspace @beekeeperstudio/ui-kit npm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@@ -1,6 +1,6 @@
{
"name": "beekeeper-studio",
"version": "5.6.0-beta.3",
"version": "5.6.0-beta.8",
"private": true,
"description": "An easy-to use SQL query editor and database UI for Mac, Windows, and Linux",
"author": {

View File

@@ -35,7 +35,7 @@
<workspace-rename-modal />
<import-queries-modal />
<import-connections-modal />
<plugin-controller />
<plugin-controller :editor-font-size="editorFontSize" />
<plugin-manager-modal />
<keyboard-shortcuts-modal />
<confirmation-modal-manager />

View File

@@ -1,5 +1,10 @@
<template>
<div v-show="azureAuthEnabled" class="host-port-user-password">
<common-ssl
:config="config"
:ssl-help="sslHelp"
:support-complex-s-s-l="supportComplexSSL"
/>
<div class="alert alert-info">
<i class="material-icons-outlined">info</i>
<div v-if="showCli">
@@ -23,12 +28,7 @@
Azure CLI Path (az)
</label
>
<input
name="cliPath"
type="text"
class="form-control"
v-model="config.azureAuthOptions.cliPath"
/>
<file-picker v-model="config.azureAuthOptions.cliPath"/>
<div class="alert alert-danger" v-show="!cliFound">
<i class="material-icons-outlined">warning</i>
<div>
@@ -49,7 +49,7 @@
style="padding-left: 0.25rem"
v-tooltip="{
content:
'This is the <code>\'Server name\'</code> field on your Sql Server in Azure, <br/> you might also think of this as the hostname. <br/> Eg. <code>example.database.windows.net</code>',
'This is the <code>\'Server name\'</code> field on your database in Azure, <br/> you might also think of this as the hostname. <br/> Eg. <code>example.database.windows.net</code>',
html: true,
}"
>help_outlined</i
@@ -85,6 +85,15 @@
</button>
</div>
</div>
<div class="form-group" v-show="showUser">
<label for="user">User</label>
<input
name="user"
type="text"
class="form-control"
v-model="config.username"
>
</div>
<div class="form-group" v-show="isServicePrincipal">
<label for="tenantId">
Tenant ID
@@ -131,12 +140,24 @@ import { AzureAuthType } from "@/lib/db/types";
import { AppEvent } from "@/common/AppEvent";
import _ from "lodash";
import MaskedInput from '@/components/MaskedInput.vue'
import CommonSsl from './CommonSsl.vue'
import { mapState } from 'vuex'
import FilePicker from '@/components/common/form/FilePicker.vue'
export default {
props: ["config", "authType"],
props: {
config: Object,
authType: [String, Number],
sslHelp: String,
supportComplexSSL: {
type: Boolean,
default: true
}
},
components: {
MaskedInput,
CommonSsl,
FilePicker
},
data() {
return {
@@ -215,14 +236,14 @@ export default {
try {
const result = await this.$util.send('backup/whichDumpTool', {toolName: "az"});
if (result) {
this.config.azureAuthOptions.cliPath = result;
this.$set(this.config.azureAuthOptions, 'cliPath', result);
this.cliError = false;
} else {
this.config.azureAuthOptions.cliPath = null;
this.$set(this.config.azureAuthOptions, 'cliPath', null);
this.cliError = true;
}
} catch (e) {
this.config.azureAuthOptions.cliPath = null;
this.$set(this.config.azureAuthOptions, 'cliPath', null);
this.cliError = true;
}
}

View File

@@ -39,12 +39,7 @@
</div>
</div>
<input
name="cliPath"
type="text"
class="form-control"
v-model="cliPath"
/>
<file-picker v-model="cliPath"/>
</div>
</div>
@@ -125,12 +120,14 @@
<script>
import MaskedInput from '@/components/MaskedInput.vue'
import FilePicker from '@/components/common/form/FilePicker.vue'
import { mapState } from 'vuex'
export default {
props: ['config', 'authType'],
components: {
MaskedInput,
FilePicker
},
data() {
return {

View File

@@ -68,102 +68,11 @@
</div>
</div>
<toggle-form-area
title="Enable SSL"
v-if="supportComplexSSL && supportsSsl"
>
<template v-slot:header>
<x-switch
@click.prevent="toggleSsl"
:toggled="config.ssl"
/>
</template>
<template v-slot:default>
<div class="row gutter">
<div class="alert alert-info">
<i class="material-icons-outlined">info</i>
<div>
Providing certificate files is optional. By default Beekeeper will just trust the server certificate.
<external-link href="https://docs.beekeeperstudio.io/user_guide/connecting/connecting/#ssl">
Read More
</external-link>
</div>
</div>
</div>
<div class="row gutter">
<div class="col form-group">
<label>CA Cert (optional)</label>
<file-picker
v-model="config.sslCaFile"
:disabled="!config.ssl"
/>
</div>
</div>
<div class="row gutter">
<div class="col form-group">
<label>Certificate (optional)</label>
<file-picker
v-model="config.sslCertFile"
:disabled="!config.ssl"
/>
</div>
</div>
<div class="row gutter">
<div class="col form-group">
<label>Key File (optional)</label>
<file-picker
v-model="config.sslKeyFile"
:disabled="!config.ssl"
/>
</div>
</div>
<div class="row gutter">
<div class="col form-group">
<label
class="checkbox-group"
for="reject"
>
<input
class="form-control"
id="reject"
type="checkbox"
name="rememberPassword"
v-model="config.sslRejectUnauthorized"
>
<span>Reject Unauthorized</span>
<i
class="material-icons"
v-tooltip="'This only takes effect if you provide certificate files'"
>help_outlined</i>
</label>
</div>
</div>
</template>
</toggle-form-area>
<!-- Simple SSL -->
<div
v-else-if="supportsSsl"
class="advanced-connection-settings"
>
<div class="flex flex-middle">
<h4
class="advanced-heading flex"
:class="{enabled: config.ssl}"
>
<span class="expand">Enable SSL</span>
<x-switch
@click.prevent="toggleSsl"
:toggled="config.ssl"
/>
</h4>
</div>
<small class="text-muted help">{{ sslHelp }}</small>
</div>
<common-ssl
:config="config"
:ssl-help="sslHelp"
:supportComplexSSL="supportComplexSSL"
/>
<div class="row gutter">
<div class="col form-group" :class="[showPasswordForm ? 's6' : 's12']">
@@ -207,11 +116,9 @@
</template>
<script>
import FilePicker from '@/components/common/form/FilePicker.vue'
import ExternalLink from '@/components/common/ExternalLink.vue'
import { findClient } from '@/lib/db/clients'
import ToggleFormArea from '../common/ToggleFormArea.vue'
import MaskedInput from '@/components/MaskedInput.vue'
import CommonSsl from './CommonSsl.vue'
import { mapState } from 'vuex'
export default {
@@ -228,34 +135,22 @@ export default {
}
},
components: {
FilePicker,
ExternalLink,
ToggleFormArea,
MaskedInput
MaskedInput,
CommonSsl
},
data() {
return {
sslToggled: false,
showPassword: false,
}
},
computed: {
...mapState('settings', ['privacyMode']),
hasAdvancedSsl() {
return this.config.sslCaFile || this.config.sslCertFile || this.config.sslKeyFile
},
toggleIcon() {
return this.sslToggled ? 'keyboard_arrow_down' : 'keyboard_arrow_right'
},
togglePasswordIcon() {
return this.showPassword ? "visibility_off" : "visibility"
},
togglePasswordInputType() {
return this.showPassword ? "text" : "password"
},
supportsSsl() {
return findClient(this.config.connectionType).supports('server:ssl')
},
supportsSocketPath() {
return findClient(this.config.connectionType).supportsSocketPath
},
@@ -276,25 +171,9 @@ export default {
return;
}
},
toggleSsl() {
this.config.ssl = !this.config.ssl
// Remove CA file when disabling ssl
if (!this.config.ssl) {
this.config.sslCaFile = null
this.config.sslCertFile = null
this.config.sslKeyFile = null
}
},
toggleSslAdvanced() {
this.sslToggled = !this.sslToggled;
},
togglePassword() {
this.showPassword = !this.showPassword
}
},
mounted() {
this.sslToggled = this.hasAdvancedSsl
}
}
</script>

View File

@@ -0,0 +1,158 @@
<template>
<div>
<toggle-form-area
title="Enable SSL"
v-if="supportComplexSSL && supportsSsl"
>
<template v-slot:header>
<x-switch
@click.prevent="toggleSsl"
:toggled="config.ssl"
/>
</template>
<template v-slot:default>
<div class="row gutter">
<div class="alert alert-info">
<i class="material-icons-outlined">info</i>
<div>
Providing certificate files is optional. By default Beekeeper will just trust the server certificate.
<external-link href="https://docs.beekeeperstudio.io/user_guide/connecting/connecting/#ssl">
Read More
</external-link>
</div>
</div>
</div>
<div class="row gutter">
<div class="col form-group">
<label>CA Cert (optional)</label>
<file-picker
v-model="config.sslCaFile"
:disabled="!config.ssl"
/>
</div>
</div>
<div class="row gutter">
<div class="col form-group">
<label>Certificate (optional)</label>
<file-picker
v-model="config.sslCertFile"
:disabled="!config.ssl"
/>
</div>
</div>
<div class="row gutter">
<div class="col form-group">
<label>Key File (optional)</label>
<file-picker
v-model="config.sslKeyFile"
:disabled="!config.ssl"
/>
</div>
</div>
<div class="row gutter">
<div class="col form-group">
<label
class="checkbox-group"
for="reject"
>
<input
class="form-control"
id="reject"
type="checkbox"
name="rememberPassword"
v-model="config.sslRejectUnauthorized"
>
<span>Reject Unauthorized</span>
<i
class="material-icons"
v-tooltip="'This only takes effect if you provide certificate files'"
>help_outlined</i>
</label>
</div>
</div>
</template>
</toggle-form-area>
<!-- Simple SSL -->
<div
v-else-if="supportsSsl"
class="advanced-connection-settings"
>
<div class="flex flex-middle">
<h4
class="advanced-heading flex"
:class="{enabled: config.ssl}"
>
<span class="expand">Enable SSL</span>
<x-switch
@click.prevent="toggleSsl"
:toggled="config.ssl"
/>
</h4>
</div>
<small class="text-muted help">{{ sslHelp }}</small>
</div>
</div>
</template>
<script lang="ts">
import FilePicker from '@/components/common/form/FilePicker.vue'
import ExternalLink from '@/components/common/ExternalLink.vue'
import ToggleFormArea from '../common/ToggleFormArea.vue'
import { findClient } from '@/lib/db/clients'
import { PropType } from 'vue'
import { IConnection } from '@/common/interfaces/IConnection'
export default {
props: {
config: Object as PropType<IConnection>,
sslHelp: String,
supportComplexSSL: {
type: Boolean,
default: true
}
},
components: {
FilePicker,
ExternalLink,
ToggleFormArea
},
data() {
return {
sslToggled: false
}
},
computed: {
hasAdvancedSsl() {
return this.config.sslCaFile || this.config.sslCertFile || this.config.sslKeyFile
},
toggleIcon() {
return this.sslToggled ? 'keyboard_arrow_down' : 'keyboard_arrow_right'
},
supportsSsl() {
return findClient(this.config.connectionType).supports('server:ssl')
}
},
methods: {
toggleSsl() {
this.config.ssl = !this.config.ssl
// Remove CA file when disabling ssl
if (!this.config.ssl) {
this.config.sslCaFile = null
this.config.sslCertFile = null
this.config.sslKeyFile = null
}
},
toggleSslAdvanced() {
this.sslToggled = !this.sslToggled
}
},
mounted() {
this.sslToggled = this.hasAdvancedSsl
}
}
</script>

View File

@@ -12,8 +12,8 @@
<common-server-inputs v-show="showServerInputs" :config="config" :show-password-form="showPasswordForm" />
<common-iam v-show="iamAuthenticationEnabled" :auth-type="authType" :config="config" />
<common-advanced :config="config" />
<common-entra-id v-show="azureAuthEnabled" :auth-type="authType" :config="config" />
<common-advanced :config="config" />
</div>
</template>
@@ -33,6 +33,9 @@ export default {
props: ['config'],
mounted() {
this.azureAuthEnabled = this.config?.azureAuthOptions?.azureAuthEnabled || false
if (this.authType !== 'default') {
this.showPasswordForm = false;
}
},
data() {
return {
@@ -40,7 +43,6 @@ export default {
iamAuthenticationEnabled: !!this.config.iamAuthOptions?.iamAuthenticationEnabled,
authType: this.config.iamAuthOptions?.authType || this.config.azureAuthOptions?.azureAuthType || 'default',
authTypes: [{ name: 'Username / Password', value: 'default' }, ...IamAuthTypes, ...AzureAuthTypes.filter(auth => auth.value === AzureAuthType.CLI)],
accountName: null,
signingOut: false,
errorSigningOut: null,
showPasswordForm: true
@@ -67,20 +69,16 @@ export default {
this.showPasswordForm = false;
if (typeof this.authType === 'string' && this.authType.includes('iam')) {
this.iamAuthenticationEnabled = true;
this.azureAuthEnabled = false;
this.config.iamAuthOptions.authType = this.authType;
} else if (this.authType === AzureAuthType.CLI) {
this.azureAuthEnabled = true;
this.iamAuthenticationEnabled = false;
this.config.azureAuthOptions.azureAuthType = this.authType;
}
}
}
const authId = this.config.azureAuthOptions?.authId || this.config?.authId
if (this.authType === AzureAuthType.CLI && !_.isNil(authId)) {
this.accountName = await this.connection.azureGetAccountName(authId);
} else {
this.accountName = null
}
},
config() {
if (this.config.azureAuthOptions.azureAuthEnabled) {

View File

@@ -21,8 +21,8 @@
<input type="text" class="form-control" v-model="config.options.cluster">
</div>
<common-iam v-show="iamAuthenticationEnabled" :auth-type="authType" :config="config" />
<common-advanced :config="config" />
<common-entra-id v-show="azureAuthEnabled" :auth-type="authType" :config="config" />
<common-advanced :config="config" />
</div>
</template>
@@ -52,7 +52,6 @@ export default {
iamAuthenticationEnabled: !!this.config.iamAuthOptions?.iamAuthenticationEnabled,
authType: this.config.iamAuthOptions?.authType || this.config.azureAuthOptions?.azureAuthType || 'default',
authTypes: [{ name: 'Username / Password', value: 'default' }, ...IamAuthTypes, ...AzureAuthTypes.filter(auth => auth.value === AzureAuthType.CLI)],
accountName: null,
signingOut: false,
errorSigningOut: null,
showPasswordForm: true
@@ -88,13 +87,6 @@ export default {
}
}
}
const authId = this.config.azureAuthOptions?.authId || this.config?.authId
if (this.authType === AzureAuthType.AccessToken && !_.isNil(authId)) {
this.accountName = await this.connection.azureGetAccountName(authId);
} else {
this.accountName = null
}
},
config() {
if (this.config.azureAuthOptions.azureAuthEnabled) {

View File

@@ -50,8 +50,8 @@
</div>
</div>
</common-server-inputs>
<common-advanced v-show="!azureAuthEnabled" :config="config" />
<common-entra-id v-show="azureAuthEnabled" :auth-type="authType" :config="config" />
<common-advanced v-show="!azureAuthEnabled" :config="config" />
</div>
</template>
@@ -76,7 +76,6 @@
azureAuthEnabled: false,
authType: 'default',
authTypes: AzureAuthTypes,
accountName: null
}
},
watch: {

View File

@@ -68,6 +68,7 @@ export default Vue.extend({
await this.$nextTick();
if (this.shouldMountIframe) {
this.mountIframe().catch((e) => {
log.error(e);
this.error = e instanceof Error ? e.message : String(e);
});
} else {

View File

@@ -5,6 +5,9 @@ import { AppEvent } from "@/common/AppEvent";
import { NativePluginMenuItem } from "@/services/plugin";
export default Vue.extend({
props: {
editorFontSize: Number,
},
computed: {
rootBindings() {
return [
@@ -19,6 +22,14 @@ export default Vue.extend({
];
},
},
watch: {
editorFontSize() {
this.$plugin.notifyAll({
name: "editorFontSizeChanged",
args: { value: this.editorFontSize },
});
},
},
methods: {
handleChangedTheme(themeValue: string) {
const data: PluginNotificationData = {

View File

@@ -111,11 +111,15 @@ export default Vue.extend({
if (item.expanded) {
expandedMap.set(item.key, true);
}
if (item.pinned) {
pinnedMap.set(item.key, true);
}
}
}
for (const pin of this.pins) {
pinnedMap.set(pin.entity, pin);
const key = entityId(pin.schemaName, pin.entity);
pinnedMap.set(key, true);
}
this.schemaTables.forEach((schema: any) => {
@@ -153,7 +157,7 @@ export default Vue.extend({
contextMenu: this.tableMenuOptions,
parent,
level: noFolder ? 0 : 1,
pinned: pinnedMap.get(table) || false,
pinned: pinnedMap.has(key) || false,
loadingColumns: false,
});
});
@@ -169,7 +173,7 @@ export default Vue.extend({
contextMenu: this.routineMenuOptions,
parent,
level: noFolder ? 0 : 1,
pinned: pinnedMap.get(routine) || false,
pinned: pinnedMap.has(key) || false,
});
});
});
@@ -272,13 +276,12 @@ export default Vue.extend({
},
handleTogglePinned(entity: Entity, pinned?: boolean) {
const item = this.items.find((item: Item) => item.entity === entity);
if (!item) return;
if (typeof pinned === "undefined") {
pinned = !item.pinned;
}
item.pinned = !item.pinned;
if (pinned) this.$store.dispatch('pins/add', entity)
else this.$store.dispatch('pins/remove', entity)
item.pinned = pinned;
},
handleScrollEnd() {
this.updateTableColumnsInRange(true);

View File

@@ -35,6 +35,7 @@ const camelCaseData: AxiosResponseTransformer = (data) => {
export interface CloudClientOptions {
token: string,
app: string,
clientVersion: string,
email: string
baseUrl: string,
workspace?: number
@@ -85,7 +86,8 @@ export class CloudClient {
headers: {
email: options.email,
token: options.token,
app: options.app
app: options.app,
clientVersion: options.clientVersion,
},
validateStatus: (status) => status < 500
})

View File

@@ -6,7 +6,7 @@ import {TokenCache} from '@/common/appdb/models/token_cache';
import globals from '@/common/globals';
import {AzureAuthOptions, AzureAuthType} from '../types';
import {spawn} from 'child_process'
import {getEntraOptions} from "@/lib/db/clients/utils";
import {getEntraOptions, sanitizeCommandPath} from "@/lib/db/clients/utils";
import {IDbConnectionServer} from "@/lib/db/backendTypes";
import BksConfig from '@/common/bksConfig';
@@ -178,14 +178,14 @@ export class AzureAuthService {
}
return new Promise<AuthConfig>((resolve, reject) => {
const proc = spawn(options.cliPath, [
const proc = spawn(sanitizeCommandPath(options.cliPath), [
'account',
'get-access-token',
'--resource',
BksConfig.azure.azSQLLoginScope,
'--output',
'json'
]);
], { shell: true });
let stdout = '';
let stderr = '';

View File

@@ -6,7 +6,6 @@ import { joinFilters } from '@/common/utils'
import { IdentifyResult } from 'sql-query-identifier/lib/defines'
import { fromIni } from "@aws-sdk/credential-providers";
import { Signer } from "@aws-sdk/rds-signer";
import globals from "@/common/globals";
import {
AWSCredentials
} from "@/lib/db/authentication/amazon-redshift";
@@ -15,6 +14,7 @@ import { AuthOptions } from "@/lib/db/authentication/azure";
import { spawn } from "child_process";
import { loadSharedConfigFiles } from "@aws-sdk/shared-ini-file-loader";
import { AwsCredentialIdentity, RuntimeConfigAwsCredentialIdentityProvider } from '@aws-sdk/types'
import platformInfo from '@/common/platform_info'
const log = logRaw.scope('db/util')
@@ -437,6 +437,18 @@ export async function refreshTokenIfNeeded(iamOptions: IamAuthOptions, server: a
return resolvedPw;
}
export function sanitizeCommandPath(path: string): string {
if (!path) return path;
if (platformInfo.isWindows) {
const escaped = path.replace(/"/g, '""');
return `"${escaped}"`;
} else {
const escaped = path.replace(/'/g, "'\\''");
return `'${escaped}'`;
}
}
export async function getAWSCLIToken(server: IDbConnectionServerConfig, options: IamAuthOptions): Promise<string> {
if (!options?.cliPath) {
throw new Error('AZ command not specified');
@@ -449,7 +461,7 @@ export async function getAWSCLIToken(server: IDbConnectionServerConfig, options:
}
return new Promise<string>((resolve, reject) => {
const proc = spawn(options.cliPath, [
const proc = spawn(sanitizeCommandPath(options.cliPath), [
'rds',
'generate-db-auth-token',
'--hostname',
@@ -461,7 +473,7 @@ export async function getAWSCLIToken(server: IDbConnectionServerConfig, options:
'--username',
server.user,
...extraArgs
]);
], { shell: true });
let stdout = '';
let stderr = '';

View File

@@ -59,7 +59,7 @@ export default class PluginStoreService {
}
getTheme() {
const styles = getComputedStyle(document.body);
const styles = getComputedStyle(this.getAppEl());
/** Key = css property, value = css value */
const palette: Record<string, string> = {};
@@ -384,4 +384,8 @@ export default class PluginStoreService {
},
};
}
private getAppEl() {
return document.body.querySelector('.beekeeper-studio-wrapper');
}
}

View File

@@ -54,6 +54,7 @@ export const cssVars = [
"--bks-text-editor-error-fg-color",
"--bks-text-editor-fg-color",
"--bks-text-editor-focused-outline-color",
"--bks-text-editor-font-size",
"--bks-text-editor-foldgutter-fg-color",
"--bks-text-editor-foldgutter-fg-color-hover",
"--bks-text-editor-gutter-bg-color",

View File

@@ -31,7 +31,7 @@ interface State {
async function credentialToBlob(c: TransportCloudCredential): Promise<CredentialBlob> {
const clientOptions: CloudClientOptions = {
app: c.appId, email: c.email, token: c.token, baseUrl: window.platformInfo.cloudUrl
app: c.appId, email: c.email, token: c.token, baseUrl: window.platformInfo.cloudUrl, clientVersion: window.platformInfo.appVersion
}
const client = new CloudClient(clientOptions)
try {

View File

@@ -62,9 +62,9 @@ export const PinConnectionModule: Module<State, RootState> = {
return;
}
const newPin = await Vue.prototype.$util.send('appdb/pinconn/new', { init: item });
let newPin = await Vue.prototype.$util.send('appdb/pinconn/new', { init: item });
newPin.position = (context.getters.orderedPins.reverse()[0]?.position || 0) + 1;
await Vue.prototype.$util.send('appdb/pinconn/save', { obj: newPin })
newPin = await Vue.prototype.$util.send('appdb/pinconn/save', { obj: newPin })
context.commit('add', newPin);
},
async reorder(context) {

View File

@@ -7,7 +7,7 @@ import Vue from "vue";
function matches(pin: TransportPinnedEntity, entity: DatabaseEntity, database?: string) {
return entity.name === pin.entityName &&
((_.isNil(entity.schema) && _.isNil(pin.schemaName)) ||
((_.isNil(entity.schema) && _.isNil(pin.schemaName)) ||
entity.schema === pin.schemaName) &&
entity.entityType === pin.entityType &&
(!database || database === pin.databaseName)
@@ -75,7 +75,7 @@ export const PinModule: Module<State, RootState> = {
// this used to be !p.hasId(), hopefully this still works? the alternative is ugly
const unsavedPins = context.state.pins.filter((p)=> !p.id)
await Promise.all(unsavedPins.map((p) => {
p.connectionId === usedConfig.id && p.workspaceId === usedConfig.id &&
p.connectionId === usedConfig.id && p.workspaceId === usedConfig.id &&
Vue.prototype.$util.send('appdb/pins/save', { obj: p });
}))
},
@@ -86,7 +86,7 @@ export const PinModule: Module<State, RootState> = {
if (database && usedConfig) {
console.log('GETTING NEW PIN: ', item, database, usedConfig)
const newPin = await Vue.prototype.$util.send('appdb/pins/new', {
let newPin = await Vue.prototype.$util.send('appdb/pins/new', {
init: {
table: item,
db: database,
@@ -95,7 +95,9 @@ export const PinModule: Module<State, RootState> = {
});
console.log('RECEIVED NEW PIN: ', newPin)
newPin.position = (context.getters.orderedPins.reverse()[0]?.position || 0) + 1
if(usedConfig.id) await Vue.prototype.$util.send('appdb/pins/save', { obj: newPin });
if(usedConfig.id) {
newPin = await Vue.prototype.$util.send('appdb/pins/save', { obj: newPin });
}
context.commit('add', newPin)
}
},

View File

@@ -7,7 +7,7 @@
"url": "https://beekeeperstudio.io"
},
"private": false,
"version": "0.3.1",
"version": "0.3.2",
"repository": {
"type": "git",
"url": "git+https://github.com/beekeeper-studio/beekeeper-studio.git",

114
bin/make-ui-kit-release.sh Executable file
View File

@@ -0,0 +1,114 @@
#!/bin/bash
set -euo pipefail
TAG_PREFIX="ui-kit:v"
# Function to list the 5 most recent remote ui-kit tags by date
list_recent_remote_tags() {
echo "Fetching the 5 most recent remote ui-kit tags by date:"
git ls-remote --tags origin | \
grep -E "refs/tags/${TAG_PREFIX}[0-9]+\.[0-9]+\.[0-9]+(-[a-z]+\.[0-9]+)?$" | \
while read -r hash ref; do
tag="${ref#refs/tags/}"
date=$(git log -1 --format='%ci' "$hash" 2>/dev/null || echo "unknown date")
echo "$tag $date"
done | sort -k2 -r | head -n 5
}
# Function to get the next version in sequence
guess_next_version() {
# Get the most recent ui-kit tag
local latest_tag
latest_tag=$(git ls-remote --tags origin | \
grep -Eo "${TAG_PREFIX}[0-9]+\.[0-9]+\.[0-9]+(-[a-z]+\.[0-9]+)?$" | \
sed "s/^${TAG_PREFIX}//" | \
sort -V | tail -n 1)
if [[ -z "$latest_tag" ]]; then
echo "0.1.0"
return
fi
if [[ "$latest_tag" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)(-(alpha|beta)\.([0-9]+))?$ ]]; then
major="${BASH_REMATCH[1]}"
minor="${BASH_REMATCH[2]}"
patch="${BASH_REMATCH[3]}"
channel="${BASH_REMATCH[5]:-}"
channel_num="${BASH_REMATCH[6]:-}"
if [[ -n "$channel" ]]; then
# Increment the channel number for pre-releases
channel_num=$((channel_num + 1))
echo "$major.$minor.$patch-$channel.$channel_num"
else
# Increment the patch version for stable releases
patch=$((patch + 1))
echo "$major.$minor.$patch"
fi
else
echo "0.1.0" # Default to this if no valid tags exist
fi
}
# Function to validate version format
validate_version() {
if [[ "$1" =~ ^v?([0-9]+\.[0-9]+\.[0-9]+(-[a-z]+\.[0-9]+)?)$ ]]; then
echo "${BASH_REMATCH[1]}"
else
echo "Error: Invalid version format. Expected x.x.x or x.x.x-channel.x"
exit 1
fi
}
# Step 1: List recent tags
list_recent_remote_tags
# Step 2: Prompt user for new version with a default guess
default_version=$(guess_next_version)
echo ""
read -p "Enter the new version (default: $default_version): " INPUT_VERSION
VERSION="${INPUT_VERSION:-$default_version}"
# Step 3: Validate and clean the version
VERSION=$(validate_version "$VERSION")
NEW_TAG="${TAG_PREFIX}${VERSION}"
# Confirm with the user
echo ""
echo "This will:"
echo " - Update apps/ui-kit/package.json to version: $VERSION"
echo " - Push a new git tag: $NEW_TAG"
read -p "Do you want to continue? (y/n): " CONFIRM
if [[ "$CONFIRM" != "y" ]]; then
echo "Aborted."
exit 0
fi
# Step 4: Update package.json if necessary
PACKAGE_VERSION=$(jq -r '.version' apps/ui-kit/package.json)
if [[ "$PACKAGE_VERSION" == "$VERSION" ]]; then
echo "package.json is already updated to version $VERSION."
else
echo "Updating apps/ui-kit/package.json..."
jq ".version = \"$VERSION\"" apps/ui-kit/package.json > apps/ui-kit/package.temp.json
mv apps/ui-kit/package.temp.json apps/ui-kit/package.json
# Commit changes
echo "Committing changes..."
git add apps/ui-kit/package.json
git commit -m "chore: bump ui-kit version to $VERSION"
git push
fi
# Step 5: Push tag
if git tag | grep -q "^${NEW_TAG}\$"; then
echo "Tag $NEW_TAG already exists."
else
echo "Creating and pushing tag $NEW_TAG..."
git tag "$NEW_TAG"
git push origin "$NEW_TAG"
fi
echo "UI Kit release process completed successfully."