Merge master.
This commit is contained in:
commit
e267096a0a
|
@ -12,4 +12,5 @@ packages/sdk/sdk
|
||||||
packages/account-portal/packages/server/build
|
packages/account-portal/packages/server/build
|
||||||
packages/account-portal/packages/ui/.routify
|
packages/account-portal/packages/ui/.routify
|
||||||
packages/account-portal/packages/ui/build
|
packages/account-portal/packages/ui/build
|
||||||
**/*.ivm.bundle.js
|
**/*.ivm.bundle.js
|
||||||
|
packages/server/build/oldClientVersions/**/**
|
||||||
|
|
|
@ -161,7 +161,7 @@ jobs:
|
||||||
docker pull postgres:16.1-bullseye
|
docker pull postgres:16.1-bullseye
|
||||||
docker pull mongo:7.0-jammy
|
docker pull mongo:7.0-jammy
|
||||||
docker pull mariadb:lts
|
docker pull mariadb:lts
|
||||||
docker pull testcontainers/ryuk:0.3.0
|
docker pull testcontainers/ryuk:0.5.1
|
||||||
docker pull budibase/couchdb
|
docker pull budibase/couchdb
|
||||||
|
|
||||||
- run: yarn --frozen-lockfile
|
- run: yarn --frozen-lockfile
|
||||||
|
|
|
@ -5,6 +5,9 @@ packages/server/runtime_apps/
|
||||||
bb-airgapped.tar.gz
|
bb-airgapped.tar.gz
|
||||||
*.iml
|
*.iml
|
||||||
|
|
||||||
|
packages/server/build/oldClientVersions/**/*
|
||||||
|
packages/builder/src/components/deploy/clientVersions.json
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
*.log
|
*.log
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
{
|
{
|
||||||
// Use IntelliSense to learn about possible attributes.
|
// Use IntelliSense to learn about possible attributes.
|
||||||
// Hover to view descriptions of existing attributes.
|
// Hover to view descriptions of existing attributes.
|
||||||
|
@ -20,6 +19,13 @@
|
||||||
"runtimeArgs": ["--nolazy", "-r", "ts-node/register/transpile-only"],
|
"runtimeArgs": ["--nolazy", "-r", "ts-node/register/transpile-only"],
|
||||||
"args": ["${workspaceFolder}/packages/worker/src/index.ts"],
|
"args": ["${workspaceFolder}/packages/worker/src/index.ts"],
|
||||||
"cwd": "${workspaceFolder}/packages/worker"
|
"cwd": "${workspaceFolder}/packages/worker"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "chrome",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Launch Chrome against localhost",
|
||||||
|
"url": "http://localhost:10000",
|
||||||
|
"webRoot": "${workspaceFolder}"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"compounds": [
|
"compounds": [
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { GenericContainer, Wait } from "testcontainers"
|
||||||
|
|
||||||
|
export default async function setup() {
|
||||||
|
await new GenericContainer("budibase/couchdb")
|
||||||
|
.withExposedPorts(5984)
|
||||||
|
.withEnvironment({
|
||||||
|
COUCHDB_PASSWORD: "budibase",
|
||||||
|
COUCHDB_USER: "budibase",
|
||||||
|
})
|
||||||
|
.withCopyContentToContainer([
|
||||||
|
{
|
||||||
|
content: `
|
||||||
|
[log]
|
||||||
|
level = warn
|
||||||
|
`,
|
||||||
|
target: "/opt/couchdb/etc/local.d/test-couchdb.ini",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
.withWaitStrategy(
|
||||||
|
Wait.forSuccessfulCommand(
|
||||||
|
"curl http://budibase:budibase@localhost:5984/_up"
|
||||||
|
).withStartupTimeout(20000)
|
||||||
|
)
|
||||||
|
.start()
|
||||||
|
}
|
|
@ -1,16 +0,0 @@
|
||||||
module.exports = () => {
|
|
||||||
return {
|
|
||||||
couchdb: {
|
|
||||||
image: "budibase/couchdb",
|
|
||||||
ports: [5984],
|
|
||||||
env: {
|
|
||||||
COUCHDB_PASSWORD: "budibase",
|
|
||||||
COUCHDB_USER: "budibase",
|
|
||||||
},
|
|
||||||
wait: {
|
|
||||||
type: "ports",
|
|
||||||
timeout: 20000,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -31,6 +31,7 @@
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"preinstall": "node scripts/syncProPackage.js",
|
"preinstall": "node scripts/syncProPackage.js",
|
||||||
|
"get-past-client-version": "node scripts/getPastClientVersion.js",
|
||||||
"setup": "git config submodule.recurse true && git submodule update && node ./hosting/scripts/setup.js && yarn && yarn build && yarn dev",
|
"setup": "git config submodule.recurse true && git submodule update && node ./hosting/scripts/setup.js && yarn && yarn build && yarn dev",
|
||||||
"build": "NODE_OPTIONS=--max-old-space-size=1500 lerna run build --stream",
|
"build": "NODE_OPTIONS=--max-old-space-size=1500 lerna run build --stream",
|
||||||
"build:dev": "lerna run --stream prebuild && yarn nx run-many --target=build --output-style=dynamic --watch --preserveWatchOutput",
|
"build:dev": "lerna run --stream prebuild && yarn nx run-many --target=build --output-style=dynamic --watch --preserveWatchOutput",
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
const { join } = require("path")
|
|
||||||
require("dotenv").config({
|
|
||||||
path: join(__dirname, "..", "..", "hosting", ".env"),
|
|
||||||
})
|
|
||||||
|
|
||||||
const jestTestcontainersConfigGenerator = require("../../jestTestcontainersConfigGenerator")
|
|
||||||
|
|
||||||
module.exports = jestTestcontainersConfigGenerator()
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { Config } from "@jest/types"
|
import { Config } from "@jest/types"
|
||||||
|
|
||||||
const baseConfig: Config.InitialProjectOptions = {
|
const baseConfig: Config.InitialProjectOptions = {
|
||||||
preset: "@trendyol/jest-testcontainers",
|
|
||||||
setupFiles: ["./tests/jestEnv.ts"],
|
setupFiles: ["./tests/jestEnv.ts"],
|
||||||
|
globalSetup: "./../../globalSetup.ts",
|
||||||
setupFilesAfterEnv: ["./tests/jestSetup.ts"],
|
setupFilesAfterEnv: ["./tests/jestSetup.ts"],
|
||||||
transform: {
|
transform: {
|
||||||
"^.+\\.ts?$": "@swc/jest",
|
"^.+\\.ts?$": "@swc/jest",
|
||||||
|
|
|
@ -60,7 +60,6 @@
|
||||||
"@shopify/jest-koa-mocks": "5.1.1",
|
"@shopify/jest-koa-mocks": "5.1.1",
|
||||||
"@swc/core": "1.3.71",
|
"@swc/core": "1.3.71",
|
||||||
"@swc/jest": "0.2.27",
|
"@swc/jest": "0.2.27",
|
||||||
"@trendyol/jest-testcontainers": "^2.1.1",
|
|
||||||
"@types/chance": "1.1.3",
|
"@types/chance": "1.1.3",
|
||||||
"@types/cookies": "0.7.8",
|
"@types/cookies": "0.7.8",
|
||||||
"@types/jest": "29.5.5",
|
"@types/jest": "29.5.5",
|
||||||
|
|
|
@ -27,7 +27,7 @@ class Replication {
|
||||||
return resolve(info)
|
return resolve(info)
|
||||||
})
|
})
|
||||||
.on("error", function (err) {
|
.on("error", function (err) {
|
||||||
throw new Error(`Replication Error: ${err}`)
|
throw err
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Event } from "@budibase/types"
|
import { Event, Identity } from "@budibase/types"
|
||||||
import { processors } from "./processors"
|
import { processors } from "./processors"
|
||||||
import identification from "./identification"
|
import identification from "./identification"
|
||||||
import * as backfill from "./backfill"
|
import * as backfill from "./backfill"
|
||||||
|
@ -7,12 +7,19 @@ import { publishAsyncEvent } from "./asyncEvents"
|
||||||
export const publishEvent = async (
|
export const publishEvent = async (
|
||||||
event: Event,
|
event: Event,
|
||||||
properties: any,
|
properties: any,
|
||||||
timestamp?: string | number
|
timestamp?: string | number,
|
||||||
|
identityOverride?: Identity
|
||||||
) => {
|
) => {
|
||||||
// in future this should use async events via a distributed queue.
|
// in future this should use async events via a distributed queue.
|
||||||
const identity = await identification.getCurrentIdentity()
|
const identity =
|
||||||
|
identityOverride || (await identification.getCurrentIdentity())
|
||||||
|
|
||||||
|
// Backfilling is get from the user cache, but when we override the identity cache is not available. Overrides are
|
||||||
|
// normally performed in automatic actions or operations in async flows (BPM) where the user session is not available.
|
||||||
|
const backfilling = identityOverride
|
||||||
|
? false
|
||||||
|
: await backfill.isBackfillingEvent(event)
|
||||||
|
|
||||||
const backfilling = await backfill.isBackfillingEvent(event)
|
|
||||||
// no backfill - send the event and exit
|
// no backfill - send the event and exit
|
||||||
if (!backfilling) {
|
if (!backfilling) {
|
||||||
// send off async events if required
|
// send off async events if required
|
||||||
|
|
|
@ -5,13 +5,19 @@ import {
|
||||||
AccountCreatedEvent,
|
AccountCreatedEvent,
|
||||||
AccountDeletedEvent,
|
AccountDeletedEvent,
|
||||||
AccountVerifiedEvent,
|
AccountVerifiedEvent,
|
||||||
|
Identity,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
|
||||||
async function created(account: Account) {
|
async function created(account: Account, identityOverride?: Identity) {
|
||||||
const properties: AccountCreatedEvent = {
|
const properties: AccountCreatedEvent = {
|
||||||
tenantId: account.tenantId,
|
tenantId: account.tenantId,
|
||||||
}
|
}
|
||||||
await publishEvent(Event.ACCOUNT_CREATED, properties)
|
await publishEvent(
|
||||||
|
Event.ACCOUNT_CREATED,
|
||||||
|
properties,
|
||||||
|
undefined,
|
||||||
|
identityOverride
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleted(account: Account) {
|
async function deleted(account: Account) {
|
||||||
|
|
|
@ -500,13 +500,13 @@ export class UserDB {
|
||||||
|
|
||||||
static async createAdminUser(
|
static async createAdminUser(
|
||||||
email: string,
|
email: string,
|
||||||
password: string,
|
|
||||||
tenantId: string,
|
tenantId: string,
|
||||||
|
password?: string,
|
||||||
opts?: CreateAdminUserOpts
|
opts?: CreateAdminUserOpts
|
||||||
) {
|
) {
|
||||||
const user: User = {
|
const user: User = {
|
||||||
email: email,
|
email: email,
|
||||||
password: password,
|
password,
|
||||||
createdAt: Date.now(),
|
createdAt: Date.now(),
|
||||||
roles: {},
|
roles: {},
|
||||||
builder: {
|
builder: {
|
||||||
|
|
|
@ -1,80 +1,58 @@
|
||||||
|
import { DatabaseImpl } from "../../../src/db"
|
||||||
import { execSync } from "child_process"
|
import { execSync } from "child_process"
|
||||||
|
|
||||||
let dockerPsResult: string | undefined
|
interface ContainerInfo {
|
||||||
|
Command: string
|
||||||
function formatDockerPsResult(serverName: string, port: number) {
|
CreatedAt: string
|
||||||
const lines = dockerPsResult?.split("\n")
|
ID: string
|
||||||
let first = true
|
Image: string
|
||||||
if (!lines) {
|
Labels: string
|
||||||
return null
|
LocalVolumes: string
|
||||||
}
|
Mounts: string
|
||||||
for (let line of lines) {
|
Names: string
|
||||||
if (first) {
|
Networks: string
|
||||||
first = false
|
Ports: string
|
||||||
continue
|
RunningFor: string
|
||||||
}
|
Size: string
|
||||||
let toLookFor = serverName.split("-service")[0]
|
State: string
|
||||||
if (!line.includes(toLookFor)) {
|
Status: string
|
||||||
continue
|
|
||||||
}
|
|
||||||
const regex = new RegExp(`0.0.0.0:([0-9]*)->${port}`, "g")
|
|
||||||
const found = line.match(regex)
|
|
||||||
if (found) {
|
|
||||||
return found[0].split(":")[1].split("->")[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTestContainerSettings(
|
function getTestcontainers(): ContainerInfo[] {
|
||||||
serverName: string,
|
return execSync("docker ps --format json")
|
||||||
key: string
|
.toString()
|
||||||
): string | null {
|
.split("\n")
|
||||||
const entry = Object.entries(global).find(
|
.filter(x => x.length > 0)
|
||||||
([k]) =>
|
.map(x => JSON.parse(x) as ContainerInfo)
|
||||||
k.includes(`${serverName.toUpperCase()}`) &&
|
.filter(x => x.Labels.includes("org.testcontainers=true"))
|
||||||
k.includes(`${key.toUpperCase()}`)
|
|
||||||
)
|
|
||||||
if (!entry) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return entry[1]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getContainerInfo(containerName: string, port: number) {
|
function getContainerByImage(image: string) {
|
||||||
let assignedPort = getTestContainerSettings(
|
return getTestcontainers().find(x => x.Image.startsWith(image))
|
||||||
containerName.toUpperCase(),
|
|
||||||
`PORT_${port}`
|
|
||||||
)
|
|
||||||
if (!dockerPsResult) {
|
|
||||||
try {
|
|
||||||
const outputBuffer = execSync("docker ps")
|
|
||||||
dockerPsResult = outputBuffer.toString("utf8")
|
|
||||||
} catch (err) {
|
|
||||||
//no-op
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const possiblePort = formatDockerPsResult(containerName, port)
|
|
||||||
if (possiblePort) {
|
|
||||||
assignedPort = possiblePort
|
|
||||||
}
|
|
||||||
const host = getTestContainerSettings(containerName.toUpperCase(), "IP")
|
|
||||||
return {
|
|
||||||
port: assignedPort,
|
|
||||||
host,
|
|
||||||
url: host && assignedPort && `http://${host}:${assignedPort}`,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCouchConfig() {
|
function getExposedPort(container: ContainerInfo, port: number) {
|
||||||
return getContainerInfo("couchdb", 5984)
|
const match = container.Ports.match(new RegExp(`0.0.0.0:(\\d+)->${port}/tcp`))
|
||||||
|
if (!match) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
return parseInt(match[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setupEnv(...envs: any[]) {
|
export function setupEnv(...envs: any[]) {
|
||||||
const couch = getCouchConfig()
|
const couch = getContainerByImage("budibase/couchdb")
|
||||||
|
if (!couch) {
|
||||||
|
throw new Error("CouchDB container not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
const couchPort = getExposedPort(couch, 5984)
|
||||||
|
if (!couchPort) {
|
||||||
|
throw new Error("CouchDB port not found")
|
||||||
|
}
|
||||||
|
|
||||||
const configs = [
|
const configs = [
|
||||||
{ key: "COUCH_DB_PORT", value: couch.port },
|
{ key: "COUCH_DB_PORT", value: `${couchPort}` },
|
||||||
{ key: "COUCH_DB_URL", value: couch.url },
|
{ key: "COUCH_DB_URL", value: `http://localhost:${couchPort}` },
|
||||||
]
|
]
|
||||||
|
|
||||||
for (const config of configs.filter(x => !!x.value)) {
|
for (const config of configs.filter(x => !!x.value)) {
|
||||||
|
@ -82,4 +60,7 @@ export function setupEnv(...envs: any[]) {
|
||||||
env._set(config.key, config.value)
|
env._set(config.key, config.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @ts-expect-error
|
||||||
|
DatabaseImpl.nano = undefined
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,3 +4,7 @@ process.env.NODE_ENV = "jest"
|
||||||
process.env.MOCK_REDIS = "1"
|
process.env.MOCK_REDIS = "1"
|
||||||
process.env.LOG_LEVEL = process.env.LOG_LEVEL || "error"
|
process.env.LOG_LEVEL = process.env.LOG_LEVEL || "error"
|
||||||
process.env.REDIS_PASSWORD = "budibase"
|
process.env.REDIS_PASSWORD = "budibase"
|
||||||
|
process.env.COUCH_DB_PASSWORD = "budibase"
|
||||||
|
process.env.COUCH_DB_USER = "budibase"
|
||||||
|
process.env.API_ENCRYPTION_KEY = "testsecret"
|
||||||
|
process.env.JWT_SECRET = "testsecret"
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
{
|
|
||||||
// Use IntelliSense to learn about possible attributes.
|
|
||||||
// Hover to view descriptions of existing attributes.
|
|
||||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
|
||||||
"version": "0.2.0",
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"type": "chrome",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "Launch Chrome against localhost",
|
|
||||||
"url": "http://localhost:3000",
|
|
||||||
"webRoot": "${workspaceFolder}"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
{
|
|
||||||
"javascript.format.enable": false,
|
|
||||||
"svelte.plugin.svelte.format.enable": false,
|
|
||||||
"html.format.enable": false,
|
|
||||||
"json.format.enable": false,
|
|
||||||
"editor.trimAutoWhitespace": false,
|
|
||||||
"sass.format.deleteWhitespace": false
|
|
||||||
}
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
<script>
|
||||||
|
import { API } from "api"
|
||||||
|
import clientVersions from "./clientVersions.json"
|
||||||
|
import { appStore } from "stores/builder"
|
||||||
|
import { Select } from "@budibase/bbui"
|
||||||
|
|
||||||
|
export let revertableVersion
|
||||||
|
$: appId = $appStore.appId
|
||||||
|
|
||||||
|
const handleChange = e => {
|
||||||
|
const value = e.detail
|
||||||
|
if (value == null) return
|
||||||
|
|
||||||
|
API.setRevertableVersion(appId, value)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="select">
|
||||||
|
<Select
|
||||||
|
autoWidth
|
||||||
|
value={revertableVersion}
|
||||||
|
options={clientVersions}
|
||||||
|
on:change={handleChange}
|
||||||
|
footer={"Older versions of the Budibase client can be acquired using `yarn get-past-client-version x.x.x`. This toggle is only available in dev mode."}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.select {
|
||||||
|
width: 120px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,4 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
|
import { admin } from "stores/portal"
|
||||||
import {
|
import {
|
||||||
Modal,
|
Modal,
|
||||||
notifications,
|
notifications,
|
||||||
|
@ -9,6 +10,7 @@
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { appStore, initialise } from "stores/builder"
|
import { appStore, initialise } from "stores/builder"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
|
import RevertModalVersionSelect from "./RevertModalVersionSelect.svelte"
|
||||||
|
|
||||||
export function show() {
|
export function show() {
|
||||||
updateModal.show()
|
updateModal.show()
|
||||||
|
@ -28,7 +30,9 @@
|
||||||
$appStore.upgradableVersion &&
|
$appStore.upgradableVersion &&
|
||||||
$appStore.version &&
|
$appStore.version &&
|
||||||
$appStore.upgradableVersion !== $appStore.version
|
$appStore.upgradableVersion !== $appStore.version
|
||||||
$: revertAvailable = $appStore.revertableVersion != null
|
$: revertAvailable =
|
||||||
|
$appStore.revertableVersion != null ||
|
||||||
|
($admin.isDev && $appStore.version === "0.0.0")
|
||||||
|
|
||||||
const refreshAppPackage = async () => {
|
const refreshAppPackage = async () => {
|
||||||
try {
|
try {
|
||||||
|
@ -62,7 +66,9 @@
|
||||||
// Don't wait for the async refresh, since this causes modal flashing
|
// Don't wait for the async refresh, since this causes modal flashing
|
||||||
refreshAppPackage()
|
refreshAppPackage()
|
||||||
notifications.success(
|
notifications.success(
|
||||||
`App reverted successfully to version ${$appStore.revertableVersion}`
|
$appStore.revertableVersion
|
||||||
|
? `App reverted successfully to version ${$appStore.revertableVersion}`
|
||||||
|
: "App reverted successfully"
|
||||||
)
|
)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
notifications.error(`Error reverting app: ${err}`)
|
notifications.error(`Error reverting app: ${err}`)
|
||||||
|
@ -103,7 +109,13 @@
|
||||||
{#if revertAvailable}
|
{#if revertAvailable}
|
||||||
<Body size="S">
|
<Body size="S">
|
||||||
You can revert this app to version
|
You can revert this app to version
|
||||||
<b>{$appStore.revertableVersion}</b>
|
{#if $admin.isDev}
|
||||||
|
<RevertModalVersionSelect
|
||||||
|
revertableVersion={$appStore.revertableVersion}
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<b>{$appStore.revertableVersion}</b>
|
||||||
|
{/if}
|
||||||
if you're experiencing issues with the current version.
|
if you're experiencing issues with the current version.
|
||||||
</Body>
|
</Body>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
[]
|
|
@ -14,17 +14,11 @@
|
||||||
snippets,
|
snippets,
|
||||||
} from "stores/builder"
|
} from "stores/builder"
|
||||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||||
import {
|
import { Layout, Heading, Body, Icon, notifications } from "@budibase/bbui"
|
||||||
ProgressCircle,
|
|
||||||
Layout,
|
|
||||||
Heading,
|
|
||||||
Body,
|
|
||||||
Icon,
|
|
||||||
notifications,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import ErrorSVG from "@budibase/frontend-core/assets/error.svg?raw"
|
import ErrorSVG from "@budibase/frontend-core/assets/error.svg?raw"
|
||||||
import { findComponent, findComponentPath } from "helpers/components"
|
import { findComponent, findComponentPath } from "helpers/components"
|
||||||
import { isActive, goto } from "@roxi/routify"
|
import { isActive, goto } from "@roxi/routify"
|
||||||
|
import { ClientAppSkeleton } from "@budibase/frontend-core"
|
||||||
|
|
||||||
let iframe
|
let iframe
|
||||||
let layout
|
let layout
|
||||||
|
@ -254,8 +248,16 @@
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<div class="component-container">
|
<div class="component-container">
|
||||||
{#if loading}
|
{#if loading}
|
||||||
<div class="center">
|
<div
|
||||||
<ProgressCircle />
|
class={`loading ${$themeStore.theme}`}
|
||||||
|
class:tablet={$previewStore.previewDevice === "tablet"}
|
||||||
|
class:mobile={$previewStore.previewDevice === "mobile"}
|
||||||
|
>
|
||||||
|
<ClientAppSkeleton
|
||||||
|
sideNav={$navigationStore?.navigation === "Left"}
|
||||||
|
hideFooter
|
||||||
|
hideDevTools
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{:else if error}
|
{:else if error}
|
||||||
<div class="center error">
|
<div class="center error">
|
||||||
|
@ -272,8 +274,6 @@
|
||||||
bind:this={iframe}
|
bind:this={iframe}
|
||||||
src="/app/preview"
|
src="/app/preview"
|
||||||
class:hidden={loading || error}
|
class:hidden={loading || error}
|
||||||
class:tablet={$previewStore.previewDevice === "tablet"}
|
|
||||||
class:mobile={$previewStore.previewDevice === "mobile"}
|
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="add-component"
|
class="add-component"
|
||||||
|
@ -293,6 +293,25 @@
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.loading {
|
||||||
|
position: absolute;
|
||||||
|
container-type: inline-size;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading.tablet {
|
||||||
|
width: calc(1024px + 6px);
|
||||||
|
max-height: calc(768px + 6px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading.mobile {
|
||||||
|
width: calc(390px + 6px);
|
||||||
|
max-height: calc(844px + 6px);
|
||||||
|
}
|
||||||
|
|
||||||
.component-container {
|
.component-container {
|
||||||
grid-row-start: middle;
|
grid-row-start: middle;
|
||||||
grid-column-start: middle;
|
grid-column-start: middle;
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
<script>
|
<script>
|
||||||
|
import { onMount, onDestroy } from "svelte"
|
||||||
import { params, goto } from "@roxi/routify"
|
import { params, goto } from "@roxi/routify"
|
||||||
import { auth, sideBarCollapsed, enrichedApps } from "stores/portal"
|
import {
|
||||||
|
licensing,
|
||||||
|
auth,
|
||||||
|
sideBarCollapsed,
|
||||||
|
enrichedApps,
|
||||||
|
} from "stores/portal"
|
||||||
import AppRowContext from "components/start/AppRowContext.svelte"
|
import AppRowContext from "components/start/AppRowContext.svelte"
|
||||||
import FavouriteAppButton from "../FavouriteAppButton.svelte"
|
import FavouriteAppButton from "../FavouriteAppButton.svelte"
|
||||||
import {
|
import {
|
||||||
|
@ -14,12 +20,17 @@
|
||||||
import { sdk } from "@budibase/shared-core"
|
import { sdk } from "@budibase/shared-core"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
import ErrorSVG from "./ErrorSVG.svelte"
|
import ErrorSVG from "./ErrorSVG.svelte"
|
||||||
|
import { ClientAppSkeleton } from "@budibase/frontend-core"
|
||||||
|
|
||||||
$: app = $enrichedApps.find(app => app.appId === $params.appId)
|
$: app = $enrichedApps.find(app => app.appId === $params.appId)
|
||||||
$: iframeUrl = getIframeURL(app)
|
$: iframeUrl = getIframeURL(app)
|
||||||
$: isBuilder = sdk.users.isBuilder($auth.user, app?.devId)
|
$: isBuilder = sdk.users.isBuilder($auth.user, app?.devId)
|
||||||
|
|
||||||
|
let loading = true
|
||||||
|
|
||||||
const getIframeURL = app => {
|
const getIframeURL = app => {
|
||||||
|
loading = true
|
||||||
|
|
||||||
if (app.status === "published") {
|
if (app.status === "published") {
|
||||||
return `/app${app.url}`
|
return `/app${app.url}`
|
||||||
}
|
}
|
||||||
|
@ -37,6 +48,20 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
$: fetchScreens(app?.devId)
|
$: fetchScreens(app?.devId)
|
||||||
|
|
||||||
|
const receiveMessage = async message => {
|
||||||
|
if (message.data.type === "docLoaded") {
|
||||||
|
loading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
window.addEventListener("message", receiveMessage)
|
||||||
|
})
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
window.removeEventListener("message", receiveMessage)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
|
@ -108,7 +133,24 @@
|
||||||
</Body>
|
</Body>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<iframe src={iframeUrl} title={app.name} />
|
<div
|
||||||
|
class:hide={!loading || !app?.features?.skeletonLoader}
|
||||||
|
class="loading"
|
||||||
|
>
|
||||||
|
<div class={`loadingThemeWrapper ${app.theme}`}>
|
||||||
|
<ClientAppSkeleton
|
||||||
|
noAnimation
|
||||||
|
hideDevTools={app?.status === "published"}
|
||||||
|
sideNav={app?.navigation.navigation === "Left"}
|
||||||
|
hideFooter={$licensing.brandingEnabled}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<iframe
|
||||||
|
class:hide={loading && app?.features?.skeletonLoader}
|
||||||
|
src={iframeUrl}
|
||||||
|
title={app.name}
|
||||||
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -139,6 +181,23 @@
|
||||||
flex: 0 0 50px;
|
flex: 0 0 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
height: 100%;
|
||||||
|
border: 1px solid var(--spectrum-global-color-gray-300);
|
||||||
|
border-radius: var(--spacing-s);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.loadingThemeWrapper {
|
||||||
|
height: 100%;
|
||||||
|
container-type: inline-size;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hide {
|
||||||
|
visibility: hidden;
|
||||||
|
height: 0;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
iframe {
|
iframe {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
border-radius: var(--spacing-s);
|
border-radius: var(--spacing-s);
|
||||||
|
|
|
@ -10,7 +10,8 @@
|
||||||
"rowSelection": true,
|
"rowSelection": true,
|
||||||
"continueIfAction": true,
|
"continueIfAction": true,
|
||||||
"showNotificationAction": true,
|
"showNotificationAction": true,
|
||||||
"sidePanel": true
|
"sidePanel": true,
|
||||||
|
"skeletonLoader": true
|
||||||
},
|
},
|
||||||
"layout": {
|
"layout": {
|
||||||
"name": "Layout",
|
"name": "Layout",
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
import Component from "./Component.svelte"
|
import Component from "./Component.svelte"
|
||||||
import SDK from "sdk"
|
import SDK from "sdk"
|
||||||
import {
|
import {
|
||||||
|
featuresStore,
|
||||||
createContextStore,
|
createContextStore,
|
||||||
initialise,
|
initialise,
|
||||||
screenStore,
|
screenStore,
|
||||||
|
@ -38,7 +39,6 @@
|
||||||
import DevTools from "components/devtools/DevTools.svelte"
|
import DevTools from "components/devtools/DevTools.svelte"
|
||||||
import FreeFooter from "components/FreeFooter.svelte"
|
import FreeFooter from "components/FreeFooter.svelte"
|
||||||
import MaintenanceScreen from "components/MaintenanceScreen.svelte"
|
import MaintenanceScreen from "components/MaintenanceScreen.svelte"
|
||||||
import licensing from "../licensing"
|
|
||||||
import SnippetsProvider from "./context/SnippetsProvider.svelte"
|
import SnippetsProvider from "./context/SnippetsProvider.svelte"
|
||||||
|
|
||||||
// Provide contexts
|
// Provide contexts
|
||||||
|
@ -83,11 +83,18 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let fontsLoaded = false
|
||||||
|
|
||||||
// Load app config
|
// Load app config
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
|
document.fonts.ready.then(() => {
|
||||||
|
fontsLoaded = true
|
||||||
|
})
|
||||||
|
|
||||||
await initialise()
|
await initialise()
|
||||||
await authStore.actions.fetchUser()
|
await authStore.actions.fetchUser()
|
||||||
dataLoaded = true
|
dataLoaded = true
|
||||||
|
|
||||||
if (get(builderStore).inBuilder) {
|
if (get(builderStore).inBuilder) {
|
||||||
builderStore.actions.notifyLoaded()
|
builderStore.actions.notifyLoaded()
|
||||||
} else {
|
} else {
|
||||||
|
@ -96,6 +103,12 @@
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
$: {
|
||||||
|
if (dataLoaded && fontsLoaded) {
|
||||||
|
document.getElementById("clientAppSkeletonLoader")?.remove()
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
|
@ -106,148 +119,148 @@
|
||||||
{/if}
|
{/if}
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
{#if dataLoaded}
|
<div
|
||||||
<div
|
id="spectrum-root"
|
||||||
id="spectrum-root"
|
lang="en"
|
||||||
lang="en"
|
dir="ltr"
|
||||||
dir="ltr"
|
class="spectrum spectrum--medium {$themeStore.baseTheme} {$themeStore.theme}"
|
||||||
class="spectrum spectrum--medium {$themeStore.baseTheme} {$themeStore.theme}"
|
class:builder={$builderStore.inBuilder}
|
||||||
class:builder={$builderStore.inBuilder}
|
class:show={fontsLoaded && dataLoaded}
|
||||||
>
|
>
|
||||||
{#if $environmentStore.maintenance.length > 0}
|
{#if $environmentStore.maintenance.length > 0}
|
||||||
<MaintenanceScreen maintenanceList={$environmentStore.maintenance} />
|
<MaintenanceScreen maintenanceList={$environmentStore.maintenance} />
|
||||||
{:else}
|
{:else}
|
||||||
<DeviceBindingsProvider>
|
<DeviceBindingsProvider>
|
||||||
<UserBindingsProvider>
|
<UserBindingsProvider>
|
||||||
<StateBindingsProvider>
|
<StateBindingsProvider>
|
||||||
<RowSelectionProvider>
|
<RowSelectionProvider>
|
||||||
<QueryParamsProvider>
|
<QueryParamsProvider>
|
||||||
<SnippetsProvider>
|
<SnippetsProvider>
|
||||||
<!-- Settings bar can be rendered outside of device preview -->
|
<!-- Settings bar can be rendered outside of device preview -->
|
||||||
<!-- Key block needs to be outside the if statement or it breaks -->
|
<!-- Key block needs to be outside the if statement or it breaks -->
|
||||||
{#key $builderStore.selectedComponentId}
|
{#key $builderStore.selectedComponentId}
|
||||||
{#if $builderStore.inBuilder}
|
{#if $builderStore.inBuilder}
|
||||||
<SettingsBar />
|
<SettingsBar />
|
||||||
{/if}
|
{/if}
|
||||||
{/key}
|
{/key}
|
||||||
|
|
||||||
<!-- Clip boundary for selection indicators -->
|
<!-- Clip boundary for selection indicators -->
|
||||||
<div
|
<div
|
||||||
id="clip-root"
|
id="clip-root"
|
||||||
class:preview={$builderStore.inBuilder}
|
class:preview={$builderStore.inBuilder}
|
||||||
class:tablet-preview={$builderStore.previewDevice ===
|
class:tablet-preview={$builderStore.previewDevice ===
|
||||||
"tablet"}
|
"tablet"}
|
||||||
class:mobile-preview={$builderStore.previewDevice ===
|
class:mobile-preview={$builderStore.previewDevice ===
|
||||||
"mobile"}
|
"mobile"}
|
||||||
>
|
>
|
||||||
<!-- Actual app -->
|
<!-- Actual app -->
|
||||||
<div id="app-root">
|
<div id="app-root">
|
||||||
{#if showDevTools}
|
{#if showDevTools}
|
||||||
<DevToolsHeader />
|
<DevToolsHeader />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div id="app-body">
|
||||||
|
{#if permissionError}
|
||||||
|
<div class="error">
|
||||||
|
<Layout justifyItems="center" gap="S">
|
||||||
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||||
|
{@html ErrorSVG}
|
||||||
|
<Heading size="L">
|
||||||
|
You don't have permission to use this app
|
||||||
|
</Heading>
|
||||||
|
<Body size="S">
|
||||||
|
Ask your administrator to grant you access
|
||||||
|
</Body>
|
||||||
|
</Layout>
|
||||||
|
</div>
|
||||||
|
{:else if !$screenStore.activeLayout}
|
||||||
|
<div class="error">
|
||||||
|
<Layout justifyItems="center" gap="S">
|
||||||
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||||
|
{@html ErrorSVG}
|
||||||
|
<Heading size="L">
|
||||||
|
Something went wrong rendering your app
|
||||||
|
</Heading>
|
||||||
|
<Body size="S">
|
||||||
|
Get in touch with support if this issue persists
|
||||||
|
</Body>
|
||||||
|
</Layout>
|
||||||
|
</div>
|
||||||
|
{:else if embedNoScreens}
|
||||||
|
<div class="error">
|
||||||
|
<Layout justifyItems="center" gap="S">
|
||||||
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||||
|
{@html ErrorSVG}
|
||||||
|
<Heading size="L">
|
||||||
|
This Budibase app is not publicly accessible
|
||||||
|
</Heading>
|
||||||
|
</Layout>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<CustomThemeWrapper>
|
||||||
|
{#key $screenStore.activeLayout._id}
|
||||||
|
<Component
|
||||||
|
isLayout
|
||||||
|
instance={$screenStore.activeLayout.props}
|
||||||
|
/>
|
||||||
|
{/key}
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Flatpickr needs to be inside the theme wrapper.
|
||||||
|
It also needs its own container because otherwise it hijacks
|
||||||
|
key events on the whole page. It is painful to work with.
|
||||||
|
-->
|
||||||
|
<div id="flatpickr-root" />
|
||||||
|
|
||||||
|
<!-- Modal container to ensure they sit on top -->
|
||||||
|
<div class="modal-container" />
|
||||||
|
|
||||||
|
<!-- Layers on top of app -->
|
||||||
|
<NotificationDisplay />
|
||||||
|
<ConfirmationDisplay />
|
||||||
|
<PeekScreenDisplay />
|
||||||
|
</CustomThemeWrapper>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div id="app-body">
|
{#if showDevTools}
|
||||||
{#if permissionError}
|
<DevTools />
|
||||||
<div class="error">
|
|
||||||
<Layout justifyItems="center" gap="S">
|
|
||||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
||||||
{@html ErrorSVG}
|
|
||||||
<Heading size="L">
|
|
||||||
You don't have permission to use this app
|
|
||||||
</Heading>
|
|
||||||
<Body size="S">
|
|
||||||
Ask your administrator to grant you access
|
|
||||||
</Body>
|
|
||||||
</Layout>
|
|
||||||
</div>
|
|
||||||
{:else if !$screenStore.activeLayout}
|
|
||||||
<div class="error">
|
|
||||||
<Layout justifyItems="center" gap="S">
|
|
||||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
||||||
{@html ErrorSVG}
|
|
||||||
<Heading size="L">
|
|
||||||
Something went wrong rendering your app
|
|
||||||
</Heading>
|
|
||||||
<Body size="S">
|
|
||||||
Get in touch with support if this issue persists
|
|
||||||
</Body>
|
|
||||||
</Layout>
|
|
||||||
</div>
|
|
||||||
{:else if embedNoScreens}
|
|
||||||
<div class="error">
|
|
||||||
<Layout justifyItems="center" gap="S">
|
|
||||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
||||||
{@html ErrorSVG}
|
|
||||||
<Heading size="L">
|
|
||||||
This Budibase app is not publicly accessible
|
|
||||||
</Heading>
|
|
||||||
</Layout>
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<CustomThemeWrapper>
|
|
||||||
{#key $screenStore.activeLayout._id}
|
|
||||||
<Component
|
|
||||||
isLayout
|
|
||||||
instance={$screenStore.activeLayout.props}
|
|
||||||
/>
|
|
||||||
{/key}
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Flatpickr needs to be inside the theme wrapper.
|
|
||||||
It also needs its own container because otherwise it hijacks
|
|
||||||
key events on the whole page. It is painful to work with.
|
|
||||||
-->
|
|
||||||
<div id="flatpickr-root" />
|
|
||||||
|
|
||||||
<!-- Modal container to ensure they sit on top -->
|
|
||||||
<div class="modal-container" />
|
|
||||||
|
|
||||||
<!-- Layers on top of app -->
|
|
||||||
<NotificationDisplay />
|
|
||||||
<ConfirmationDisplay />
|
|
||||||
<PeekScreenDisplay />
|
|
||||||
</CustomThemeWrapper>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if showDevTools}
|
|
||||||
<DevTools />
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if !$builderStore.inBuilder && licensing.logoEnabled()}
|
|
||||||
<FreeFooter />
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Preview and dev tools utilities -->
|
{#if !$builderStore.inBuilder && $featuresStore.logoEnabled}
|
||||||
{#if $appStore.isDevApp}
|
<FreeFooter />
|
||||||
<SelectionIndicator />
|
|
||||||
{/if}
|
|
||||||
{#if $builderStore.inBuilder || $devToolsStore.allowSelection}
|
|
||||||
<HoverIndicator />
|
|
||||||
{/if}
|
|
||||||
{#if $builderStore.inBuilder}
|
|
||||||
<DNDHandler />
|
|
||||||
<GridDNDHandler />
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</SnippetsProvider>
|
|
||||||
</QueryParamsProvider>
|
<!-- Preview and dev tools utilities -->
|
||||||
</RowSelectionProvider>
|
{#if $appStore.isDevApp}
|
||||||
</StateBindingsProvider>
|
<SelectionIndicator />
|
||||||
</UserBindingsProvider>
|
{/if}
|
||||||
</DeviceBindingsProvider>
|
{#if $builderStore.inBuilder || $devToolsStore.allowSelection}
|
||||||
{/if}
|
<HoverIndicator />
|
||||||
</div>
|
{/if}
|
||||||
<KeyboardManager />
|
{#if $builderStore.inBuilder}
|
||||||
{/if}
|
<DNDHandler />
|
||||||
|
<GridDNDHandler />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</SnippetsProvider>
|
||||||
|
</QueryParamsProvider>
|
||||||
|
</RowSelectionProvider>
|
||||||
|
</StateBindingsProvider>
|
||||||
|
</UserBindingsProvider>
|
||||||
|
</DeviceBindingsProvider>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<KeyboardManager />
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
#spectrum-root {
|
#spectrum-root {
|
||||||
|
height: 0;
|
||||||
|
visibility: hidden;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
@ -268,6 +281,11 @@
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#spectrum-root.show {
|
||||||
|
height: 100%;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
#app-root {
|
#app-root {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.free-footer {
|
.free-footer {
|
||||||
|
min-height: 51px;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
padding: 16px 20px;
|
padding: 16px 20px;
|
||||||
border-top: 1px solid var(--spectrum-global-color-gray-300);
|
border-top: 1px solid var(--spectrum-global-color-gray-300);
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
import { isFreePlan } from "./utils.js"
|
|
||||||
|
|
||||||
export const logoEnabled = () => {
|
|
||||||
return isFreePlan()
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
import * as features from "./features"
|
|
||||||
|
|
||||||
const licensing = {
|
|
||||||
...features,
|
|
||||||
}
|
|
||||||
|
|
||||||
export default licensing
|
|
|
@ -1,32 +0,0 @@
|
||||||
import { authStore } from "../stores/auth.js"
|
|
||||||
import { appStore } from "../stores/app.js"
|
|
||||||
import { get } from "svelte/store"
|
|
||||||
import { Constants } from "@budibase/frontend-core"
|
|
||||||
|
|
||||||
const getUserLicense = () => {
|
|
||||||
const user = get(authStore)
|
|
||||||
if (user) {
|
|
||||||
return user.license
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getAppLicenseType = () => {
|
|
||||||
const appDef = get(appStore)
|
|
||||||
if (appDef?.licenseType) {
|
|
||||||
return appDef.licenseType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const isFreePlan = () => {
|
|
||||||
let licenseType = getAppLicenseType()
|
|
||||||
if (!licenseType) {
|
|
||||||
const license = getUserLicense()
|
|
||||||
licenseType = license?.plan?.type
|
|
||||||
}
|
|
||||||
if (licenseType) {
|
|
||||||
return licenseType === Constants.PlanType.FREE
|
|
||||||
} else {
|
|
||||||
// safety net - no license means free plan
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { derived } from "svelte/store"
|
||||||
|
import { appStore } from "./app"
|
||||||
|
import { authStore } from "./auth"
|
||||||
|
import { Constants } from "@budibase/frontend-core"
|
||||||
|
|
||||||
|
const createFeaturesStore = () => {
|
||||||
|
return derived([authStore, appStore], ([$authStore, $appStore]) => {
|
||||||
|
const getUserLicense = () => {
|
||||||
|
const user = $authStore
|
||||||
|
if (user) {
|
||||||
|
return user.license
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getAppLicenseType = () => {
|
||||||
|
const appDef = $appStore
|
||||||
|
if (appDef?.licenseType) {
|
||||||
|
return appDef.licenseType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isFreePlan = () => {
|
||||||
|
let licenseType = getAppLicenseType()
|
||||||
|
if (!licenseType) {
|
||||||
|
const license = getUserLicense()
|
||||||
|
licenseType = license?.plan?.type
|
||||||
|
}
|
||||||
|
if (licenseType) {
|
||||||
|
return licenseType === Constants.PlanType.FREE
|
||||||
|
} else {
|
||||||
|
// safety net - no license means free plan
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
logoEnabled: isFreePlan(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const featuresStore = createFeaturesStore()
|
|
@ -31,6 +31,7 @@ export { hoverStore } from "./hover"
|
||||||
|
|
||||||
// Context stores are layered and duplicated, so it is not a singleton
|
// Context stores are layered and duplicated, so it is not a singleton
|
||||||
export { createContextStore } from "./context"
|
export { createContextStore } from "./context"
|
||||||
|
export { featuresStore } from "./features"
|
||||||
|
|
||||||
// Initialises an app by loading screens and routes
|
// Initialises an app by loading screens and routes
|
||||||
export { initialise } from "./initialise"
|
export { initialise } from "./initialise"
|
||||||
|
|
|
@ -197,4 +197,13 @@ export const buildAppEndpoints = API => ({
|
||||||
url: `/api/applications/${appId}/sample`,
|
url: `/api/applications/${appId}/sample`,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setRevertableVersion: async (appId, revertableVersion) => {
|
||||||
|
return await API.post({
|
||||||
|
url: `/api/applications/${appId}/setRevertableVersion`,
|
||||||
|
body: {
|
||||||
|
revertableVersion,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,244 @@
|
||||||
|
<script>
|
||||||
|
export let sideNav = false
|
||||||
|
export let hideDevTools = false
|
||||||
|
export let hideFooter = false
|
||||||
|
export let noAnimation = false
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class:sideNav id="clientAppSkeletonLoader" class="skeleton">
|
||||||
|
<div class="animation" class:noAnimation />
|
||||||
|
|
||||||
|
{#if !hideDevTools}
|
||||||
|
<div class="devTools" />
|
||||||
|
{/if}
|
||||||
|
<div class="main">
|
||||||
|
<div class="nav" />
|
||||||
|
<div class="body">
|
||||||
|
<div class="bodyVerticalPadding" />
|
||||||
|
<div class="bodyHorizontal">
|
||||||
|
<div class="bodyHorizontalPadding" />
|
||||||
|
<svg
|
||||||
|
class="svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
width="240"
|
||||||
|
height="256"
|
||||||
|
>
|
||||||
|
<mask id="mask">
|
||||||
|
<rect x="0" y="0" width="240" height="256" fill="white" />
|
||||||
|
<rect x="0" y="0" width="240" height="32" rx="6" fill="black" />
|
||||||
|
<rect x="0" y="56" width="240" height="32" rx="6" fill="black" />
|
||||||
|
<rect x="0" y="112" width="240" height="32" rx="6" fill="black" />
|
||||||
|
<rect x="0" y="168" width="240" height="32" rx="6" fill="black" />
|
||||||
|
<rect x="71" y="224" width="98" height="32" rx="6" fill="black" />
|
||||||
|
</mask>
|
||||||
|
|
||||||
|
<rect
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
width="240"
|
||||||
|
height="256"
|
||||||
|
fill="black"
|
||||||
|
mask="url(#mask)"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<div class="bodyHorizontalPadding" />
|
||||||
|
</div>
|
||||||
|
<div class="bodyVerticalPadding" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{#if !hideFooter}
|
||||||
|
<div class="footer" />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.skeleton {
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: var(--spectrum-global-color-gray-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.animation {
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
background: linear-gradient(
|
||||||
|
to right,
|
||||||
|
transparent 0%,
|
||||||
|
var(--spectrum-global-color-gray-300) 20%,
|
||||||
|
transparent 40%,
|
||||||
|
transparent 100%
|
||||||
|
);
|
||||||
|
animation-duration: 1.3s;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
animation-name: shimmer;
|
||||||
|
animation-timing-function: linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.noAnimation {
|
||||||
|
animation-name: none;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.devTools {
|
||||||
|
display: flex;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: black;
|
||||||
|
height: 60px;
|
||||||
|
padding: 1px 24px 1px 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 1;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
color: white;
|
||||||
|
mix-blend-mode: multiply;
|
||||||
|
background: rgb(0 0 0);
|
||||||
|
font-size: 30px;
|
||||||
|
font-family: Source Sans Pro;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 720px) {
|
||||||
|
#clientAppSkeletonLoader .main {
|
||||||
|
flex-direction: column;
|
||||||
|
width: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@container (max-width: 720px) {
|
||||||
|
#clientAppSkeletonLoader .main {
|
||||||
|
flex-direction: column;
|
||||||
|
width: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sideNav .main {
|
||||||
|
flex-direction: row;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 141px;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 720px) {
|
||||||
|
#clientAppSkeletonLoader .nav {
|
||||||
|
height: 61px;
|
||||||
|
width: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@container (max-width: 720px) {
|
||||||
|
#clientAppSkeletonLoader .nav {
|
||||||
|
height: 61px;
|
||||||
|
width: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sideNav .nav {
|
||||||
|
height: 100%;
|
||||||
|
width: 251px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
z-index: 2;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 720px) {
|
||||||
|
#clientAppSkeletonLoader .body {
|
||||||
|
width: initial;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@container (max-width: 720px) {
|
||||||
|
#clientAppSkeletonLoader .body {
|
||||||
|
width: initial;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sideNav .body {
|
||||||
|
width: 100%;
|
||||||
|
height: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body :global(svg > rect) {
|
||||||
|
fill: var(--spectrum-alias-background-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.body :global(svg) {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bodyHorizontal {
|
||||||
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bodyHorizontalPadding {
|
||||||
|
height: 100%;
|
||||||
|
flex-grow: 1;
|
||||||
|
background-color: var(--spectrum-alias-background-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bodyVerticalPadding {
|
||||||
|
width: 100%;
|
||||||
|
flex-grow: 1;
|
||||||
|
background-color: var(--spectrum-alias-background-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
flex-shrink: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
z-index: 1;
|
||||||
|
height: 52px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 720px) {
|
||||||
|
#clientAppSkeletonLoader .footer {
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@container (max-width: 720px) {
|
||||||
|
#clientAppSkeletonLoader .footer {
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sideNav .footer {
|
||||||
|
border-top: 3px solid var(--spectrum-alias-background-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shimmer {
|
||||||
|
0% {
|
||||||
|
left: -170%;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
left: 170%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -5,3 +5,4 @@ export { default as UserAvatar } from "./UserAvatar.svelte"
|
||||||
export { default as UserAvatars } from "./UserAvatars.svelte"
|
export { default as UserAvatars } from "./UserAvatars.svelte"
|
||||||
export { default as Updating } from "./Updating.svelte"
|
export { default as Updating } from "./Updating.svelte"
|
||||||
export { Grid } from "./grid"
|
export { Grid } from "./grid"
|
||||||
|
export { default as ClientAppSkeleton } from "./ClientAppSkeleton.svelte"
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
*/
|
*/
|
||||||
export { OperatorOptions, SqlNumberTypeRangeMap } from "@budibase/shared-core"
|
export { OperatorOptions, SqlNumberTypeRangeMap } from "@budibase/shared-core"
|
||||||
export { Feature as Features } from "@budibase/types"
|
export { Feature as Features } from "@budibase/types"
|
||||||
|
import { BpmCorrelationKey } from "@budibase/shared-core"
|
||||||
|
|
||||||
// Cookie names
|
// Cookie names
|
||||||
export const Cookies = {
|
export const Cookies = {
|
||||||
|
@ -10,6 +11,7 @@ export const Cookies = {
|
||||||
CurrentApp: "budibase:currentapp",
|
CurrentApp: "budibase:currentapp",
|
||||||
ReturnUrl: "budibase:returnurl",
|
ReturnUrl: "budibase:returnurl",
|
||||||
AccountReturnUrl: "budibase:account:returnurl",
|
AccountReturnUrl: "budibase:account:returnurl",
|
||||||
|
OnboardingProcessCorrelationKey: BpmCorrelationKey.ONBOARDING,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Table names
|
// Table names
|
||||||
|
|
|
@ -18,4 +18,3 @@
|
||||||
--drop-shadow: rgba(0, 0, 0, 0.25) !important;
|
--drop-shadow: rgba(0, 0, 0, 0.25) !important;
|
||||||
--spectrum-global-color-blue-100: rgba(35, 40, 50) !important;
|
--spectrum-global-color-blue-100: rgba(35, 40, 50) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,142 +0,0 @@
|
||||||
{
|
|
||||||
// Use IntelliSense to learn about possible attributes.
|
|
||||||
// Hover to view descriptions of existing attributes.
|
|
||||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
|
||||||
"version": "0.2.0",
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"name": "Start Server",
|
|
||||||
"type": "node",
|
|
||||||
"request": "launch",
|
|
||||||
"runtimeExecutable": "node",
|
|
||||||
"runtimeArgs": ["--nolazy", "-r", "ts-node/register/transpile-only"],
|
|
||||||
"args": ["src/index.ts"],
|
|
||||||
"cwd": "${workspaceRoot}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "node",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "Jest - All",
|
|
||||||
"program": "${workspaceFolder}/node_modules/.bin/jest",
|
|
||||||
"args": [],
|
|
||||||
"console": "integratedTerminal",
|
|
||||||
"internalConsoleOptions": "neverOpen",
|
|
||||||
"disableOptimisticBPs": true,
|
|
||||||
"windows": {
|
|
||||||
"program": "${workspaceFolder}/node_modules/jest-cli/bin/jest",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "node",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "Jest - Users",
|
|
||||||
"program": "${workspaceFolder}/node_modules/.bin/jest",
|
|
||||||
"args": ["user.spec", "--runInBand"],
|
|
||||||
"console": "integratedTerminal",
|
|
||||||
"internalConsoleOptions": "neverOpen",
|
|
||||||
"disableOptimisticBPs": true,
|
|
||||||
"windows": {
|
|
||||||
"program": "${workspaceFolder}/node_modules/jest-cli/bin/jest",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "node",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "Jest - Instances",
|
|
||||||
"program": "${workspaceFolder}/node_modules/.bin/jest",
|
|
||||||
"args": ["instance.spec", "--runInBand"],
|
|
||||||
"console": "integratedTerminal",
|
|
||||||
"internalConsoleOptions": "neverOpen",
|
|
||||||
"disableOptimisticBPs": true,
|
|
||||||
"windows": {
|
|
||||||
"program": "${workspaceFolder}/node_modules/jest-cli/bin/jest",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "node",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "Jest - Roles",
|
|
||||||
"program": "${workspaceFolder}/node_modules/.bin/jest",
|
|
||||||
"args": ["role.spec", "--runInBand"],
|
|
||||||
"console": "integratedTerminal",
|
|
||||||
"internalConsoleOptions": "neverOpen",
|
|
||||||
"disableOptimisticBPs": true,
|
|
||||||
"windows": {
|
|
||||||
"program": "${workspaceFolder}/node_modules/jest-cli/bin/jest",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "node",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "Jest - Records",
|
|
||||||
"program": "${workspaceFolder}/node_modules/.bin/jest",
|
|
||||||
"args": ["record.spec", "--runInBand"],
|
|
||||||
"console": "integratedTerminal",
|
|
||||||
"internalConsoleOptions": "neverOpen",
|
|
||||||
"disableOptimisticBPs": true,
|
|
||||||
"windows": {
|
|
||||||
"program": "${workspaceFolder}/node_modules/jest-cli/bin/jest",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "node",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "Jest - Models",
|
|
||||||
"program": "${workspaceFolder}/node_modules/.bin/jest",
|
|
||||||
"args": ["table.spec", "--runInBand"],
|
|
||||||
"console": "integratedTerminal",
|
|
||||||
"internalConsoleOptions": "neverOpen",
|
|
||||||
"disableOptimisticBPs": true,
|
|
||||||
"windows": {
|
|
||||||
"program": "${workspaceFolder}/node_modules/jest-cli/bin/jest",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "node",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "Jest - Views",
|
|
||||||
"program": "${workspaceFolder}/node_modules/.bin/jest",
|
|
||||||
"args": ["view.spec", "--runInBand"],
|
|
||||||
"console": "integratedTerminal",
|
|
||||||
"internalConsoleOptions": "neverOpen",
|
|
||||||
"disableOptimisticBPs": true,
|
|
||||||
"windows": {
|
|
||||||
"program": "${workspaceFolder}/node_modules/jest-cli/bin/jest",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "node",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "Jest - Applications",
|
|
||||||
"program": "${workspaceFolder}/node_modules/.bin/jest",
|
|
||||||
"args": ["application.spec", "--runInBand"],
|
|
||||||
"console": "integratedTerminal",
|
|
||||||
"internalConsoleOptions": "neverOpen",
|
|
||||||
"disableOptimisticBPs": true,
|
|
||||||
"windows": {
|
|
||||||
"program": "${workspaceFolder}/node_modules/jest-cli/bin/jest",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "node",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "Jest Builder",
|
|
||||||
"program": "${workspaceFolder}/node_modules/.bin/jest",
|
|
||||||
"args": ["builder", "--runInBand"],
|
|
||||||
"console": "integratedTerminal",
|
|
||||||
"internalConsoleOptions": "neverOpen",
|
|
||||||
"disableOptimisticBPs": true,
|
|
||||||
"windows": {
|
|
||||||
"program": "${workspaceFolder}/node_modules/jest-cli/bin/jest",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "node",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "Initialise Budibase",
|
|
||||||
"program": "yarn",
|
|
||||||
"args": ["run", "initialise"],
|
|
||||||
"console": "externalTerminal"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
const { join } = require("path")
|
|
||||||
require("dotenv").config({
|
|
||||||
path: join(__dirname, "..", "..", "hosting", ".env"),
|
|
||||||
})
|
|
||||||
|
|
||||||
const jestTestcontainersConfigGenerator = require("../../jestTestcontainersConfigGenerator")
|
|
||||||
|
|
||||||
module.exports = jestTestcontainersConfigGenerator()
|
|
|
@ -4,7 +4,6 @@ import * as fs from "fs"
|
||||||
import { join } from "path"
|
import { join } from "path"
|
||||||
|
|
||||||
const baseConfig: Config.InitialProjectOptions = {
|
const baseConfig: Config.InitialProjectOptions = {
|
||||||
preset: "@trendyol/jest-testcontainers",
|
|
||||||
setupFiles: ["./src/tests/jestEnv.ts"],
|
setupFiles: ["./src/tests/jestEnv.ts"],
|
||||||
moduleFileExtensions: [
|
moduleFileExtensions: [
|
||||||
"js",
|
"js",
|
||||||
|
@ -18,6 +17,7 @@ const baseConfig: Config.InitialProjectOptions = {
|
||||||
"svelte",
|
"svelte",
|
||||||
],
|
],
|
||||||
setupFilesAfterEnv: ["./src/tests/jestSetup.ts"],
|
setupFilesAfterEnv: ["./src/tests/jestSetup.ts"],
|
||||||
|
globalSetup: "./../../globalSetup.ts",
|
||||||
transform: {
|
transform: {
|
||||||
"^.+\\.ts?$": "@swc/jest",
|
"^.+\\.ts?$": "@swc/jest",
|
||||||
"^.+\\.js?$": "@swc/jest",
|
"^.+\\.js?$": "@swc/jest",
|
||||||
|
|
|
@ -53,6 +53,7 @@
|
||||||
"@budibase/pro": "0.0.0",
|
"@budibase/pro": "0.0.0",
|
||||||
"@budibase/shared-core": "0.0.0",
|
"@budibase/shared-core": "0.0.0",
|
||||||
"@budibase/string-templates": "0.0.0",
|
"@budibase/string-templates": "0.0.0",
|
||||||
|
"@budibase/frontend-core": "0.0.0",
|
||||||
"@budibase/types": "0.0.0",
|
"@budibase/types": "0.0.0",
|
||||||
"@bull-board/api": "5.10.2",
|
"@bull-board/api": "5.10.2",
|
||||||
"@bull-board/koa": "5.10.2",
|
"@bull-board/koa": "5.10.2",
|
||||||
|
@ -121,7 +122,6 @@
|
||||||
"@babel/preset-env": "7.16.11",
|
"@babel/preset-env": "7.16.11",
|
||||||
"@swc/core": "1.3.71",
|
"@swc/core": "1.3.71",
|
||||||
"@swc/jest": "0.2.27",
|
"@swc/jest": "0.2.27",
|
||||||
"@trendyol/jest-testcontainers": "2.1.1",
|
|
||||||
"@types/global-agent": "2.1.1",
|
"@types/global-agent": "2.1.1",
|
||||||
"@types/google-spreadsheet": "3.1.5",
|
"@types/google-spreadsheet": "3.1.5",
|
||||||
"@types/jest": "29.5.5",
|
"@types/jest": "29.5.5",
|
||||||
|
|
|
@ -306,6 +306,7 @@ async function performAppCreate(ctx: UserCtx<CreateAppRequest, App>) {
|
||||||
features: {
|
features: {
|
||||||
componentValidation: true,
|
componentValidation: true,
|
||||||
disableUserMetadata: true,
|
disableUserMetadata: true,
|
||||||
|
skeletonLoader: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -486,10 +487,11 @@ export async function updateClient(ctx: UserCtx) {
|
||||||
const application = await db.get<App>(DocumentType.APP_METADATA)
|
const application = await db.get<App>(DocumentType.APP_METADATA)
|
||||||
const currentVersion = application.version
|
const currentVersion = application.version
|
||||||
|
|
||||||
|
let manifest
|
||||||
// Update client library and manifest
|
// Update client library and manifest
|
||||||
if (!env.isTest()) {
|
if (!env.isTest()) {
|
||||||
await backupClientLibrary(ctx.params.appId)
|
await backupClientLibrary(ctx.params.appId)
|
||||||
await updateClientLibrary(ctx.params.appId)
|
manifest = await updateClientLibrary(ctx.params.appId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update versions in app package
|
// Update versions in app package
|
||||||
|
@ -497,6 +499,10 @@ export async function updateClient(ctx: UserCtx) {
|
||||||
const appPackageUpdates = {
|
const appPackageUpdates = {
|
||||||
version: updatedToVersion,
|
version: updatedToVersion,
|
||||||
revertableVersion: currentVersion,
|
revertableVersion: currentVersion,
|
||||||
|
features: {
|
||||||
|
...(application.features ?? {}),
|
||||||
|
skeletonLoader: manifest?.features?.skeletonLoader ?? false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
const app = await updateAppPackage(appPackageUpdates, ctx.params.appId)
|
const app = await updateAppPackage(appPackageUpdates, ctx.params.appId)
|
||||||
await events.app.versionUpdated(app, currentVersion, updatedToVersion)
|
await events.app.versionUpdated(app, currentVersion, updatedToVersion)
|
||||||
|
@ -512,9 +518,10 @@ export async function revertClient(ctx: UserCtx) {
|
||||||
ctx.throw(400, "There is no version to revert to")
|
ctx.throw(400, "There is no version to revert to")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let manifest
|
||||||
// Update client library and manifest
|
// Update client library and manifest
|
||||||
if (!env.isTest()) {
|
if (!env.isTest()) {
|
||||||
await revertClientLibrary(ctx.params.appId)
|
manifest = await revertClientLibrary(ctx.params.appId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update versions in app package
|
// Update versions in app package
|
||||||
|
@ -523,6 +530,10 @@ export async function revertClient(ctx: UserCtx) {
|
||||||
const appPackageUpdates = {
|
const appPackageUpdates = {
|
||||||
version: revertedToVersion,
|
version: revertedToVersion,
|
||||||
revertableVersion: undefined,
|
revertableVersion: undefined,
|
||||||
|
features: {
|
||||||
|
...(application.features ?? {}),
|
||||||
|
skeletonLoader: manifest?.features?.skeletonLoader ?? false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
const app = await updateAppPackage(appPackageUpdates, ctx.params.appId)
|
const app = await updateAppPackage(appPackageUpdates, ctx.params.appId)
|
||||||
await events.app.versionReverted(app, currentVersion, revertedToVersion)
|
await events.app.versionReverted(app, currentVersion, revertedToVersion)
|
||||||
|
@ -729,6 +740,21 @@ export async function updateAppPackage(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function setRevertableVersion(
|
||||||
|
ctx: UserCtx<{ revertableVersion: string }, App>
|
||||||
|
) {
|
||||||
|
if (!env.isDev()) {
|
||||||
|
ctx.status = 403
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const db = context.getAppDB()
|
||||||
|
const app = await db.get<App>(DocumentType.APP_METADATA)
|
||||||
|
app.revertableVersion = ctx.request.body.revertableVersion
|
||||||
|
await db.put(app)
|
||||||
|
|
||||||
|
ctx.status = 200
|
||||||
|
}
|
||||||
|
|
||||||
async function migrateAppNavigation() {
|
async function migrateAppNavigation() {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
const existing: App = await db.get(DocumentType.APP_METADATA)
|
const existing: App = await db.get(DocumentType.APP_METADATA)
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
import { InvalidFileExtensions } from "@budibase/shared-core"
|
import { InvalidFileExtensions } from "@budibase/shared-core"
|
||||||
|
|
||||||
import AppComponent from "./templates/BudibaseApp.svelte"
|
import AppComponent from "./templates/BudibaseApp.svelte"
|
||||||
|
|
||||||
import { join } from "../../../utilities/centralPath"
|
import { join } from "../../../utilities/centralPath"
|
||||||
import * as uuid from "uuid"
|
import * as uuid from "uuid"
|
||||||
import { ObjectStoreBuckets } from "../../../constants"
|
import { ObjectStoreBuckets, devClientVersion } from "../../../constants"
|
||||||
import { processString } from "@budibase/string-templates"
|
import { processString } from "@budibase/string-templates"
|
||||||
import {
|
import {
|
||||||
loadHandlebarsFile,
|
loadHandlebarsFile,
|
||||||
|
@ -24,13 +22,20 @@ import AWS from "aws-sdk"
|
||||||
import fs from "fs"
|
import fs from "fs"
|
||||||
import sdk from "../../../sdk"
|
import sdk from "../../../sdk"
|
||||||
import * as pro from "@budibase/pro"
|
import * as pro from "@budibase/pro"
|
||||||
import { App, Ctx, ProcessAttachmentResponse } from "@budibase/types"
|
import {
|
||||||
|
UserCtx,
|
||||||
|
App,
|
||||||
|
Ctx,
|
||||||
|
ProcessAttachmentResponse,
|
||||||
|
Feature,
|
||||||
|
} from "@budibase/types"
|
||||||
import {
|
import {
|
||||||
getAppMigrationVersion,
|
getAppMigrationVersion,
|
||||||
getLatestMigrationId,
|
getLatestMigrationId,
|
||||||
} from "../../../appMigrations"
|
} from "../../../appMigrations"
|
||||||
|
|
||||||
import send from "koa-send"
|
import send from "koa-send"
|
||||||
|
import { getThemeVariables } from "../../../constants/themes"
|
||||||
|
|
||||||
export const toggleBetaUiFeature = async function (ctx: Ctx) {
|
export const toggleBetaUiFeature = async function (ctx: Ctx) {
|
||||||
const cookieName = `beta:${ctx.params.feature}`
|
const cookieName = `beta:${ctx.params.feature}`
|
||||||
|
@ -146,7 +151,7 @@ const requiresMigration = async (ctx: Ctx) => {
|
||||||
return requiresMigrations
|
return requiresMigrations
|
||||||
}
|
}
|
||||||
|
|
||||||
export const serveApp = async function (ctx: Ctx) {
|
export const serveApp = async function (ctx: UserCtx) {
|
||||||
const needMigrations = await requiresMigration(ctx)
|
const needMigrations = await requiresMigration(ctx)
|
||||||
|
|
||||||
const bbHeaderEmbed =
|
const bbHeaderEmbed =
|
||||||
|
@ -165,12 +170,23 @@ export const serveApp = async function (ctx: Ctx) {
|
||||||
try {
|
try {
|
||||||
db = context.getAppDB({ skip_setup: true })
|
db = context.getAppDB({ skip_setup: true })
|
||||||
const appInfo = await db.get<any>(DocumentType.APP_METADATA)
|
const appInfo = await db.get<any>(DocumentType.APP_METADATA)
|
||||||
|
|
||||||
let appId = context.getAppId()
|
let appId = context.getAppId()
|
||||||
|
const hideDevTools = !!ctx.params.appUrl
|
||||||
|
const sideNav = appInfo.navigation.navigation === "Left"
|
||||||
|
const hideFooter =
|
||||||
|
ctx?.user?.license?.features?.includes(Feature.BRANDING) || false
|
||||||
|
const themeVariables = getThemeVariables(appInfo?.theme)
|
||||||
|
|
||||||
if (!env.isJest()) {
|
if (!env.isJest()) {
|
||||||
const plugins = objectStore.enrichPluginURLs(appInfo.usedPlugins)
|
const plugins = objectStore.enrichPluginURLs(appInfo.usedPlugins)
|
||||||
|
|
||||||
const { head, html, css } = AppComponent.render({
|
const { head, html, css } = AppComponent.render({
|
||||||
title: branding?.platformTitle || `${appInfo.name}`,
|
title: branding?.platformTitle || `${appInfo.name}`,
|
||||||
|
showSkeletonLoader: appInfo.features?.skeletonLoader ?? false,
|
||||||
|
hideDevTools,
|
||||||
|
sideNav,
|
||||||
|
hideFooter,
|
||||||
metaImage:
|
metaImage:
|
||||||
branding?.metaImageUrl ||
|
branding?.metaImageUrl ||
|
||||||
"https://res.cloudinary.com/daog6scxm/image/upload/v1698759482/meta-images/plain-branded-meta-image-coral_ocxmgu.png",
|
"https://res.cloudinary.com/daog6scxm/image/upload/v1698759482/meta-images/plain-branded-meta-image-coral_ocxmgu.png",
|
||||||
|
@ -195,7 +211,7 @@ export const serveApp = async function (ctx: Ctx) {
|
||||||
ctx.body = await processString(appHbs, {
|
ctx.body = await processString(appHbs, {
|
||||||
head,
|
head,
|
||||||
body: html,
|
body: html,
|
||||||
style: css.code,
|
css: `:root{${themeVariables}} ${css.code}`,
|
||||||
appId,
|
appId,
|
||||||
embedded: bbHeaderEmbed,
|
embedded: bbHeaderEmbed,
|
||||||
})
|
})
|
||||||
|
@ -247,18 +263,20 @@ export const serveBuilderPreview = async function (ctx: Ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const serveClientLibrary = async function (ctx: Ctx) {
|
export const serveClientLibrary = async function (ctx: Ctx) {
|
||||||
|
const version = ctx.request.query.version
|
||||||
|
|
||||||
const appId = context.getAppId() || (ctx.request.query.appId as string)
|
const appId = context.getAppId() || (ctx.request.query.appId as string)
|
||||||
let rootPath = join(NODE_MODULES_PATH, "@budibase", "client", "dist")
|
let rootPath = join(NODE_MODULES_PATH, "@budibase", "client", "dist")
|
||||||
if (!appId) {
|
if (!appId) {
|
||||||
ctx.throw(400, "No app ID provided - cannot fetch client library.")
|
ctx.throw(400, "No app ID provided - cannot fetch client library.")
|
||||||
}
|
}
|
||||||
if (env.isProd()) {
|
if (env.isProd() || (env.isDev() && version !== devClientVersion)) {
|
||||||
ctx.body = await objectStore.getReadStream(
|
ctx.body = await objectStore.getReadStream(
|
||||||
ObjectStoreBuckets.APPS,
|
ObjectStoreBuckets.APPS,
|
||||||
objectStore.clientLibraryPath(appId!)
|
objectStore.clientLibraryPath(appId!)
|
||||||
)
|
)
|
||||||
ctx.set("Content-Type", "application/javascript")
|
ctx.set("Content-Type", "application/javascript")
|
||||||
} else if (env.isDev()) {
|
} else if (env.isDev() && version === devClientVersion) {
|
||||||
// incase running from TS directly
|
// incase running from TS directly
|
||||||
const tsPath = join(require.resolve("@budibase/client"), "..")
|
const tsPath = join(require.resolve("@budibase/client"), "..")
|
||||||
return send(ctx, "budibase-client.js", {
|
return send(ctx, "budibase-client.js", {
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
|
import ClientAppSkeleton from "@budibase/frontend-core/src/components/ClientAppSkeleton.svelte"
|
||||||
|
|
||||||
export let title = ""
|
export let title = ""
|
||||||
export let favicon = ""
|
export let favicon = ""
|
||||||
|
|
||||||
|
@ -9,6 +11,11 @@
|
||||||
export let clientLibPath
|
export let clientLibPath
|
||||||
export let usedPlugins
|
export let usedPlugins
|
||||||
export let appMigrating
|
export let appMigrating
|
||||||
|
|
||||||
|
export let showSkeletonLoader = false
|
||||||
|
export let hideDevTools
|
||||||
|
export let sideNav
|
||||||
|
export let hideFooter
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
|
@ -96,6 +103,9 @@
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<body id="app">
|
<body id="app">
|
||||||
|
{#if showSkeletonLoader}
|
||||||
|
<ClientAppSkeleton {hideDevTools} {sideNav} {hideFooter} />
|
||||||
|
{/if}
|
||||||
<div id="error">
|
<div id="error">
|
||||||
{#if clientLibPath}
|
{#if clientLibPath}
|
||||||
<h1>There was an error loading your app</h1>
|
<h1>There was an error loading your app</h1>
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
<html>
|
<html>
|
||||||
|
<script>
|
||||||
|
document.fonts.ready.then(() => {
|
||||||
|
window.parent.postMessage({ type: "docLoaded" });
|
||||||
|
})
|
||||||
|
</script>
|
||||||
<head>
|
<head>
|
||||||
{{{head}}}
|
{{{head}}}
|
||||||
<style>{{{style}}}</style>
|
<style>{{{css}}}</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
|
@ -68,5 +68,10 @@ router
|
||||||
authorized(permissions.BUILDER),
|
authorized(permissions.BUILDER),
|
||||||
controller.importToApp
|
controller.importToApp
|
||||||
)
|
)
|
||||||
|
.post(
|
||||||
|
"/api/applications/:appId/setRevertableVersion",
|
||||||
|
authorized(permissions.BUILDER),
|
||||||
|
controller.setRevertableVersion
|
||||||
|
)
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
|
|
@ -51,8 +51,8 @@ router
|
||||||
controller.deleteObjects
|
controller.deleteObjects
|
||||||
)
|
)
|
||||||
.get("/app/preview", authorized(BUILDER), controller.serveBuilderPreview)
|
.get("/app/preview", authorized(BUILDER), controller.serveBuilderPreview)
|
||||||
.get("/:appId/:path*", controller.serveApp)
|
|
||||||
.get("/app/:appUrl/:path*", controller.serveApp)
|
.get("/app/:appUrl/:path*", controller.serveApp)
|
||||||
|
.get("/:appId/:path*", controller.serveApp)
|
||||||
.post(
|
.post(
|
||||||
"/api/attachments/:datasourceId/url",
|
"/api/attachments/:datasourceId/url",
|
||||||
authorized(PermissionType.TABLE, PermissionLevel.READ),
|
authorized(PermissionType.TABLE, PermissionLevel.READ),
|
||||||
|
|
|
@ -165,6 +165,8 @@ export enum AutomationErrors {
|
||||||
FAILURE_CONDITION = "FAILURE_CONDITION_MET",
|
FAILURE_CONDITION = "FAILURE_CONDITION_MET",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const devClientVersion = "0.0.0"
|
||||||
|
|
||||||
// pass through the list from the auth/core lib
|
// pass through the list from the auth/core lib
|
||||||
export const ObjectStoreBuckets = objectStore.ObjectStoreBuckets
|
export const ObjectStoreBuckets = objectStore.ObjectStoreBuckets
|
||||||
export const MAX_AUTOMATION_RECURRING_ERRORS = 5
|
export const MAX_AUTOMATION_RECURRING_ERRORS = 5
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
export const getThemeVariables = (theme: string) => {
|
||||||
|
if (theme === "spectrum--lightest") {
|
||||||
|
return `
|
||||||
|
--spectrum-global-color-gray-50: rgb(255, 255, 255);
|
||||||
|
--spectrum-global-color-gray-200: rgb(244, 244, 244);
|
||||||
|
--spectrum-global-color-gray-300: rgb(234, 234, 234);
|
||||||
|
--spectrum-alias-background-color-primary: var(--spectrum-global-color-gray-50);
|
||||||
|
`
|
||||||
|
}
|
||||||
|
if (theme === "spectrum--light") {
|
||||||
|
return `
|
||||||
|
--spectrum-global-color-gray-50: rgb(255, 255, 255);
|
||||||
|
--spectrum-global-color-gray-200: rgb(234, 234, 234);
|
||||||
|
--spectrum-global-color-gray-300: rgb(225, 225, 225);
|
||||||
|
--spectrum-alias-background-color-primary: var(--spectrum-global-color-gray-50);
|
||||||
|
|
||||||
|
`
|
||||||
|
}
|
||||||
|
if (theme === "spectrum--dark") {
|
||||||
|
return `
|
||||||
|
--spectrum-global-color-gray-100: rgb(50, 50, 50);
|
||||||
|
--spectrum-global-color-gray-200: rgb(62, 62, 62);
|
||||||
|
--spectrum-global-color-gray-300: rgb(74, 74, 74);
|
||||||
|
--spectrum-alias-background-color-primary: var(--spectrum-global-color-gray-100);
|
||||||
|
`
|
||||||
|
}
|
||||||
|
if (theme === "spectrum--darkest") {
|
||||||
|
return `
|
||||||
|
--spectrum-global-color-gray-100: rgb(30, 30, 30);
|
||||||
|
--spectrum-global-color-gray-200: rgb(44, 44, 44);
|
||||||
|
--spectrum-global-color-gray-300: rgb(57, 57, 57);
|
||||||
|
--spectrum-alias-background-color-primary: var(--spectrum-global-color-gray-100);
|
||||||
|
`
|
||||||
|
}
|
||||||
|
if (theme === "spectrum--nord") {
|
||||||
|
return `
|
||||||
|
--spectrum-global-color-gray-100: #3b4252;
|
||||||
|
|
||||||
|
--spectrum-global-color-gray-200: #424a5c;
|
||||||
|
--spectrum-global-color-gray-300: #4c566a;
|
||||||
|
--spectrum-alias-background-color-primary: var(--spectrum-global-color-gray-100);
|
||||||
|
`
|
||||||
|
}
|
||||||
|
if (theme === "spectrum--midnight") {
|
||||||
|
return `
|
||||||
|
--hue: 220;
|
||||||
|
--sat: 10%;
|
||||||
|
--spectrum-global-color-gray-100: hsl(var(--hue), var(--sat), 17%);
|
||||||
|
--spectrum-global-color-gray-200: hsl(var(--hue), var(--sat), 20%);
|
||||||
|
--spectrum-global-color-gray-300: hsl(var(--hue), var(--sat), 24%);
|
||||||
|
--spectrum-alias-background-color-primary: var(--spectrum-global-color-gray-100);
|
||||||
|
`
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,3 +11,6 @@ process.env.PLATFORM_URL = "http://localhost:10000"
|
||||||
process.env.REDIS_PASSWORD = "budibase"
|
process.env.REDIS_PASSWORD = "budibase"
|
||||||
process.env.BUDIBASE_VERSION = "0.0.0+jest"
|
process.env.BUDIBASE_VERSION = "0.0.0+jest"
|
||||||
process.env.WORKER_URL = "http://localhost:10000"
|
process.env.WORKER_URL = "http://localhost:10000"
|
||||||
|
process.env.COUCH_DB_PASSWORD = "budibase"
|
||||||
|
process.env.COUCH_DB_USER = "budibase"
|
||||||
|
process.env.JWT_SECRET = "jwtsecret"
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
import { budibaseTempDir } from "../budibaseDir"
|
import { budibaseTempDir } from "../budibaseDir"
|
||||||
import fs from "fs"
|
import fs from "fs"
|
||||||
import { join } from "path"
|
import { join } from "path"
|
||||||
import { ObjectStoreBuckets } from "../../constants"
|
import { ObjectStoreBuckets, devClientVersion } from "../../constants"
|
||||||
import { updateClientLibrary } from "./clientLibrary"
|
import { updateClientLibrary } from "./clientLibrary"
|
||||||
import env from "../../environment"
|
import env from "../../environment"
|
||||||
import { objectStore, context } from "@budibase/backend-core"
|
import { objectStore, context } from "@budibase/backend-core"
|
||||||
import { TOP_LEVEL_PATH } from "./filesystem"
|
import { TOP_LEVEL_PATH } from "./filesystem"
|
||||||
|
import { DocumentType } from "../../db/utils"
|
||||||
|
import { App } from "@budibase/types"
|
||||||
|
|
||||||
export const NODE_MODULES_PATH = join(TOP_LEVEL_PATH, "node_modules")
|
export const NODE_MODULES_PATH = join(TOP_LEVEL_PATH, "node_modules")
|
||||||
|
|
||||||
|
@ -35,20 +37,25 @@ export const getComponentLibraryManifest = async (library: string) => {
|
||||||
const filename = "manifest.json"
|
const filename = "manifest.json"
|
||||||
|
|
||||||
if (env.isDev() || env.isTest()) {
|
if (env.isDev() || env.isTest()) {
|
||||||
const paths = [
|
const db = context.getAppDB()
|
||||||
join(TOP_LEVEL_PATH, "packages/client", filename),
|
const app = await db.get<App>(DocumentType.APP_METADATA)
|
||||||
join(process.cwd(), "client", filename),
|
|
||||||
]
|
if (app.version === devClientVersion || env.isTest()) {
|
||||||
for (let path of paths) {
|
const paths = [
|
||||||
if (fs.existsSync(path)) {
|
join(TOP_LEVEL_PATH, "packages/client", filename),
|
||||||
// always load from new so that updates are refreshed
|
join(process.cwd(), "client", filename),
|
||||||
delete require.cache[require.resolve(path)]
|
]
|
||||||
return require(path)
|
for (let path of paths) {
|
||||||
|
if (fs.existsSync(path)) {
|
||||||
|
// always load from new so that updates are refreshed
|
||||||
|
delete require.cache[require.resolve(path)]
|
||||||
|
return require(path)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
throw new Error(
|
||||||
|
`Unable to find ${filename} in development environment (may need to build).`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
throw new Error(
|
|
||||||
`Unable to find ${filename} in development environment (may need to build).`
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!appId) {
|
if (!appId) {
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import path, { join } from "path"
|
import path, { join } from "path"
|
||||||
import { ObjectStoreBuckets } from "../../constants"
|
import { ObjectStoreBuckets } from "../../constants"
|
||||||
import fs from "fs"
|
import fs from "fs"
|
||||||
import { objectStore } from "@budibase/backend-core"
|
import { context, objectStore } from "@budibase/backend-core"
|
||||||
import { resolve } from "../centralPath"
|
import { resolve } from "../centralPath"
|
||||||
import env from "../../environment"
|
import env from "../../environment"
|
||||||
import { TOP_LEVEL_PATH } from "./filesystem"
|
import { TOP_LEVEL_PATH } from "./filesystem"
|
||||||
|
import { DocumentType } from "../../db/utils"
|
||||||
|
import { App } from "@budibase/types"
|
||||||
|
|
||||||
export function devClientLibPath() {
|
export function devClientLibPath() {
|
||||||
return require.resolve("@budibase/client")
|
return require.resolve("@budibase/client")
|
||||||
|
@ -120,7 +122,12 @@ export async function updateClientLibrary(appId: string) {
|
||||||
ContentType: "application/javascript",
|
ContentType: "application/javascript",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
await Promise.all([manifestUpload, clientUpload])
|
|
||||||
|
const manifestSrc = fs.promises.readFile(manifest, "utf8")
|
||||||
|
|
||||||
|
await Promise.all([manifestUpload, clientUpload, manifestSrc])
|
||||||
|
|
||||||
|
return JSON.parse(await manifestSrc)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -130,30 +137,49 @@ export async function updateClientLibrary(appId: string) {
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
export async function revertClientLibrary(appId: string) {
|
export async function revertClientLibrary(appId: string) {
|
||||||
// Copy backups manifest to tmp directory
|
let manifestPath, clientPath
|
||||||
const tmpManifestPath = await objectStore.retrieveToTmp(
|
|
||||||
ObjectStoreBuckets.APPS,
|
|
||||||
join(appId, "manifest.json.bak")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Copy backup client lib to tmp
|
if (env.isDev()) {
|
||||||
const tmpClientPath = await objectStore.retrieveToTmp(
|
const db = context.getAppDB()
|
||||||
ObjectStoreBuckets.APPS,
|
const app = await db.get<App>(DocumentType.APP_METADATA)
|
||||||
join(appId, "budibase-client.js.bak")
|
clientPath = join(
|
||||||
)
|
__dirname,
|
||||||
|
`/oldClientVersions/${app.revertableVersion}/app.js`
|
||||||
|
)
|
||||||
|
manifestPath = join(
|
||||||
|
__dirname,
|
||||||
|
`/oldClientVersions/${app.revertableVersion}/manifest.json`
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// Copy backups manifest to tmp directory
|
||||||
|
manifestPath = await objectStore.retrieveToTmp(
|
||||||
|
ObjectStoreBuckets.APPS,
|
||||||
|
join(appId, "manifest.json.bak")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Copy backup client lib to tmp
|
||||||
|
clientPath = await objectStore.retrieveToTmp(
|
||||||
|
ObjectStoreBuckets.APPS,
|
||||||
|
join(appId, "budibase-client.js.bak")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const manifestSrc = fs.promises.readFile(manifestPath, "utf8")
|
||||||
|
|
||||||
// Upload backups as new versions
|
// Upload backups as new versions
|
||||||
const manifestUpload = objectStore.upload({
|
const manifestUpload = objectStore.upload({
|
||||||
bucket: ObjectStoreBuckets.APPS,
|
bucket: ObjectStoreBuckets.APPS,
|
||||||
filename: join(appId, "manifest.json"),
|
filename: join(appId, "manifest.json"),
|
||||||
path: tmpManifestPath,
|
path: manifestPath,
|
||||||
type: "application/json",
|
type: "application/json",
|
||||||
})
|
})
|
||||||
const clientUpload = objectStore.upload({
|
const clientUpload = objectStore.upload({
|
||||||
bucket: ObjectStoreBuckets.APPS,
|
bucket: ObjectStoreBuckets.APPS,
|
||||||
filename: join(appId, "budibase-client.js"),
|
filename: join(appId, "budibase-client.js"),
|
||||||
path: tmpClientPath,
|
path: clientPath,
|
||||||
type: "application/javascript",
|
type: "application/javascript",
|
||||||
})
|
})
|
||||||
await Promise.all([manifestUpload, clientUpload])
|
await Promise.all([manifestSrc, manifestUpload, clientUpload])
|
||||||
|
|
||||||
|
return JSON.parse(await manifestSrc)
|
||||||
}
|
}
|
||||||
|
|
|
@ -159,3 +159,22 @@ export const InvalidFileExtensions = [
|
||||||
"wsh",
|
"wsh",
|
||||||
"zip",
|
"zip",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
export enum BpmCorrelationKey {
|
||||||
|
ONBOARDING = "budibase:onboarding:correlationkey",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum BpmInstanceKey {
|
||||||
|
ONBOARDING = "budibase:onboarding:instancekey",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum BpmStatusKey {
|
||||||
|
ONBOARDING = "budibase:onboarding:status",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum BpmStatusValue {
|
||||||
|
STARTED = "started",
|
||||||
|
COMPLETING_ACCOUNT_INFO = "completing_account_info",
|
||||||
|
VERIFYING_EMAIL = "verifying_email",
|
||||||
|
COMPLETED = "completed",
|
||||||
|
}
|
||||||
|
|
|
@ -63,7 +63,7 @@ export interface SearchUsersRequest {
|
||||||
|
|
||||||
export interface CreateAdminUserRequest {
|
export interface CreateAdminUserRequest {
|
||||||
email: string
|
email: string
|
||||||
password: string
|
password?: string
|
||||||
tenantId: string
|
tenantId: string
|
||||||
ssoId?: string
|
ssoId?: string
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,6 +71,7 @@ export interface AppIcon {
|
||||||
export interface AppFeatures {
|
export interface AppFeatures {
|
||||||
componentValidation?: boolean
|
componentValidation?: boolean
|
||||||
disableUserMetadata?: boolean
|
disableUserMetadata?: boolean
|
||||||
|
skeletonLoader?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AutomationSettings {
|
export interface AutomationSettings {
|
||||||
|
|
|
@ -1,142 +0,0 @@
|
||||||
{
|
|
||||||
// Use IntelliSense to learn about possible attributes.
|
|
||||||
// Hover to view descriptions of existing attributes.
|
|
||||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
|
||||||
"version": "0.2.0",
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"name": "Start Server",
|
|
||||||
"type": "node",
|
|
||||||
"request": "launch",
|
|
||||||
"runtimeExecutable": "node",
|
|
||||||
"runtimeArgs": ["--nolazy", "-r", "ts-node/register/transpile-only"],
|
|
||||||
"args": ["src/index.ts"],
|
|
||||||
"cwd": "${workspaceRoot}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "node",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "Jest - All",
|
|
||||||
"program": "${workspaceFolder}/node_modules/.bin/jest",
|
|
||||||
"args": [],
|
|
||||||
"console": "integratedTerminal",
|
|
||||||
"internalConsoleOptions": "neverOpen",
|
|
||||||
"disableOptimisticBPs": true,
|
|
||||||
"windows": {
|
|
||||||
"program": "${workspaceFolder}/node_modules/jest-cli/bin/jest",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "node",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "Jest - Users",
|
|
||||||
"program": "${workspaceFolder}/node_modules/.bin/jest",
|
|
||||||
"args": ["user.spec", "--runInBand"],
|
|
||||||
"console": "integratedTerminal",
|
|
||||||
"internalConsoleOptions": "neverOpen",
|
|
||||||
"disableOptimisticBPs": true,
|
|
||||||
"windows": {
|
|
||||||
"program": "${workspaceFolder}/node_modules/jest-cli/bin/jest",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "node",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "Jest - Instances",
|
|
||||||
"program": "${workspaceFolder}/node_modules/.bin/jest",
|
|
||||||
"args": ["instance.spec", "--runInBand"],
|
|
||||||
"console": "integratedTerminal",
|
|
||||||
"internalConsoleOptions": "neverOpen",
|
|
||||||
"disableOptimisticBPs": true,
|
|
||||||
"windows": {
|
|
||||||
"program": "${workspaceFolder}/node_modules/jest-cli/bin/jest",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "node",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "Jest - Roles",
|
|
||||||
"program": "${workspaceFolder}/node_modules/.bin/jest",
|
|
||||||
"args": ["role.spec", "--runInBand"],
|
|
||||||
"console": "integratedTerminal",
|
|
||||||
"internalConsoleOptions": "neverOpen",
|
|
||||||
"disableOptimisticBPs": true,
|
|
||||||
"windows": {
|
|
||||||
"program": "${workspaceFolder}/node_modules/jest-cli/bin/jest",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "node",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "Jest - Records",
|
|
||||||
"program": "${workspaceFolder}/node_modules/.bin/jest",
|
|
||||||
"args": ["record.spec", "--runInBand"],
|
|
||||||
"console": "integratedTerminal",
|
|
||||||
"internalConsoleOptions": "neverOpen",
|
|
||||||
"disableOptimisticBPs": true,
|
|
||||||
"windows": {
|
|
||||||
"program": "${workspaceFolder}/node_modules/jest-cli/bin/jest",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "node",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "Jest - Models",
|
|
||||||
"program": "${workspaceFolder}/node_modules/.bin/jest",
|
|
||||||
"args": ["table.spec", "--runInBand"],
|
|
||||||
"console": "integratedTerminal",
|
|
||||||
"internalConsoleOptions": "neverOpen",
|
|
||||||
"disableOptimisticBPs": true,
|
|
||||||
"windows": {
|
|
||||||
"program": "${workspaceFolder}/node_modules/jest-cli/bin/jest",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "node",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "Jest - Views",
|
|
||||||
"program": "${workspaceFolder}/node_modules/.bin/jest",
|
|
||||||
"args": ["view.spec", "--runInBand"],
|
|
||||||
"console": "integratedTerminal",
|
|
||||||
"internalConsoleOptions": "neverOpen",
|
|
||||||
"disableOptimisticBPs": true,
|
|
||||||
"windows": {
|
|
||||||
"program": "${workspaceFolder}/node_modules/jest-cli/bin/jest",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "node",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "Jest - Applications",
|
|
||||||
"program": "${workspaceFolder}/node_modules/.bin/jest",
|
|
||||||
"args": ["application.spec", "--runInBand"],
|
|
||||||
"console": "integratedTerminal",
|
|
||||||
"internalConsoleOptions": "neverOpen",
|
|
||||||
"disableOptimisticBPs": true,
|
|
||||||
"windows": {
|
|
||||||
"program": "${workspaceFolder}/node_modules/jest-cli/bin/jest",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "node",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "Jest Builder",
|
|
||||||
"program": "${workspaceFolder}/node_modules/.bin/jest",
|
|
||||||
"args": ["builder", "--runInBand"],
|
|
||||||
"console": "integratedTerminal",
|
|
||||||
"internalConsoleOptions": "neverOpen",
|
|
||||||
"disableOptimisticBPs": true,
|
|
||||||
"windows": {
|
|
||||||
"program": "${workspaceFolder}/node_modules/jest-cli/bin/jest",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "node",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "Initialise Budibase",
|
|
||||||
"program": "yarn",
|
|
||||||
"args": ["run", "initialise"],
|
|
||||||
"console": "externalTerminal"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
const { join } = require("path")
|
|
||||||
require("dotenv").config({
|
|
||||||
path: join(__dirname, "..", "..", "hosting", ".env"),
|
|
||||||
})
|
|
||||||
|
|
||||||
const jestTestcontainersConfigGenerator = require("../../jestTestcontainersConfigGenerator")
|
|
||||||
|
|
||||||
module.exports = jestTestcontainersConfigGenerator()
|
|
|
@ -2,7 +2,7 @@ import { Config } from "@jest/types"
|
||||||
import * as fs from "fs"
|
import * as fs from "fs"
|
||||||
|
|
||||||
const config: Config.InitialOptions = {
|
const config: Config.InitialOptions = {
|
||||||
preset: "@trendyol/jest-testcontainers",
|
globalSetup: "./../../globalSetup.ts",
|
||||||
setupFiles: ["./src/tests/jestEnv.ts"],
|
setupFiles: ["./src/tests/jestEnv.ts"],
|
||||||
setupFilesAfterEnv: ["./src/tests/jestSetup.ts"],
|
setupFilesAfterEnv: ["./src/tests/jestSetup.ts"],
|
||||||
collectCoverageFrom: ["src/**/*.{js,ts}", "../backend-core/src/**/*.{js,ts}"],
|
collectCoverageFrom: ["src/**/*.{js,ts}", "../backend-core/src/**/*.{js,ts}"],
|
||||||
|
|
|
@ -75,7 +75,6 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@swc/core": "1.3.71",
|
"@swc/core": "1.3.71",
|
||||||
"@swc/jest": "0.2.27",
|
"@swc/jest": "0.2.27",
|
||||||
"@trendyol/jest-testcontainers": "2.1.1",
|
|
||||||
"@types/jest": "29.5.5",
|
"@types/jest": "29.5.5",
|
||||||
"@types/jsonwebtoken": "9.0.3",
|
"@types/jsonwebtoken": "9.0.3",
|
||||||
"@types/koa": "2.13.4",
|
"@types/koa": "2.13.4",
|
||||||
|
|
|
@ -127,8 +127,8 @@ export const adminUser = async (
|
||||||
try {
|
try {
|
||||||
const finalUser = await userSdk.db.createAdminUser(
|
const finalUser = await userSdk.db.createAdminUser(
|
||||||
email,
|
email,
|
||||||
password,
|
|
||||||
tenantId,
|
tenantId,
|
||||||
|
password,
|
||||||
{
|
{
|
||||||
ssoId,
|
ssoId,
|
||||||
hashPassword,
|
hashPassword,
|
||||||
|
|
|
@ -7,12 +7,13 @@ import { users } from "../validation"
|
||||||
import * as selfController from "../../controllers/global/self"
|
import * as selfController from "../../controllers/global/self"
|
||||||
|
|
||||||
const router: Router = new Router()
|
const router: Router = new Router()
|
||||||
|
const OPTIONAL_STRING = Joi.string().optional().allow(null).allow("")
|
||||||
|
|
||||||
function buildAdminInitValidation() {
|
function buildAdminInitValidation() {
|
||||||
return auth.joiValidator.body(
|
return auth.joiValidator.body(
|
||||||
Joi.object({
|
Joi.object({
|
||||||
email: Joi.string().required(),
|
email: Joi.string().required(),
|
||||||
password: Joi.string(),
|
password: OPTIONAL_STRING,
|
||||||
tenantId: Joi.string().required(),
|
tenantId: Joi.string().required(),
|
||||||
ssoId: Joi.string(),
|
ssoId: Joi.string(),
|
||||||
})
|
})
|
||||||
|
|
|
@ -11,3 +11,5 @@ process.env.INTERNAL_API_KEY = "tet"
|
||||||
process.env.DISABLE_ACCOUNT_PORTAL = "0"
|
process.env.DISABLE_ACCOUNT_PORTAL = "0"
|
||||||
process.env.MOCK_REDIS = "1"
|
process.env.MOCK_REDIS = "1"
|
||||||
process.env.BUDIBASE_VERSION = "0.0.0+jest"
|
process.env.BUDIBASE_VERSION = "0.0.0+jest"
|
||||||
|
process.env.COUCH_DB_PASSWORD = "budibase"
|
||||||
|
process.env.COUCH_DB_USER = "budibase"
|
||||||
|
|
|
@ -1,42 +0,0 @@
|
||||||
import TestConfiguration from "../../config/TestConfiguration"
|
|
||||||
import * as fixtures from "../../fixtures"
|
|
||||||
|
|
||||||
describe("Internal API - Application creation", () => {
|
|
||||||
const config = new TestConfiguration()
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
await config.beforeAll()
|
|
||||||
})
|
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await config.afterAll()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("Get applications without applications", async () => {
|
|
||||||
await config.api.apps.fetchEmptyAppList()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("Get all Applications after creating an application", async () => {
|
|
||||||
await config.api.apps.create({
|
|
||||||
...fixtures.apps.generateApp(),
|
|
||||||
useTemplate: "false",
|
|
||||||
})
|
|
||||||
|
|
||||||
await config.api.apps.fetchAllApplications()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("Get application details", async () => {
|
|
||||||
const app = await config.createApp({
|
|
||||||
...fixtures.apps.generateApp(),
|
|
||||||
useTemplate: "false",
|
|
||||||
})
|
|
||||||
|
|
||||||
const [appPackageResponse, appPackageJson] =
|
|
||||||
await config.api.apps.getAppPackage(app.appId!)
|
|
||||||
expect(appPackageJson.application.name).toEqual(app.name)
|
|
||||||
expect(appPackageJson.application.version).toEqual(app.version)
|
|
||||||
expect(appPackageJson.application.url).toEqual(app.url)
|
|
||||||
expect(appPackageJson.application.tenantId).toEqual(app.tenantId)
|
|
||||||
expect(appPackageJson.application.status).toEqual(app.status)
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,19 +0,0 @@
|
||||||
import TestConfiguration from "../../config/TestConfiguration"
|
|
||||||
|
|
||||||
describe("Internal API - Application creation, update, publish and delete", () => {
|
|
||||||
const config = new TestConfiguration()
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
await config.beforeAll()
|
|
||||||
})
|
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await config.afterAll()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("DELETE - Delete an application", async () => {
|
|
||||||
const app = await config.createApp()
|
|
||||||
|
|
||||||
await config.api.apps.delete(app.appId!)
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,54 +0,0 @@
|
||||||
import TestConfiguration from "../../config/TestConfiguration"
|
|
||||||
import { db } from "@budibase/backend-core"
|
|
||||||
import * as fixtures from "../../fixtures"
|
|
||||||
|
|
||||||
describe("Internal API - Application creation, update, publish and delete", () => {
|
|
||||||
const config = new TestConfiguration()
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
await config.beforeAll()
|
|
||||||
})
|
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await config.afterAll()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("Publish app", async () => {
|
|
||||||
// create the app
|
|
||||||
const app = await config.createApp(fixtures.apps.appFromTemplate())
|
|
||||||
|
|
||||||
// check preview renders
|
|
||||||
await config.api.apps.canRender()
|
|
||||||
|
|
||||||
// publish app
|
|
||||||
await config.api.apps.publish(app.appId)
|
|
||||||
|
|
||||||
// check published app renders
|
|
||||||
config.state.appId = db.getProdAppID(app.appId!)
|
|
||||||
await config.api.apps.canRender()
|
|
||||||
|
|
||||||
// unpublish app
|
|
||||||
await config.api.apps.unpublish(app.appId!)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("Sync application before deployment", async () => {
|
|
||||||
const app = await config.createApp()
|
|
||||||
|
|
||||||
const [syncResponse, sync] = await config.api.apps.sync(app.appId!)
|
|
||||||
expect(sync).toEqual({
|
|
||||||
message: "App sync completed successfully.",
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it("Sync application after deployment", async () => {
|
|
||||||
const app = await config.createApp()
|
|
||||||
|
|
||||||
// publish app
|
|
||||||
await config.api.apps.publish(app._id)
|
|
||||||
|
|
||||||
const [syncResponse, sync] = await config.api.apps.sync(app.appId!)
|
|
||||||
expect(sync).toEqual({
|
|
||||||
message: "App sync completed successfully.",
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,45 +0,0 @@
|
||||||
import TestConfiguration from "../../config/TestConfiguration"
|
|
||||||
import { generator } from "../../../shared"
|
|
||||||
import * as fixtures from "../../fixtures"
|
|
||||||
|
|
||||||
describe("Internal API - Application creation, update, publish and delete", () => {
|
|
||||||
const config = new TestConfiguration()
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
await config.beforeAll()
|
|
||||||
})
|
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await config.afterAll()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("Update an application", async () => {
|
|
||||||
const app = await config.createApp()
|
|
||||||
|
|
||||||
await config.api.apps.rename(app.appId!, app.name!, {
|
|
||||||
name: generator.word(),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it("Revert Changes without changes", async () => {
|
|
||||||
const app = await config.createApp()
|
|
||||||
|
|
||||||
await config.api.apps.revertUnpublished(app.appId!)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("Revert Changes", async () => {
|
|
||||||
const app = await config.createApp()
|
|
||||||
|
|
||||||
// publish app
|
|
||||||
await config.api.apps.publish(app._id)
|
|
||||||
|
|
||||||
// Change/add component to the app
|
|
||||||
await config.api.screens.create(fixtures.screens.generateScreen("BASIC"))
|
|
||||||
|
|
||||||
// // Revert the app to published state
|
|
||||||
await config.api.apps.revertPublished(app.appId!)
|
|
||||||
|
|
||||||
// Check screen is removed
|
|
||||||
await config.api.apps.getRoutes()
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,51 +0,0 @@
|
||||||
import TestConfiguration from "../../config/TestConfiguration"
|
|
||||||
import * as fixtures from "../../fixtures"
|
|
||||||
|
|
||||||
describe("Internal API - /screens endpoints", () => {
|
|
||||||
const config = new TestConfiguration()
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
await config.beforeAll()
|
|
||||||
})
|
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await config.afterAll()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("Create a screen with each role type", async () => {
|
|
||||||
// Create app
|
|
||||||
await config.createApp()
|
|
||||||
|
|
||||||
// Create Screen
|
|
||||||
const roleArray = ["BASIC", "POWER", "ADMIN", "PUBLIC"]
|
|
||||||
for (let role in roleArray) {
|
|
||||||
const [response, screen] = await config.api.screens.create(
|
|
||||||
fixtures.screens.generateScreen(roleArray[role])
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
it("Get screens", async () => {
|
|
||||||
// Create app
|
|
||||||
await config.createApp()
|
|
||||||
|
|
||||||
// Create Screen
|
|
||||||
await config.api.screens.create(fixtures.screens.generateScreen("BASIC"))
|
|
||||||
|
|
||||||
// Check screen exists
|
|
||||||
await config.api.apps.getRoutes(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("Delete a screen", async () => {
|
|
||||||
// Create app
|
|
||||||
await config.createApp()
|
|
||||||
|
|
||||||
// Create Screen
|
|
||||||
const [screenResponse, screen] = await config.api.screens.create(
|
|
||||||
fixtures.screens.generateScreen("BASIC")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Delete Screen
|
|
||||||
await config.api.screens.delete(screen._id!, screen._rev!)
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,133 +0,0 @@
|
||||||
import TestConfiguration from "../../config/TestConfiguration"
|
|
||||||
import { generator } from "../../../shared"
|
|
||||||
import * as fixtures from "../../fixtures"
|
|
||||||
|
|
||||||
describe("Internal API - Table Operations", () => {
|
|
||||||
const config = new TestConfiguration()
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
await config.beforeAll()
|
|
||||||
})
|
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await config.afterAll()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("Create and delete table, columns and rows", async () => {
|
|
||||||
// create the app
|
|
||||||
await config.createApp(fixtures.apps.appFromTemplate())
|
|
||||||
|
|
||||||
// Get current tables: expect 2 in this template
|
|
||||||
await config.api.tables.getAll(2)
|
|
||||||
|
|
||||||
// Add new table
|
|
||||||
const [createdTableResponse, createdTableData] =
|
|
||||||
await config.api.tables.save(fixtures.tables.generateTable())
|
|
||||||
|
|
||||||
//Table was added
|
|
||||||
await config.api.tables.getAll(3)
|
|
||||||
|
|
||||||
//Get information about the table
|
|
||||||
await config.api.tables.getTableById(createdTableData._id!)
|
|
||||||
|
|
||||||
//Add Column to table
|
|
||||||
const newColumn =
|
|
||||||
fixtures.tables.generateNewColumnForTable(createdTableData)
|
|
||||||
const [addColumnResponse, addColumnData] = await config.api.tables.save(
|
|
||||||
newColumn,
|
|
||||||
true
|
|
||||||
)
|
|
||||||
|
|
||||||
//Add Row to table
|
|
||||||
const newRow = fixtures.rows.generateNewRowForTable(addColumnData._id!)
|
|
||||||
await config.api.rows.add(addColumnData._id!, newRow)
|
|
||||||
|
|
||||||
//Get Row from table
|
|
||||||
const [getRowResponse, getRowData] = await config.api.rows.getAll(
|
|
||||||
addColumnData._id!
|
|
||||||
)
|
|
||||||
|
|
||||||
//Delete Row from table
|
|
||||||
const rowToDelete = {
|
|
||||||
rows: [getRowData[0]],
|
|
||||||
}
|
|
||||||
const [deleteRowResponse, deleteRowData] = await config.api.rows.delete(
|
|
||||||
addColumnData._id!,
|
|
||||||
rowToDelete
|
|
||||||
)
|
|
||||||
expect(deleteRowData[0]._id).toEqual(getRowData[0]._id)
|
|
||||||
|
|
||||||
//Delete the table
|
|
||||||
const [deleteTableResponse, deleteTable] = await config.api.tables.delete(
|
|
||||||
addColumnData._id!,
|
|
||||||
addColumnData._rev!
|
|
||||||
)
|
|
||||||
|
|
||||||
//Table was deleted
|
|
||||||
await config.api.tables.getAll(2)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("Search and pagination", async () => {
|
|
||||||
// create the app
|
|
||||||
await config.createApp(fixtures.apps.appFromTemplate())
|
|
||||||
|
|
||||||
// Get current tables: expect 2 in this template
|
|
||||||
await config.api.tables.getAll(2)
|
|
||||||
|
|
||||||
// Add new table
|
|
||||||
const [createdTableResponse, createdTableData] =
|
|
||||||
await config.api.tables.save(fixtures.tables.generateTable())
|
|
||||||
|
|
||||||
//Table was added
|
|
||||||
await config.api.tables.getAll(3)
|
|
||||||
|
|
||||||
//Get information about the table
|
|
||||||
await config.api.tables.getTableById(createdTableData._id!)
|
|
||||||
|
|
||||||
//Add Column to table
|
|
||||||
const newColumn =
|
|
||||||
fixtures.tables.generateNewColumnForTable(createdTableData)
|
|
||||||
const [addColumnResponse, addColumnData] = await config.api.tables.save(
|
|
||||||
newColumn,
|
|
||||||
true
|
|
||||||
)
|
|
||||||
|
|
||||||
//Add Row to table
|
|
||||||
let newRow = fixtures.rows.generateNewRowForTable(addColumnData._id!)
|
|
||||||
await config.api.rows.add(addColumnData._id!, newRow)
|
|
||||||
|
|
||||||
//Search single row
|
|
||||||
await config.api.rows.searchNoPagination(
|
|
||||||
createdTableData._id!,
|
|
||||||
fixtures.rows.searchBody(createdTableData.primaryDisplay!)
|
|
||||||
)
|
|
||||||
|
|
||||||
//Add 10 more rows
|
|
||||||
for (let i = 0; i < 10; i++) {
|
|
||||||
let newRow = fixtures.rows.generateNewRowForTable(addColumnData._id!)
|
|
||||||
await config.api.rows.add(addColumnData._id!, newRow)
|
|
||||||
}
|
|
||||||
|
|
||||||
//Search rows with pagination
|
|
||||||
const [allRowsResponse, allRowsJson] =
|
|
||||||
await config.api.rows.searchWithPagination(
|
|
||||||
createdTableData._id!,
|
|
||||||
fixtures.rows.searchBody(createdTableData.primaryDisplay!)
|
|
||||||
)
|
|
||||||
|
|
||||||
//Delete Rows from table
|
|
||||||
const rowToDelete = {
|
|
||||||
rows: [allRowsJson],
|
|
||||||
}
|
|
||||||
const [deleteRowResponse, deleteRowData] = await config.api.rows.delete(
|
|
||||||
createdTableData._id!,
|
|
||||||
rowToDelete
|
|
||||||
)
|
|
||||||
|
|
||||||
//Search single row
|
|
||||||
await config.api.rows.searchWithPagination(
|
|
||||||
createdTableData._id!,
|
|
||||||
fixtures.rows.searchBody(createdTableData.primaryDisplay!)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,104 +0,0 @@
|
||||||
import TestConfiguration from "../../config/TestConfiguration"
|
|
||||||
import { User } from "@budibase/types"
|
|
||||||
import { db } from "@budibase/backend-core"
|
|
||||||
import * as fixtures from "../../fixtures"
|
|
||||||
|
|
||||||
describe("Internal API - App Specific Roles & Permissions", () => {
|
|
||||||
const config = new TestConfiguration()
|
|
||||||
|
|
||||||
// Before each test, login as admin. Some tests will require login as a different user
|
|
||||||
beforeAll(async () => {
|
|
||||||
await config.beforeAll()
|
|
||||||
})
|
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await config.afterAll()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("Add BASIC user to app", async () => {
|
|
||||||
const appUser = fixtures.users.generateUser()
|
|
||||||
expect(appUser[0].builder?.global).toEqual(false)
|
|
||||||
expect(appUser[0].admin?.global).toEqual(false)
|
|
||||||
const [createUserResponse, createUserJson] =
|
|
||||||
await config.api.users.addMultiple(appUser)
|
|
||||||
|
|
||||||
const app = await config.createApp(fixtures.apps.appFromTemplate())
|
|
||||||
|
|
||||||
const [userInfoResponse, userInfoJson] = await config.api.users.getInfo(
|
|
||||||
createUserJson.created.successful[0]._id
|
|
||||||
)
|
|
||||||
const body: User = {
|
|
||||||
...userInfoJson,
|
|
||||||
roles: {
|
|
||||||
[app.appId!]: "BASIC",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
await config.api.users.updateInfo(body)
|
|
||||||
|
|
||||||
const [changedUserInfoResponse, changedUserInfoJson] =
|
|
||||||
await config.api.users.getInfo(createUserJson.created.successful[0]._id)
|
|
||||||
expect(changedUserInfoJson.roles[app.appId!]).toBeDefined()
|
|
||||||
expect(changedUserInfoJson.roles[app.appId!]).toEqual("BASIC")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("Add ADMIN user to app", async () => {
|
|
||||||
// Create a user with ADMIN role and check if it was created successfully
|
|
||||||
const adminUser = fixtures.users.generateUser(1, "admin")
|
|
||||||
expect(adminUser[0].builder?.global).toEqual(true)
|
|
||||||
expect(adminUser[0].admin?.global).toEqual(true)
|
|
||||||
const [createUserResponse, createUserJson] =
|
|
||||||
await config.api.users.addMultiple(adminUser)
|
|
||||||
|
|
||||||
// const app = await config.createApp(fixtures.apps.appFromTemplate())
|
|
||||||
const app = await config.createApp(fixtures.apps.appFromTemplate())
|
|
||||||
|
|
||||||
const [userInfoResponse, userInfoJson] = await config.api.users.getInfo(
|
|
||||||
createUserJson.created.successful[0]._id
|
|
||||||
)
|
|
||||||
const body: User = {
|
|
||||||
...userInfoJson,
|
|
||||||
roles: {
|
|
||||||
[app.appId!]: "ADMIN",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
await config.api.users.updateInfo(body)
|
|
||||||
|
|
||||||
const [changedUserInfoResponse, changedUserInfoJson] =
|
|
||||||
await config.api.users.getInfo(createUserJson.created.successful[0]._id)
|
|
||||||
expect(changedUserInfoJson.roles[app.appId!]).toBeDefined()
|
|
||||||
expect(changedUserInfoJson.roles[app.appId!]).toEqual("ADMIN")
|
|
||||||
|
|
||||||
// publish app
|
|
||||||
await config.api.apps.publish(app.appId)
|
|
||||||
// check published app renders
|
|
||||||
config.state.appId = db.getProdAppID(app.appId!)
|
|
||||||
await config.api.apps.canRender()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("Add POWER user to app", async () => {
|
|
||||||
const powerUser = fixtures.users.generateUser(1, "developer")
|
|
||||||
expect(powerUser[0].builder?.global).toEqual(true)
|
|
||||||
|
|
||||||
const [createUserResponse, createUserJson] =
|
|
||||||
await config.api.users.addMultiple(powerUser)
|
|
||||||
|
|
||||||
const app = await config.createApp()
|
|
||||||
|
|
||||||
const [userInfoResponse, userInfoJson] = await config.api.users.getInfo(
|
|
||||||
createUserJson.created.successful[0]._id
|
|
||||||
)
|
|
||||||
const body: User = {
|
|
||||||
...userInfoJson,
|
|
||||||
roles: {
|
|
||||||
[app.appId!]: "POWER",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
await config.api.users.updateInfo(body)
|
|
||||||
|
|
||||||
// Get the user information again and check if the role was added
|
|
||||||
const [changedUserInfoResponse, changedUserInfoJson] =
|
|
||||||
await config.api.users.getInfo(createUserJson.created.successful[0]._id)
|
|
||||||
expect(changedUserInfoJson.roles[app.appId!]).toBeDefined()
|
|
||||||
expect(changedUserInfoJson.roles[app.appId!]).toEqual("POWER")
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,90 +0,0 @@
|
||||||
import TestConfiguration from "../../config/TestConfiguration"
|
|
||||||
import { User } from "@budibase/types"
|
|
||||||
import * as fixtures from "./../../fixtures"
|
|
||||||
|
|
||||||
describe("Internal API - User Management & Permissions", () => {
|
|
||||||
const config = new TestConfiguration()
|
|
||||||
|
|
||||||
// Before each test, login as admin. Some tests will require login as a different user
|
|
||||||
beforeAll(async () => {
|
|
||||||
await config.beforeAll()
|
|
||||||
})
|
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await config.afterAll()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("Add Users with different roles", async () => {
|
|
||||||
// Get all users
|
|
||||||
await config.api.users.search()
|
|
||||||
|
|
||||||
// Get all roles
|
|
||||||
await config.api.users.getRoles()
|
|
||||||
|
|
||||||
// Add users with each role
|
|
||||||
const admin = fixtures.users.generateUser(1, "admin")
|
|
||||||
expect(admin[0].builder?.global).toEqual(true)
|
|
||||||
expect(admin[0].admin?.global).toEqual(true)
|
|
||||||
const developer = fixtures.users.generateUser(1, "developer")
|
|
||||||
expect(developer[0].builder?.global).toEqual(true)
|
|
||||||
const appUser = fixtures.users.generateUser(1, "appUser")
|
|
||||||
expect(appUser[0].builder?.global).toEqual(false)
|
|
||||||
expect(appUser[0].admin?.global).toEqual(false)
|
|
||||||
|
|
||||||
const userList = [...admin, ...developer, ...appUser]
|
|
||||||
|
|
||||||
await config.api.users.addMultiple(userList)
|
|
||||||
|
|
||||||
// Check users are added
|
|
||||||
const [allUsersResponse, allUsersJson] = await config.api.users.getAll()
|
|
||||||
expect(allUsersJson.length).toBeGreaterThan(0)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("Delete User", async () => {
|
|
||||||
const appUser = fixtures.users.generateUser()
|
|
||||||
expect(appUser[0].builder?.global).toEqual(false)
|
|
||||||
expect(appUser[0].admin?.global).toEqual(false)
|
|
||||||
const [userResponse, userJson] = await config.api.users.addMultiple(appUser)
|
|
||||||
const userId = userJson.created.successful[0]._id
|
|
||||||
await config.api.users.delete(userId)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("Reset Password", async () => {
|
|
||||||
const appUser = fixtures.users.generateUser()
|
|
||||||
expect(appUser[0].builder?.global).toEqual(false)
|
|
||||||
expect(appUser[0].admin?.global).toEqual(false)
|
|
||||||
const [userResponse, userJson] = await config.api.users.addMultiple(appUser)
|
|
||||||
const [userInfoResponse, userInfoJson] = await config.api.users.getInfo(
|
|
||||||
userJson.created.successful[0]._id
|
|
||||||
)
|
|
||||||
const body: User = {
|
|
||||||
...userInfoJson,
|
|
||||||
password: "newPassword",
|
|
||||||
}
|
|
||||||
await config.api.users.forcePasswordReset(body)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("Change User information", async () => {
|
|
||||||
const appUser = fixtures.users.generateUser()
|
|
||||||
expect(appUser[0].builder?.global).toEqual(false)
|
|
||||||
expect(appUser[0].admin?.global).toEqual(false)
|
|
||||||
const [userResponse, userJson] = await config.api.users.addMultiple(appUser)
|
|
||||||
const [userInfoResponse, userInfoJson] = await config.api.users.getInfo(
|
|
||||||
userJson.created.successful[0]._id
|
|
||||||
)
|
|
||||||
const body: User = {
|
|
||||||
...userInfoJson,
|
|
||||||
firstName: "newFirstName",
|
|
||||||
lastName: "newLastName",
|
|
||||||
builder: {
|
|
||||||
global: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
await config.api.users.updateInfo(body)
|
|
||||||
|
|
||||||
const [changedUserInfoResponse, changedUserInfoJson] =
|
|
||||||
await config.api.users.getInfo(userJson.created.successful[0]._id)
|
|
||||||
expect(changedUserInfoJson.builder?.global).toBeDefined()
|
|
||||||
expect(changedUserInfoJson.builder?.global).toEqual(true)
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,97 +0,0 @@
|
||||||
import { db as dbCore } from "@budibase/backend-core"
|
|
||||||
import { TestConfiguration } from "../../config"
|
|
||||||
import { Application } from "../../../types"
|
|
||||||
import * as fixtures from "../../fixtures"
|
|
||||||
|
|
||||||
describe("Public API - /applications endpoints", () => {
|
|
||||||
const config = new TestConfiguration<Application>()
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
await config.beforeAll()
|
|
||||||
await config.createApp()
|
|
||||||
config.context = (await config.api.apps.read(config.state.appId!))[1]
|
|
||||||
})
|
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await config.afterAll()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("POST - Create an application", async () => {
|
|
||||||
const [response, app] = await config.api.apps.create(
|
|
||||||
fixtures.apps.generateApp()
|
|
||||||
)
|
|
||||||
expect(response).toHaveStatusCode(200)
|
|
||||||
expect(app._id).toBeDefined()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("POST - Search applications", async () => {
|
|
||||||
const [response, apps] = await config.api.apps.search({
|
|
||||||
name: config.context.name,
|
|
||||||
})
|
|
||||||
expect(response).toHaveStatusCode(200)
|
|
||||||
expect(apps[0]).toEqual(config.context)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("GET - Retrieve an application", async () => {
|
|
||||||
const [response, app] = await config.api.apps.read(config.context._id)
|
|
||||||
expect(response).toHaveStatusCode(200)
|
|
||||||
expect(app).toEqual(config.context)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("PUT - update an application", async () => {
|
|
||||||
config.context.name = "UpdatedName"
|
|
||||||
const [response, app] = await config.api.apps.update(
|
|
||||||
config.context._id,
|
|
||||||
config.context
|
|
||||||
)
|
|
||||||
expect(response).toHaveStatusCode(200)
|
|
||||||
expect(app.updatedAt).not.toEqual(config.context.updatedAt)
|
|
||||||
expect(app.name).toEqual(config.context.name)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("POST - publish an application", async () => {
|
|
||||||
config.context.name = "UpdatedName"
|
|
||||||
const [response, deployment] = await config.api.apps.publish(
|
|
||||||
config.context._id
|
|
||||||
)
|
|
||||||
expect(response).toHaveStatusCode(200)
|
|
||||||
expect(deployment).toEqual({
|
|
||||||
status: "SUCCESS",
|
|
||||||
})
|
|
||||||
|
|
||||||
// Verify publish
|
|
||||||
const prodAppId = dbCore.getProdAppID(config.context._id)
|
|
||||||
const [_, publishedApp] = await config.api.apps.read(prodAppId)
|
|
||||||
expect(response).toHaveStatusCode(200)
|
|
||||||
expect(publishedApp._id).toEqual(prodAppId)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("POST - unpublish a published application", async () => {
|
|
||||||
await config.api.apps.publish(config.context._id)
|
|
||||||
const [response] = await config.api.apps.unpublish(config.context._id)
|
|
||||||
expect(response).toHaveStatusCode(204)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("POST - unpublish an unpublished application", async () => {
|
|
||||||
const [response] = await config.api.apps.unpublish(config.context._id)
|
|
||||||
expect(response).toHaveStatusCode(400)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("DELETE - delete a published application and the dev application", async () => {
|
|
||||||
await config.api.apps.publish(config.context._id)
|
|
||||||
const [response, deletion] = await config.api.apps.delete(
|
|
||||||
config.context._id
|
|
||||||
)
|
|
||||||
expect(response).toHaveStatusCode(200)
|
|
||||||
expect(deletion._id).toEqual(config.context._id)
|
|
||||||
|
|
||||||
// verify dev app deleted
|
|
||||||
const [devAppResponse] = await config.api.apps.read(config.context._id)
|
|
||||||
expect(devAppResponse).toHaveStatusCode(404)
|
|
||||||
|
|
||||||
// verify prod app deleted
|
|
||||||
const prodAppId = dbCore.getProdAppID(config.context._id)
|
|
||||||
const [publishedAppResponse] = await config.api.apps.read(prodAppId)
|
|
||||||
expect(publishedAppResponse).toHaveStatusCode(404)
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,62 +0,0 @@
|
||||||
import { TestConfiguration } from "../../config"
|
|
||||||
import * as fixtures from "../../fixtures"
|
|
||||||
import { Row } from "../../../types"
|
|
||||||
|
|
||||||
describe("Public API - /rows endpoints", () => {
|
|
||||||
const config = new TestConfiguration<Row>()
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
await config.beforeAll()
|
|
||||||
await config.createApp()
|
|
||||||
|
|
||||||
const [tResp, table] = await config.api.tables.seed()
|
|
||||||
config.state.tableId = table._id
|
|
||||||
|
|
||||||
const [rResp, row] = await config.api.rows.seed(table._id)
|
|
||||||
config.context = row
|
|
||||||
})
|
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await config.afterAll()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("POST - Create a row", async () => {
|
|
||||||
const [response, row] = await config.api.rows.create(
|
|
||||||
fixtures.rows.generateRow()
|
|
||||||
)
|
|
||||||
expect(response).toHaveStatusCode(200)
|
|
||||||
expect(row._id).toBeDefined()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("POST - Search rows", async () => {
|
|
||||||
const [response, rows] = await config.api.rows.search({
|
|
||||||
query: {
|
|
||||||
string: {
|
|
||||||
testColumn: config.context.testColumn as string,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
expect(response).toHaveStatusCode(200)
|
|
||||||
expect(rows.length).toEqual(1)
|
|
||||||
expect(rows[0]._id).toEqual(config.context._id)
|
|
||||||
expect(rows[0].tableId).toEqual(config.context.tableId)
|
|
||||||
expect(rows[0].testColumn).toEqual(config.context.testColumn)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("GET - Retrieve a row", async () => {
|
|
||||||
const [response, row] = await config.api.rows.read(config.context._id)
|
|
||||||
expect(response).toHaveStatusCode(200)
|
|
||||||
expect(row._id).toEqual(config.context._id)
|
|
||||||
expect(row.tableId).toEqual(config.context.tableId)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("PUT - update a row", async () => {
|
|
||||||
config.context.testColumn = "UpdatedName"
|
|
||||||
const [response, row] = await config.api.rows.update(
|
|
||||||
config.context._id,
|
|
||||||
config.context
|
|
||||||
)
|
|
||||||
expect(response).toHaveStatusCode(200)
|
|
||||||
expect(row.testColumn).toEqual(config.context.testColumn)
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,51 +0,0 @@
|
||||||
import { TestConfiguration } from "../../config"
|
|
||||||
import * as fixtures from "../../fixtures"
|
|
||||||
import { Table } from "../../../types"
|
|
||||||
|
|
||||||
describe("Public API - /tables endpoints", () => {
|
|
||||||
const config = new TestConfiguration<Table>()
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
await config.beforeAll()
|
|
||||||
await config.createApp()
|
|
||||||
|
|
||||||
const [tableResp, table] = await config.api.tables.seed()
|
|
||||||
config.context = table
|
|
||||||
})
|
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await config.afterAll()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("POST - Create a table", async () => {
|
|
||||||
const [response, table] = await config.api.tables.create(
|
|
||||||
fixtures.tables.generateTable()
|
|
||||||
)
|
|
||||||
expect(response).toHaveStatusCode(200)
|
|
||||||
expect(table._id).toBeDefined()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("POST - Search tables", async () => {
|
|
||||||
const [response, tables] = await config.api.tables.search({
|
|
||||||
name: config.context.name,
|
|
||||||
})
|
|
||||||
expect(response).toHaveStatusCode(200)
|
|
||||||
expect(tables[0]).toEqual(config.context)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("GET - Retrieve a table", async () => {
|
|
||||||
const [response, table] = await config.api.tables.read(config.context._id)
|
|
||||||
expect(response).toHaveStatusCode(200)
|
|
||||||
expect(table).toEqual(config.context)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("PUT - update a table", async () => {
|
|
||||||
config.context.name = "updatedName"
|
|
||||||
const [response, table] = await config.api.tables.update(
|
|
||||||
config.context._id,
|
|
||||||
config.context
|
|
||||||
)
|
|
||||||
expect(response).toHaveStatusCode(200)
|
|
||||||
expect(table).toEqual(config.context)
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,49 +0,0 @@
|
||||||
import TestConfiguration from "../../config/TestConfiguration"
|
|
||||||
import * as fixtures from "../../fixtures"
|
|
||||||
import { User } from "../../../types"
|
|
||||||
|
|
||||||
describe("Public API - /users endpoints", () => {
|
|
||||||
const config = new TestConfiguration<User>()
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
await config.beforeAll()
|
|
||||||
const [_, user] = await config.api.users.seed()
|
|
||||||
config.context = user
|
|
||||||
})
|
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await config.afterAll()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("POST - Create a user", async () => {
|
|
||||||
const [response, user] = await config.api.users.create(
|
|
||||||
fixtures.users.generateUser()
|
|
||||||
)
|
|
||||||
expect(response).toHaveStatusCode(200)
|
|
||||||
expect(user._id).toBeDefined()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("POST - Search users", async () => {
|
|
||||||
const [response, users] = await config.api.users.search({
|
|
||||||
name: config.context.email,
|
|
||||||
})
|
|
||||||
expect(response).toHaveStatusCode(200)
|
|
||||||
expect(users[0]).toEqual(config.context)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("GET - Retrieve a user", async () => {
|
|
||||||
const [response, user] = await config.api.users.read(config.context._id)
|
|
||||||
expect(response).toHaveStatusCode(200)
|
|
||||||
expect(user).toEqual(config.context)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("PUT - update a user", async () => {
|
|
||||||
config.context.firstName = "Updated First Name"
|
|
||||||
const [response, user] = await config.api.users.update(
|
|
||||||
config.context._id,
|
|
||||||
config.context
|
|
||||||
)
|
|
||||||
expect(response).toHaveStatusCode(200)
|
|
||||||
expect(user).toEqual(config.context)
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -3,7 +3,7 @@
|
||||||
const start = Date.now()
|
const start = Date.now()
|
||||||
|
|
||||||
const fs = require("fs")
|
const fs = require("fs")
|
||||||
const { readdir, copyFile, mkdir } = require('node:fs/promises');
|
const { cp, readdir, copyFile, mkdir } = require('node:fs/promises');
|
||||||
const path = require("path")
|
const path = require("path")
|
||||||
|
|
||||||
const { build } = require("esbuild")
|
const { build } = require("esbuild")
|
||||||
|
@ -17,12 +17,6 @@ const { nodeExternalsPlugin } = require("esbuild-node-externals")
|
||||||
const svelteCompilePlugin = {
|
const svelteCompilePlugin = {
|
||||||
name: 'svelteCompile',
|
name: 'svelteCompile',
|
||||||
setup(build) {
|
setup(build) {
|
||||||
// This resolve handler is necessary to bundle the Svelte runtime into the the final output,
|
|
||||||
// otherwise the bundled script will attempt to resolve it at runtime
|
|
||||||
build.onResolve({ filter: /svelte\/internal/ }, async () => {
|
|
||||||
return { path: `${process.cwd()}/../../node_modules/svelte/src/runtime/internal/ssr.js` }
|
|
||||||
})
|
|
||||||
|
|
||||||
// Compiles `.svelte` files into JS classes so that they can be directly imported into our
|
// Compiles `.svelte` files into JS classes so that they can be directly imported into our
|
||||||
// Typescript packages
|
// Typescript packages
|
||||||
build.onLoad({ filter: /\.svelte$/ }, async (args) => {
|
build.onLoad({ filter: /\.svelte$/ }, async (args) => {
|
||||||
|
@ -37,7 +31,7 @@ const svelteCompilePlugin = {
|
||||||
contents: js.code,
|
contents: js.code,
|
||||||
// The loader this is passed to, basically how the above provided content is "treated",
|
// The loader this is passed to, basically how the above provided content is "treated",
|
||||||
// the contents provided above will be transpiled and bundled like any other JS file.
|
// the contents provided above will be transpiled and bundled like any other JS file.
|
||||||
loader: 'js',
|
loader: 'js',
|
||||||
// Where to resolve any imports present in the loaded file
|
// Where to resolve any imports present in the loaded file
|
||||||
resolveDir: dir
|
resolveDir: dir
|
||||||
}
|
}
|
||||||
|
@ -80,11 +74,11 @@ async function runBuild(entry, outfile) {
|
||||||
plugins: [
|
plugins: [
|
||||||
svelteCompilePlugin,
|
svelteCompilePlugin,
|
||||||
TsconfigPathsPlugin({ tsconfig: tsconfigPathPluginContent }),
|
TsconfigPathsPlugin({ tsconfig: tsconfigPathPluginContent }),
|
||||||
nodeExternalsPlugin(),
|
nodeExternalsPlugin({
|
||||||
|
allowList: ["@budibase/frontend-core", "svelte"]
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
preserveSymlinks: true,
|
preserveSymlinks: true,
|
||||||
loader: {
|
|
||||||
},
|
|
||||||
metafile: true,
|
metafile: true,
|
||||||
external: [
|
external: [
|
||||||
"deasync",
|
"deasync",
|
||||||
|
@ -109,13 +103,23 @@ async function runBuild(entry, outfile) {
|
||||||
await Promise.all(fileCopyPromises)
|
await Promise.all(fileCopyPromises)
|
||||||
})()
|
})()
|
||||||
|
|
||||||
|
const oldClientVersions = (async () => {
|
||||||
|
try {
|
||||||
|
await cp('./build/oldClientVersions', './dist/oldClientVersions', { recursive: true });
|
||||||
|
} catch (e) {
|
||||||
|
if (e.code !== "EEXIST" && e.code !== "ENOENT") {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
const mainBuild = build({
|
const mainBuild = build({
|
||||||
...sharedConfig,
|
...sharedConfig,
|
||||||
platform: "node",
|
platform: "node",
|
||||||
outfile,
|
outfile,
|
||||||
})
|
})
|
||||||
|
|
||||||
await Promise.all([hbsFiles, mainBuild])
|
await Promise.all([hbsFiles, mainBuild, oldClientVersions])
|
||||||
|
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
`dist/${path.basename(outfile)}.meta.json`,
|
`dist/${path.basename(outfile)}.meta.json`,
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
const fs = require('node:fs/promises');
|
||||||
|
const util = require('node:util');
|
||||||
|
const { argv } = require('node:process');
|
||||||
|
const exec = util.promisify(require('node:child_process').exec);
|
||||||
|
|
||||||
|
const version = argv[2]
|
||||||
|
|
||||||
|
const getPastClientVersion = async () => {
|
||||||
|
const manifestRaw = await fetch(`https://api.github.com/repos/budibase/budibase/contents/packages/client/manifest.json?ref=v${version}`).then(r => r.json());
|
||||||
|
|
||||||
|
// GitHub response will contain a message field containing the following string if the version can't be found.
|
||||||
|
if (manifestRaw?.message?.includes("No commit found")) {
|
||||||
|
throw `Can't find a GitHub tag with version "${version}"`
|
||||||
|
}
|
||||||
|
|
||||||
|
const manifest = Buffer.from(manifestRaw.content, 'base64').toString('utf8')
|
||||||
|
|
||||||
|
await fs.mkdir(`packages/server/build/oldClientVersions/${version}`, { recursive: true });
|
||||||
|
await fs.writeFile(`packages/server/build/oldClientVersions/${version}/manifest.json`, manifest)
|
||||||
|
|
||||||
|
const npmRegistry = await fetch(`https://registry.npmjs.org/@budibase/client/${version}`).then(r => r.json());
|
||||||
|
|
||||||
|
// The json response from npm is just a string starting with the following if the version can't be found
|
||||||
|
if (typeof npmRegistry === "string" && npmRegistry.startsWith("version not found")) {
|
||||||
|
throw `Can't find @budibase/client with version "${version}" in npm registry`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a temp directory to store the @budibase/client library in
|
||||||
|
await fs.mkdir("clientVersionTmp", { recursive: true });
|
||||||
|
|
||||||
|
// Get the tarball of the @budibase/client library and pipe it into tar to extract it
|
||||||
|
await exec(`curl -L ${npmRegistry.dist.tarball} --output - | tar -xvzf - -C clientVersionTmp`);
|
||||||
|
|
||||||
|
// Copy the compiled client version we want to the oldClientVersions dir and delete the temp directory
|
||||||
|
await fs.copyFile('./clientVersionTmp/package/dist/budibase-client.js', `./packages/server/build/oldClientVersions/${version}/app.js`);
|
||||||
|
await fs.rm("clientVersionTmp", { recursive: true });
|
||||||
|
|
||||||
|
// Check what client versions the user has locally and update the `clientVersions.json` file in the builder so that they can be selected
|
||||||
|
const rootDir = await fs.readdir('packages/server/build/oldClientVersions/', { withFileTypes: true });
|
||||||
|
const dirs = rootDir.filter(entry => entry.isDirectory()).map(dir => dir.name);
|
||||||
|
|
||||||
|
await fs.writeFile("packages/builder/src/components/deploy/clientVersions.json", JSON.stringify(dirs))
|
||||||
|
}
|
||||||
|
|
||||||
|
getPastClientVersion().catch(e => console.error(e));
|
13
yarn.lock
13
yarn.lock
|
@ -5098,7 +5098,7 @@
|
||||||
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf"
|
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf"
|
||||||
integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==
|
integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==
|
||||||
|
|
||||||
"@trendyol/jest-testcontainers@2.1.1", "@trendyol/jest-testcontainers@^2.1.1":
|
"@trendyol/jest-testcontainers@2.1.1":
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/@trendyol/jest-testcontainers/-/jest-testcontainers-2.1.1.tgz#dced95cf9c37b75efe0a65db9b75ae8912f2f14a"
|
resolved "https://registry.yarnpkg.com/@trendyol/jest-testcontainers/-/jest-testcontainers-2.1.1.tgz#dced95cf9c37b75efe0a65db9b75ae8912f2f14a"
|
||||||
integrity sha512-4iAc2pMsev4BTUzoA7jO1VvbTOU2N3juQUYa8TwiSPXPuQtxKwV9WB9ZEP+JQ+Pj15YqfGOXp5H0WNMPtapjiA==
|
integrity sha512-4iAc2pMsev4BTUzoA7jO1VvbTOU2N3juQUYa8TwiSPXPuQtxKwV9WB9ZEP+JQ+Pj15YqfGOXp5H0WNMPtapjiA==
|
||||||
|
@ -9793,16 +9793,7 @@ docker-modem@^3.0.0:
|
||||||
split-ca "^1.0.1"
|
split-ca "^1.0.1"
|
||||||
ssh2 "^1.11.0"
|
ssh2 "^1.11.0"
|
||||||
|
|
||||||
dockerode@^3.2.1:
|
dockerode@^3.2.1, dockerode@^3.3.5:
|
||||||
version "3.3.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/dockerode/-/dockerode-3.3.4.tgz#875de614a1be797279caa9fe27e5637cf0e40548"
|
|
||||||
integrity sha512-3EUwuXnCU+RUlQEheDjmBE0B7q66PV9Rw5NiH1sXwINq0M9c5ERP9fxgkw36ZHOtzf4AGEEYySnkx/sACC9EgQ==
|
|
||||||
dependencies:
|
|
||||||
"@balena/dockerignore" "^1.0.2"
|
|
||||||
docker-modem "^3.0.0"
|
|
||||||
tar-fs "~2.0.1"
|
|
||||||
|
|
||||||
dockerode@^3.3.5:
|
|
||||||
version "3.3.5"
|
version "3.3.5"
|
||||||
resolved "https://registry.yarnpkg.com/dockerode/-/dockerode-3.3.5.tgz#7ae3f40f2bec53ae5e9a741ce655fff459745629"
|
resolved "https://registry.yarnpkg.com/dockerode/-/dockerode-3.3.5.tgz#7ae3f40f2bec53ae5e9a741ce655fff459745629"
|
||||||
integrity sha512-/0YNa3ZDNeLr/tSckmD69+Gq+qVNhvKfAHNeZJBnp7EOP6RGKV8ORrJHkUn20So5wU+xxT7+1n5u8PjHbfjbSA==
|
integrity sha512-/0YNa3ZDNeLr/tSckmD69+Gq+qVNhvKfAHNeZJBnp7EOP6RGKV8ORrJHkUn20So5wU+xxT7+1n5u8PjHbfjbSA==
|
||||||
|
|
Loading…
Reference in New Issue