Merge branch 'develop' into cheeks-lab-day-portal-poc
This commit is contained in:
commit
38cad518a4
|
@ -12,7 +12,8 @@ jobs:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: passeidireto/trigger-external-workflow-action@main
|
- uses: passeidireto/trigger-external-workflow-action@main
|
||||||
env:
|
env:
|
||||||
BRANCH: ${{ github.head_ref }}
|
PAYLOAD_BRANCH: ${{ github.head_ref }}
|
||||||
|
PAYLOAD_PR_NUMBER: ${{ github.ref }}
|
||||||
with:
|
with:
|
||||||
repository: budibase/budibase-deploys
|
repository: budibase/budibase-deploys
|
||||||
event: featurebranch-qa-deploy
|
event: featurebranch-qa-deploy
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "2.10.12-alpha.7",
|
"version": "2.10.12-alpha.11",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
"@esbuild-plugins/tsconfig-paths": "^0.1.2",
|
"@esbuild-plugins/tsconfig-paths": "^0.1.2",
|
||||||
"@nx/js": "16.4.3",
|
"@nx/js": "16.4.3",
|
||||||
"@rollup/plugin-json": "^4.0.2",
|
"@rollup/plugin-json": "^4.0.2",
|
||||||
"@typescript-eslint/parser": "5.45.0",
|
"@typescript-eslint/parser": "6.7.2",
|
||||||
"esbuild": "^0.18.17",
|
"esbuild": "^0.18.17",
|
||||||
"esbuild-node-externals": "^1.8.0",
|
"esbuild-node-externals": "^1.8.0",
|
||||||
"eslint": "^8.44.0",
|
"eslint": "^8.44.0",
|
||||||
|
@ -22,7 +22,7 @@
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"rollup-plugin-replace": "^2.2.0",
|
"rollup-plugin-replace": "^2.2.0",
|
||||||
"svelte": "^3.38.2",
|
"svelte": "^3.38.2",
|
||||||
"typescript": "4.7.3",
|
"typescript": "5.2.2",
|
||||||
"@babel/core": "^7.22.5",
|
"@babel/core": "^7.22.5",
|
||||||
"@babel/eslint-parser": "^7.22.5",
|
"@babel/eslint-parser": "^7.22.5",
|
||||||
"@babel/preset-env": "^7.22.5",
|
"@babel/preset-env": "^7.22.5",
|
||||||
|
|
|
@ -87,7 +87,7 @@
|
||||||
"timekeeper": "2.2.0",
|
"timekeeper": "2.2.0",
|
||||||
"ts-node": "10.8.1",
|
"ts-node": "10.8.1",
|
||||||
"tsconfig-paths": "4.0.0",
|
"tsconfig-paths": "4.0.0",
|
||||||
"typescript": "4.7.3"
|
"typescript": "5.2.2"
|
||||||
},
|
},
|
||||||
"nx": {
|
"nx": {
|
||||||
"targets": {
|
"targets": {
|
||||||
|
|
|
@ -86,8 +86,8 @@ export const useAuditLogs = () => {
|
||||||
return useFeature(Feature.AUDIT_LOGS)
|
return useFeature(Feature.AUDIT_LOGS)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const usePublicApiUserRoles = () => {
|
export const useExpandedPublicApi = () => {
|
||||||
return useFeature(Feature.USER_ROLE_PUBLIC_API)
|
return useFeature(Feature.EXPANDED_PUBLIC_API)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useScimIntegration = () => {
|
export const useScimIntegration = () => {
|
||||||
|
|
|
@ -10,14 +10,13 @@ import { authDetails } from "./sso"
|
||||||
import { uuid } from "./common"
|
import { uuid } from "./common"
|
||||||
import { generator } from "./generator"
|
import { generator } from "./generator"
|
||||||
import { tenant } from "."
|
import { tenant } from "."
|
||||||
import { generateGlobalUserID } from "../../../../src/docIds"
|
|
||||||
|
|
||||||
export const newEmail = () => {
|
export const newEmail = () => {
|
||||||
return `${uuid()}@test.com`
|
return `${uuid()}@test.com`
|
||||||
}
|
}
|
||||||
|
|
||||||
export const user = (userProps?: Partial<Omit<User, "userId">>): User => {
|
export const user = (userProps?: Partial<Omit<User, "userId">>): User => {
|
||||||
const userId = userProps?._id || generateGlobalUserID()
|
const userId = userProps?._id
|
||||||
return {
|
return {
|
||||||
_id: userId,
|
_id: userId,
|
||||||
userId,
|
userId,
|
||||||
|
@ -53,7 +52,7 @@ export const adminOnlyUser = (userProps?: any): AdminOnlyUser => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const builderUser = (userProps?: any): BuilderUser => {
|
export const builderUser = (userProps?: Partial<User>): BuilderUser => {
|
||||||
return {
|
return {
|
||||||
...user(userProps),
|
...user(userProps),
|
||||||
builder: {
|
builder: {
|
||||||
|
|
|
@ -108,7 +108,7 @@
|
||||||
"svelte-jester": "^1.3.2",
|
"svelte-jester": "^1.3.2",
|
||||||
"ts-node": "10.8.1",
|
"ts-node": "10.8.1",
|
||||||
"tsconfig-paths": "4.0.0",
|
"tsconfig-paths": "4.0.0",
|
||||||
"typescript": "4.7.3",
|
"typescript": "5.2.2",
|
||||||
"vite": "^3.0.8",
|
"vite": "^3.0.8",
|
||||||
"vite-plugin-static-copy": "^0.16.0",
|
"vite-plugin-static-copy": "^0.16.0",
|
||||||
"vitest": "^0.29.2"
|
"vitest": "^0.29.2"
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
ModalContent,
|
||||||
|
Toggle,
|
||||||
|
Input,
|
||||||
|
Layout,
|
||||||
|
Dropzone,
|
||||||
|
notifications,
|
||||||
|
Body,
|
||||||
|
} from "@budibase/bbui"
|
||||||
|
import { API } from "api"
|
||||||
|
import { automationStore, store } from "../../builderStore"
|
||||||
|
|
||||||
|
export let app
|
||||||
|
|
||||||
|
$: disabled = (encrypted && !password) || !file
|
||||||
|
let encrypted = false,
|
||||||
|
password
|
||||||
|
let file
|
||||||
|
|
||||||
|
async function updateApp() {
|
||||||
|
try {
|
||||||
|
let data = new FormData()
|
||||||
|
data.append("appExport", file)
|
||||||
|
if (encrypted) {
|
||||||
|
data.append("encryptionPassword", password.trim())
|
||||||
|
}
|
||||||
|
const appId = app.devId
|
||||||
|
await API.updateAppFromExport(appId, data)
|
||||||
|
const pkg = await API.fetchAppPackage(appId)
|
||||||
|
await store.actions.initialise(pkg)
|
||||||
|
await automationStore.actions.fetch()
|
||||||
|
notifications.success("App updated successfully")
|
||||||
|
} catch (err) {
|
||||||
|
notifications.error(`Failed to update app - ${err.message || err}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ModalContent
|
||||||
|
title={`Update ${app.name}`}
|
||||||
|
confirmText="Update"
|
||||||
|
onConfirm={updateApp}
|
||||||
|
bind:disabled
|
||||||
|
>
|
||||||
|
<Body size="S"
|
||||||
|
>Updating an app using an app export will replace all tables, datasources,
|
||||||
|
queries, screens and automations. It is recommended to perform a backup
|
||||||
|
before running this operation.</Body
|
||||||
|
>
|
||||||
|
<Layout noPadding gap="XS">
|
||||||
|
<Dropzone
|
||||||
|
gallery={false}
|
||||||
|
label="App export"
|
||||||
|
on:change={e => {
|
||||||
|
file = e.detail?.[0]
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Toggle text="Encrypted" bind:value={encrypted} />
|
||||||
|
{#if encrypted}
|
||||||
|
<Input
|
||||||
|
type="password"
|
||||||
|
label="Password"
|
||||||
|
placeholder="Type here..."
|
||||||
|
bind:value={password}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</Layout>
|
||||||
|
</ModalContent>
|
|
@ -32,9 +32,9 @@
|
||||||
active={$isActive("./embed")}
|
active={$isActive("./embed")}
|
||||||
/>
|
/>
|
||||||
<SideNavItem
|
<SideNavItem
|
||||||
text="Export"
|
text="Export/Import"
|
||||||
url={$url("./export")}
|
url={$url("./exportImport")}
|
||||||
active={$isActive("./export")}
|
active={$isActive("./exportImport")}
|
||||||
/>
|
/>
|
||||||
<SideNavItem
|
<SideNavItem
|
||||||
text="Name and URL"
|
text="Name and URL"
|
||||||
|
|
|
@ -11,31 +11,39 @@
|
||||||
import { apps } from "stores/portal"
|
import { apps } from "stores/portal"
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import ExportAppModal from "components/start/ExportAppModal.svelte"
|
import ExportAppModal from "components/start/ExportAppModal.svelte"
|
||||||
|
import ImportAppModal from "components/start/ImportAppModal.svelte"
|
||||||
|
|
||||||
$: filteredApps = $apps.filter(app => app.devId == $store.appId)
|
$: filteredApps = $apps.filter(app => app.devId == $store.appId)
|
||||||
$: app = filteredApps.length ? filteredApps[0] : {}
|
$: app = filteredApps.length ? filteredApps[0] : {}
|
||||||
$: appDeployed = app?.status === AppStatus.DEPLOYED
|
$: appDeployed = app?.status === AppStatus.DEPLOYED
|
||||||
|
|
||||||
let exportModal
|
let exportModal, importModal
|
||||||
let exportPublishedVersion = false
|
let exportPublishedVersion = false
|
||||||
|
|
||||||
const exportApp = opts => {
|
const exportApp = opts => {
|
||||||
exportPublishedVersion = !!opts?.published
|
exportPublishedVersion = !!opts?.published
|
||||||
exportModal.show()
|
exportModal.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const importApp = () => {
|
||||||
|
importModal.show()
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal bind:this={exportModal} padding={false}>
|
<Modal bind:this={exportModal} padding={false}>
|
||||||
<ExportAppModal {app} published={exportPublishedVersion} />
|
<ExportAppModal {app} published={exportPublishedVersion} />
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
<Modal bind:this={importModal} padding={false}>
|
||||||
|
<ImportAppModal {app} />
|
||||||
|
</Modal>
|
||||||
|
|
||||||
<Layout noPadding>
|
<Layout noPadding>
|
||||||
<Layout gap="XS" noPadding>
|
<Layout gap="XS" noPadding>
|
||||||
<Heading>Export your app</Heading>
|
<Heading>Export your app</Heading>
|
||||||
<Body>Export your latest edited or published app</Body>
|
<Body>Export your latest edited or published app</Body>
|
||||||
</Layout>
|
</Layout>
|
||||||
<Divider />
|
<div class="body">
|
||||||
<div class="export-body">
|
|
||||||
<ActionButton secondary on:click={() => exportApp({ published: false })}>
|
<ActionButton secondary on:click={() => exportApp({ published: false })}>
|
||||||
Export latest edited app
|
Export latest edited app
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
|
@ -47,10 +55,20 @@
|
||||||
Export latest published app
|
Export latest published app
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</div>
|
</div>
|
||||||
|
<Divider />
|
||||||
|
<Layout gap="XS" noPadding>
|
||||||
|
<Heading>Import your app</Heading>
|
||||||
|
<Body>Import an export to update this app</Body>
|
||||||
|
</Layout>
|
||||||
|
<div class="body">
|
||||||
|
<ActionButton secondary on:click={() => importApp()}>
|
||||||
|
Import app
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.export-body {
|
.body {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--spacing-l);
|
gap: var(--spacing-l);
|
||||||
}
|
}
|
|
@ -62,6 +62,6 @@
|
||||||
"eslint": "^7.20.0",
|
"eslint": "^7.20.0",
|
||||||
"renamer": "^4.0.0",
|
"renamer": "^4.0.0",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"typescript": "4.7.3"
|
"typescript": "5.2.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,8 +49,8 @@ async function askAboutTopLevel(name: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function init(opts: PluginOpts) {
|
async function init(opts: PluginOpts | PluginType) {
|
||||||
const type = opts["init"] || (opts as PluginType)
|
const type = (opts as PluginOpts).init || (opts as PluginType)
|
||||||
if (!type || !PLUGIN_TYPE_ARR.includes(type)) {
|
if (!type || !PLUGIN_TYPE_ARR.includes(type)) {
|
||||||
console.log(
|
console.log(
|
||||||
error(
|
error(
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { sdk } from "@budibase/shared-core"
|
||||||
|
|
||||||
export const buildAppEndpoints = API => ({
|
export const buildAppEndpoints = API => ({
|
||||||
/**
|
/**
|
||||||
* Fetches screen definition for an app.
|
* Fetches screen definition for an app.
|
||||||
|
@ -81,6 +83,22 @@ export const buildAppEndpoints = API => ({
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update an application using an export - the body
|
||||||
|
* should be of type FormData, with a "file" and a "password" if encrypted.
|
||||||
|
* @param appId The ID of the app to update - this will always be
|
||||||
|
* converted to development ID.
|
||||||
|
* @param body a FormData body with a file and password.
|
||||||
|
*/
|
||||||
|
updateAppFromExport: async (appId, body) => {
|
||||||
|
const devId = sdk.applications.getDevAppID(appId)
|
||||||
|
return await API.post({
|
||||||
|
url: `/api/applications/${devId}/import`,
|
||||||
|
body,
|
||||||
|
json: false,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Imports an export of all apps.
|
* Imports an export of all apps.
|
||||||
* @param apps the FormData containing the apps to import
|
* @param apps the FormData containing the apps to import
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 4638ae916e55ce89166095578cbd01745d0ee9ee
|
Subproject commit 30385682141e5ba9d98de7d71d5be1672109cd15
|
|
@ -153,7 +153,6 @@
|
||||||
"@types/server-destroy": "1.0.1",
|
"@types/server-destroy": "1.0.1",
|
||||||
"@types/supertest": "2.0.12",
|
"@types/supertest": "2.0.12",
|
||||||
"@types/tar": "6.1.5",
|
"@types/tar": "6.1.5",
|
||||||
"@typescript-eslint/parser": "5.45.0",
|
|
||||||
"apidoc": "0.50.4",
|
"apidoc": "0.50.4",
|
||||||
"babel-jest": "29.6.2",
|
"babel-jest": "29.6.2",
|
||||||
"copyfiles": "2.4.1",
|
"copyfiles": "2.4.1",
|
||||||
|
@ -174,7 +173,7 @@
|
||||||
"timekeeper": "2.2.0",
|
"timekeeper": "2.2.0",
|
||||||
"ts-node": "10.8.1",
|
"ts-node": "10.8.1",
|
||||||
"tsconfig-paths": "4.0.0",
|
"tsconfig-paths": "4.0.0",
|
||||||
"typescript": "4.7.3",
|
"typescript": "5.2.2",
|
||||||
"update-dotenv": "1.1.1"
|
"update-dotenv": "1.1.1"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
|
|
|
@ -9,6 +9,7 @@ CREATE TABLE Persons (
|
||||||
Address varchar(255),
|
Address varchar(255),
|
||||||
City varchar(255) DEFAULT 'Belfast',
|
City varchar(255) DEFAULT 'Belfast',
|
||||||
Age INTEGER DEFAULT 20 NOT NULL,
|
Age INTEGER DEFAULT 20 NOT NULL,
|
||||||
|
Year INTEGER,
|
||||||
Type person_job
|
Type person_job
|
||||||
);
|
);
|
||||||
CREATE TABLE Tasks (
|
CREATE TABLE Tasks (
|
||||||
|
@ -49,9 +50,10 @@ CREATE TABLE CompositeTable (
|
||||||
Name varchar(255),
|
Name varchar(255),
|
||||||
PRIMARY KEY (KeyPartOne, KeyPartTwo)
|
PRIMARY KEY (KeyPartOne, KeyPartTwo)
|
||||||
);
|
);
|
||||||
INSERT INTO Persons (FirstName, LastName, Address, City, Type) VALUES ('Mike', 'Hughes', '123 Fake Street', 'Belfast', 'qa');
|
INSERT INTO Persons (FirstName, LastName, Address, City, Type, Year) VALUES ('Mike', 'Hughes', '123 Fake Street', 'Belfast', 'qa', 1999);
|
||||||
INSERT INTO Persons (FirstName, LastName, Address, City, Type) VALUES ('John', 'Smith', '64 Updown Road', 'Dublin', 'programmer');
|
INSERT INTO Persons (FirstName, LastName, Address, City, Type, Year) VALUES ('John', 'Smith', '64 Updown Road', 'Dublin', 'programmer', 1996);
|
||||||
INSERT INTO Persons (FirstName, LastName, Address, City, Type, Age) VALUES ('Foo', 'Bar', 'Foo Street', 'Bartown', 'support', 0);
|
INSERT INTO Persons (FirstName, LastName, Address, City, Type, Age, Year) VALUES ('Foo', 'Bar', 'Foo Street', 'Bartown', 'support', 0, 1993);
|
||||||
|
INSERT INTO Persons (FirstName, LastName, Address, City, Type) VALUES ('Jonny', 'Muffin', 'Muffin Street', 'Cork', 'support');
|
||||||
INSERT INTO Tasks (ExecutorID, QaID, TaskName, Completed) VALUES (1, 2, 'assembling', TRUE);
|
INSERT INTO Tasks (ExecutorID, QaID, TaskName, Completed) VALUES (1, 2, 'assembling', TRUE);
|
||||||
INSERT INTO Tasks (ExecutorID, QaID, TaskName, Completed) VALUES (2, 1, 'processing', FALSE);
|
INSERT INTO Tasks (ExecutorID, QaID, TaskName, Completed) VALUES (2, 1, 'processing', FALSE);
|
||||||
INSERT INTO Products (ProductName) VALUES ('Computers');
|
INSERT INTO Products (ProductName) VALUES ('Computers');
|
||||||
|
|
|
@ -613,6 +613,23 @@
|
||||||
"data"
|
"data"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"appExport": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"encryptPassword": {
|
||||||
|
"description": "An optional password used to encrypt the export.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"excludeRows": {
|
||||||
|
"description": "Set whether the internal table rows should be excluded from the export.",
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"encryptPassword",
|
||||||
|
"excludeRows"
|
||||||
|
]
|
||||||
|
},
|
||||||
"row": {
|
"row": {
|
||||||
"description": "The row to be created/updated, based on the table schema.",
|
"description": "The row to be created/updated, based on the table schema.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
@ -2163,6 +2180,87 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/applications/{appId}/import": {
|
||||||
|
"post": {
|
||||||
|
"operationId": "appImport",
|
||||||
|
"summary": "Import an app to an existing app 🔒",
|
||||||
|
"description": "This endpoint is only available on a business or enterprise license.",
|
||||||
|
"tags": [
|
||||||
|
"applications"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/parameters/appIdUrl"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"multipart/form-data": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"encryptedPassword": {
|
||||||
|
"description": "Password for the export if it is encrypted.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"appExport": {
|
||||||
|
"description": "The app export to import.",
|
||||||
|
"type": "string",
|
||||||
|
"format": "binary"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"appExport"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"204": {
|
||||||
|
"description": "Application has been updated."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/applications/{appId}/export": {
|
||||||
|
"post": {
|
||||||
|
"operationId": "appExport",
|
||||||
|
"summary": "Export an app 🔒",
|
||||||
|
"description": "This endpoint is only available on a business or enterprise license.",
|
||||||
|
"tags": [
|
||||||
|
"applications"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/parameters/appIdUrl"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/appExport"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A gzip tarball containing the app export, encrypted if password provided.",
|
||||||
|
"content": {
|
||||||
|
"application/gzip": {
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "binary",
|
||||||
|
"example": "Tarball containing database and object store contents..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/applications/search": {
|
"/applications/search": {
|
||||||
"post": {
|
"post": {
|
||||||
"operationId": "appSearch",
|
"operationId": "appSearch",
|
||||||
|
|
|
@ -587,6 +587,19 @@ components:
|
||||||
- appUrl
|
- appUrl
|
||||||
required:
|
required:
|
||||||
- data
|
- data
|
||||||
|
appExport:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
encryptPassword:
|
||||||
|
description: An optional password used to encrypt the export.
|
||||||
|
type: string
|
||||||
|
excludeRows:
|
||||||
|
description: Set whether the internal table rows should be excluded from the
|
||||||
|
export.
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- encryptPassword
|
||||||
|
- excludeRows
|
||||||
row:
|
row:
|
||||||
description: The row to be created/updated, based on the table schema.
|
description: The row to be created/updated, based on the table schema.
|
||||||
type: object
|
type: object
|
||||||
|
@ -1763,6 +1776,57 @@ paths:
|
||||||
examples:
|
examples:
|
||||||
deployment:
|
deployment:
|
||||||
$ref: "#/components/examples/deploymentOutput"
|
$ref: "#/components/examples/deploymentOutput"
|
||||||
|
"/applications/{appId}/import":
|
||||||
|
post:
|
||||||
|
operationId: appImport
|
||||||
|
summary: Import an app to an existing app 🔒
|
||||||
|
description: This endpoint is only available on a business or enterprise license.
|
||||||
|
tags:
|
||||||
|
- applications
|
||||||
|
parameters:
|
||||||
|
- $ref: "#/components/parameters/appIdUrl"
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
multipart/form-data:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
encryptedPassword:
|
||||||
|
description: Password for the export if it is encrypted.
|
||||||
|
type: string
|
||||||
|
appExport:
|
||||||
|
description: The app export to import.
|
||||||
|
type: string
|
||||||
|
format: binary
|
||||||
|
required:
|
||||||
|
- appExport
|
||||||
|
responses:
|
||||||
|
"204":
|
||||||
|
description: Application has been updated.
|
||||||
|
"/applications/{appId}/export":
|
||||||
|
post:
|
||||||
|
operationId: appExport
|
||||||
|
summary: Export an app 🔒
|
||||||
|
description: This endpoint is only available on a business or enterprise license.
|
||||||
|
tags:
|
||||||
|
- applications
|
||||||
|
parameters:
|
||||||
|
- $ref: "#/components/parameters/appIdUrl"
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/appExport"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: A gzip tarball containing the app export, encrypted if password
|
||||||
|
provided.
|
||||||
|
content:
|
||||||
|
application/gzip:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: binary
|
||||||
|
example: Tarball containing database and object store contents...
|
||||||
/applications/search:
|
/applications/search:
|
||||||
post:
|
post:
|
||||||
operationId: appSearch
|
operationId: appSearch
|
||||||
|
|
|
@ -134,4 +134,15 @@ export default new Resource()
|
||||||
deploymentOutput: object({
|
deploymentOutput: object({
|
||||||
data: deploymentOutputSchema,
|
data: deploymentOutputSchema,
|
||||||
}),
|
}),
|
||||||
|
appExport: object({
|
||||||
|
encryptPassword: {
|
||||||
|
description: "An optional password used to encrypt the export.",
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
excludeRows: {
|
||||||
|
description:
|
||||||
|
"Set whether the internal table rows should be excluded from the export.",
|
||||||
|
type: "boolean",
|
||||||
|
},
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
|
|
|
@ -39,9 +39,8 @@ import {
|
||||||
} from "../../db/defaultData/datasource_bb_default"
|
} from "../../db/defaultData/datasource_bb_default"
|
||||||
import { removeAppFromUserRoles } from "../../utilities/workerRequests"
|
import { removeAppFromUserRoles } from "../../utilities/workerRequests"
|
||||||
import { stringToReadStream } from "../../utilities"
|
import { stringToReadStream } from "../../utilities"
|
||||||
import { doesUserHaveLock, getLocksById } from "../../utilities/redis"
|
import { doesUserHaveLock } from "../../utilities/redis"
|
||||||
import { cleanupAutomations } from "../../automations/utils"
|
import { cleanupAutomations } from "../../automations/utils"
|
||||||
import { checkAppMetadata } from "../../automations/logging"
|
|
||||||
import { getUniqueRows } from "../../utilities/usageQuota/rows"
|
import { getUniqueRows } from "../../utilities/usageQuota/rows"
|
||||||
import { groups, licensing, quotas } from "@budibase/pro"
|
import { groups, licensing, quotas } from "@budibase/pro"
|
||||||
import {
|
import {
|
||||||
|
@ -51,7 +50,6 @@ import {
|
||||||
PlanType,
|
PlanType,
|
||||||
Screen,
|
Screen,
|
||||||
UserCtx,
|
UserCtx,
|
||||||
ContextUser,
|
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { BASE_LAYOUT_PROP_IDS } from "../../constants/layouts"
|
import { BASE_LAYOUT_PROP_IDS } from "../../constants/layouts"
|
||||||
import sdk from "../../sdk"
|
import sdk from "../../sdk"
|
||||||
|
@ -575,6 +573,28 @@ export async function sync(ctx: UserCtx) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function importToApp(ctx: UserCtx) {
|
||||||
|
const { appId } = ctx.params
|
||||||
|
const appExport = ctx.request.files?.appExport
|
||||||
|
const password = ctx.request.body.encryptionPassword as string
|
||||||
|
if (!appExport) {
|
||||||
|
ctx.throw(400, "Must supply app export to import")
|
||||||
|
}
|
||||||
|
if (Array.isArray(appExport)) {
|
||||||
|
ctx.throw(400, "Must only supply one app export")
|
||||||
|
}
|
||||||
|
const fileAttributes = { type: appExport.type!, path: appExport.path! }
|
||||||
|
try {
|
||||||
|
await sdk.applications.updateWithExport(appId, fileAttributes, password)
|
||||||
|
} catch (err: any) {
|
||||||
|
ctx.throw(
|
||||||
|
500,
|
||||||
|
`Unable to perform update, please retry - ${err?.message || err}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ctx.body = { message: "app updated" }
|
||||||
|
}
|
||||||
|
|
||||||
export async function updateAppPackage(appPackage: any, appId: any) {
|
export async function updateAppPackage(appPackage: any, appId: any) {
|
||||||
return context.doInAppContext(appId, async () => {
|
return context.doInAppContext(appId, async () => {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
|
|
|
@ -2,9 +2,11 @@ import { db as dbCore, context } from "@budibase/backend-core"
|
||||||
import { search as stringSearch, addRev } from "./utils"
|
import { search as stringSearch, addRev } from "./utils"
|
||||||
import * as controller from "../application"
|
import * as controller from "../application"
|
||||||
import * as deployController from "../deploy"
|
import * as deployController from "../deploy"
|
||||||
|
import * as backupController from "../backup"
|
||||||
import { Application } from "../../../definitions/common"
|
import { Application } from "../../../definitions/common"
|
||||||
import { UserCtx } from "@budibase/types"
|
import { UserCtx } from "@budibase/types"
|
||||||
import { Next } from "koa"
|
import { Next } from "koa"
|
||||||
|
import { sdk as proSdk } from "@budibase/pro"
|
||||||
|
|
||||||
function fixAppID(app: Application, params: any) {
|
function fixAppID(app: Application, params: any) {
|
||||||
if (!params) {
|
if (!params) {
|
||||||
|
@ -80,6 +82,8 @@ export async function destroy(ctx: UserCtx, next: Next) {
|
||||||
export async function unpublish(ctx: UserCtx, next: Next) {
|
export async function unpublish(ctx: UserCtx, next: Next) {
|
||||||
await context.doInAppContext(ctx.params.appId, async () => {
|
await context.doInAppContext(ctx.params.appId, async () => {
|
||||||
await controller.unpublish(ctx)
|
await controller.unpublish(ctx)
|
||||||
|
ctx.body = undefined
|
||||||
|
ctx.status = 204
|
||||||
await next()
|
await next()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -91,12 +95,22 @@ export async function publish(ctx: UserCtx, next: Next) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get licensed endpoints from pro
|
||||||
|
export const importToApp = proSdk.publicApi.applications.buildImportFn(
|
||||||
|
controller.importToApp
|
||||||
|
)
|
||||||
|
export const exportApp = proSdk.publicApi.applications.buildExportFn(
|
||||||
|
backupController.exportAppDump
|
||||||
|
)
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
create,
|
create,
|
||||||
update,
|
update,
|
||||||
read,
|
read,
|
||||||
destroy,
|
destroy,
|
||||||
search,
|
search,
|
||||||
publish,
|
|
||||||
unpublish,
|
unpublish,
|
||||||
|
publish,
|
||||||
|
importToApp,
|
||||||
|
exportApp,
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import * as deploymentController from "../controllers/deploy"
|
||||||
import authorized from "../../middleware/authorized"
|
import authorized from "../../middleware/authorized"
|
||||||
import { permissions } from "@budibase/backend-core"
|
import { permissions } from "@budibase/backend-core"
|
||||||
import { applicationValidator } from "./utils/validators"
|
import { applicationValidator } from "./utils/validators"
|
||||||
|
import { importToApp } from "../controllers/application"
|
||||||
|
|
||||||
const router: Router = new Router()
|
const router: Router = new Router()
|
||||||
|
|
||||||
|
@ -58,5 +59,10 @@ router
|
||||||
authorized(permissions.GLOBAL_BUILDER),
|
authorized(permissions.GLOBAL_BUILDER),
|
||||||
controller.destroy
|
controller.destroy
|
||||||
)
|
)
|
||||||
|
.post(
|
||||||
|
"/api/applications/:appId/import",
|
||||||
|
authorized(permissions.BUILDER),
|
||||||
|
controller.importToApp
|
||||||
|
)
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
|
|
@ -137,6 +137,70 @@ write.push(
|
||||||
new Endpoint("post", "/applications/:appId/publish", controller.publish)
|
new Endpoint("post", "/applications/:appId/publish", controller.publish)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @openapi
|
||||||
|
* /applications/{appId}/import:
|
||||||
|
* post:
|
||||||
|
* operationId: appImport
|
||||||
|
* summary: Import an app to an existing app 🔒
|
||||||
|
* description: This endpoint is only available on a business or enterprise license.
|
||||||
|
* tags:
|
||||||
|
* - applications
|
||||||
|
* parameters:
|
||||||
|
* - $ref: '#/components/parameters/appIdUrl'
|
||||||
|
* requestBody:
|
||||||
|
* content:
|
||||||
|
* multipart/form-data:
|
||||||
|
* schema:
|
||||||
|
* type: object
|
||||||
|
* properties:
|
||||||
|
* encryptedPassword:
|
||||||
|
* description: Password for the export if it is encrypted.
|
||||||
|
* type: string
|
||||||
|
* appExport:
|
||||||
|
* description: The app export to import.
|
||||||
|
* type: string
|
||||||
|
* format: binary
|
||||||
|
* required:
|
||||||
|
* - appExport
|
||||||
|
* responses:
|
||||||
|
* 204:
|
||||||
|
* description: Application has been updated.
|
||||||
|
*/
|
||||||
|
write.push(
|
||||||
|
new Endpoint("post", "/applications/:appId/import", controller.importToApp)
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @openapi
|
||||||
|
* /applications/{appId}/export:
|
||||||
|
* post:
|
||||||
|
* operationId: appExport
|
||||||
|
* summary: Export an app 🔒
|
||||||
|
* description: This endpoint is only available on a business or enterprise license.
|
||||||
|
* tags:
|
||||||
|
* - applications
|
||||||
|
* parameters:
|
||||||
|
* - $ref: '#/components/parameters/appIdUrl'
|
||||||
|
* requestBody:
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/appExport'
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: A gzip tarball containing the app export, encrypted if password provided.
|
||||||
|
* content:
|
||||||
|
* application/gzip:
|
||||||
|
* schema:
|
||||||
|
* type: string
|
||||||
|
* format: binary
|
||||||
|
* example: Tarball containing database and object store contents...
|
||||||
|
*/
|
||||||
|
read.push(
|
||||||
|
new Endpoint("post", "/applications/:appId/export", controller.exportApp)
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @openapi
|
* @openapi
|
||||||
* /applications/{appId}:
|
* /applications/{appId}:
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { Ctx } from "@budibase/types"
|
||||||
import mapping from "../../../controllers/public/mapping"
|
import mapping from "../../../controllers/public/mapping"
|
||||||
|
|
||||||
enum Resources {
|
enum Resources {
|
||||||
|
@ -9,11 +10,19 @@ enum Resources {
|
||||||
SEARCH = "search",
|
SEARCH = "search",
|
||||||
}
|
}
|
||||||
|
|
||||||
function isArrayResponse(ctx: any) {
|
function isAttachment(ctx: Ctx) {
|
||||||
|
return ctx.body?.path && ctx.body?.flags && ctx.body?.mode
|
||||||
|
}
|
||||||
|
|
||||||
|
function isArrayResponse(ctx: Ctx) {
|
||||||
return ctx.url.endsWith(Resources.SEARCH) || Array.isArray(ctx.body)
|
return ctx.url.endsWith(Resources.SEARCH) || Array.isArray(ctx.body)
|
||||||
}
|
}
|
||||||
|
|
||||||
function processApplications(ctx: any) {
|
function noResponse(ctx: Ctx) {
|
||||||
|
return !Array.isArray(ctx.body) && Object.keys(ctx.body).length === 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function processApplications(ctx: Ctx) {
|
||||||
if (isArrayResponse(ctx)) {
|
if (isArrayResponse(ctx)) {
|
||||||
return mapping.mapApplications(ctx)
|
return mapping.mapApplications(ctx)
|
||||||
} else {
|
} else {
|
||||||
|
@ -21,7 +30,7 @@ function processApplications(ctx: any) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function processTables(ctx: any) {
|
function processTables(ctx: Ctx) {
|
||||||
if (isArrayResponse(ctx)) {
|
if (isArrayResponse(ctx)) {
|
||||||
return mapping.mapTables(ctx)
|
return mapping.mapTables(ctx)
|
||||||
} else {
|
} else {
|
||||||
|
@ -29,7 +38,7 @@ function processTables(ctx: any) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function processRows(ctx: any) {
|
function processRows(ctx: Ctx) {
|
||||||
if (isArrayResponse(ctx)) {
|
if (isArrayResponse(ctx)) {
|
||||||
return mapping.mapRowSearch(ctx)
|
return mapping.mapRowSearch(ctx)
|
||||||
} else {
|
} else {
|
||||||
|
@ -37,7 +46,7 @@ function processRows(ctx: any) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function processUsers(ctx: any) {
|
function processUsers(ctx: Ctx) {
|
||||||
if (isArrayResponse(ctx)) {
|
if (isArrayResponse(ctx)) {
|
||||||
return mapping.mapUsers(ctx)
|
return mapping.mapUsers(ctx)
|
||||||
} else {
|
} else {
|
||||||
|
@ -45,7 +54,7 @@ function processUsers(ctx: any) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function processQueries(ctx: any) {
|
function processQueries(ctx: Ctx) {
|
||||||
if (isArrayResponse(ctx)) {
|
if (isArrayResponse(ctx)) {
|
||||||
return mapping.mapQueries(ctx)
|
return mapping.mapQueries(ctx)
|
||||||
} else {
|
} else {
|
||||||
|
@ -53,8 +62,8 @@ function processQueries(ctx: any) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async (ctx: any, next: any) => {
|
export default async (ctx: Ctx, next: any) => {
|
||||||
if (!ctx.body) {
|
if (!ctx.body || noResponse(ctx) || isAttachment(ctx)) {
|
||||||
return await next()
|
return await next()
|
||||||
}
|
}
|
||||||
let urlParts = ctx.url.split("/")
|
let urlParts = ctx.url.split("/")
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
import * as setup from "../../tests/utilities"
|
||||||
|
import {
|
||||||
|
generateMakeRequest,
|
||||||
|
generateMakeRequestWithFormData,
|
||||||
|
MakeRequestResponse,
|
||||||
|
MakeRequestWithFormDataResponse,
|
||||||
|
} from "./utils"
|
||||||
|
import { User } from "@budibase/types"
|
||||||
|
import { join } from "path"
|
||||||
|
import { mocks } from "@budibase/backend-core/tests"
|
||||||
|
|
||||||
|
const PASSWORD = "testtest"
|
||||||
|
const NO_LICENSE_MSG = "Endpoint unavailable, license required."
|
||||||
|
|
||||||
|
let config = setup.getConfig()
|
||||||
|
let apiKey: string,
|
||||||
|
globalUser: User,
|
||||||
|
makeRequest: MakeRequestResponse,
|
||||||
|
makeRequestFormData: MakeRequestWithFormDataResponse
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await config.init()
|
||||||
|
globalUser = await config.globalUser()
|
||||||
|
apiKey = await config.generateApiKey(globalUser._id)
|
||||||
|
makeRequest = generateMakeRequest(apiKey)
|
||||||
|
makeRequestFormData = generateMakeRequestWithFormData(apiKey)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(setup.afterAll)
|
||||||
|
|
||||||
|
describe("check export/import", () => {
|
||||||
|
async function runExport() {
|
||||||
|
return await makeRequest("post", `/applications/${config.appId}/export`, {
|
||||||
|
encryptionPassword: PASSWORD,
|
||||||
|
excludeRows: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runImport() {
|
||||||
|
const pathToExport = join(
|
||||||
|
__dirname,
|
||||||
|
"..",
|
||||||
|
"..",
|
||||||
|
"tests",
|
||||||
|
"assets",
|
||||||
|
"export.tar.gz"
|
||||||
|
)
|
||||||
|
return await makeRequestFormData(
|
||||||
|
"post",
|
||||||
|
`/applications/${config.appId}/import`,
|
||||||
|
{
|
||||||
|
encryptionPassword: PASSWORD,
|
||||||
|
appExport: { path: pathToExport },
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
it("check licensing for export", async () => {
|
||||||
|
const res = await runExport()
|
||||||
|
expect(res.status).toBe(403)
|
||||||
|
expect(res.body.message).toBe(NO_LICENSE_MSG)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("check licensing for import", async () => {
|
||||||
|
const res = await runImport()
|
||||||
|
expect(res.status).toBe(403)
|
||||||
|
expect(res.body.message).toBe(NO_LICENSE_MSG)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should be able to export app", async () => {
|
||||||
|
mocks.licenses.useExpandedPublicApi()
|
||||||
|
const res = await runExport()
|
||||||
|
expect(res.headers["content-disposition"]).toMatch(
|
||||||
|
/attachment; filename=".*-export-.*\.tar.gz"/g
|
||||||
|
)
|
||||||
|
expect(res.body instanceof Buffer).toBe(true)
|
||||||
|
expect(res.status).toBe(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should be able to import app", async () => {
|
||||||
|
mocks.licenses.useExpandedPublicApi()
|
||||||
|
const res = await runImport()
|
||||||
|
expect(Object.keys(res.body).length).toBe(0)
|
||||||
|
// check screens imported correctly
|
||||||
|
const screens = await config.api.screen.list()
|
||||||
|
expect(screens.length).toBe(2)
|
||||||
|
expect(screens[0].routing.route).toBe("/derp")
|
||||||
|
expect(screens[1].routing.route).toBe("/blank")
|
||||||
|
expect(res.status).toBe(204)
|
||||||
|
})
|
||||||
|
})
|
|
@ -92,7 +92,7 @@ describe("no user role update in free", () => {
|
||||||
describe("no user role update in business", () => {
|
describe("no user role update in business", () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
updateMock()
|
updateMock()
|
||||||
mocks.licenses.usePublicApiUserRoles()
|
mocks.licenses.useExpandedPublicApi()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should allow 'roles' to be updated", async () => {
|
it("should allow 'roles' to be updated", async () => {
|
||||||
|
@ -105,7 +105,7 @@ describe("no user role update in business", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should allow 'admin' to be updated", async () => {
|
it("should allow 'admin' to be updated", async () => {
|
||||||
mocks.licenses.usePublicApiUserRoles()
|
mocks.licenses.useExpandedPublicApi()
|
||||||
const res = await makeRequest("post", "/users", {
|
const res = await makeRequest("post", "/users", {
|
||||||
...base(),
|
...base(),
|
||||||
admin: { global: true },
|
admin: { global: true },
|
||||||
|
@ -115,7 +115,7 @@ describe("no user role update in business", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should allow 'builder' to be updated", async () => {
|
it("should allow 'builder' to be updated", async () => {
|
||||||
mocks.licenses.usePublicApiUserRoles()
|
mocks.licenses.useExpandedPublicApi()
|
||||||
const res = await makeRequest("post", "/users", {
|
const res = await makeRequest("post", "/users", {
|
||||||
...base(),
|
...base(),
|
||||||
builder: { global: true },
|
builder: { global: true },
|
||||||
|
|
|
@ -11,6 +11,32 @@ export type MakeRequestResponse = (
|
||||||
intAppId?: string
|
intAppId?: string
|
||||||
) => Promise<supertest.Response>
|
) => Promise<supertest.Response>
|
||||||
|
|
||||||
|
export type MakeRequestWithFormDataResponse = (
|
||||||
|
method: HttpMethod,
|
||||||
|
endpoint: string,
|
||||||
|
fields: Record<string, string | { path: string }>,
|
||||||
|
intAppId?: string
|
||||||
|
) => Promise<supertest.Response>
|
||||||
|
|
||||||
|
function base(
|
||||||
|
apiKey: string,
|
||||||
|
endpoint: string,
|
||||||
|
intAppId: string | null,
|
||||||
|
isInternal: boolean
|
||||||
|
) {
|
||||||
|
const extraHeaders: any = {
|
||||||
|
"x-budibase-api-key": apiKey,
|
||||||
|
}
|
||||||
|
if (intAppId) {
|
||||||
|
extraHeaders["x-budibase-app-id"] = intAppId
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = isInternal
|
||||||
|
? endpoint
|
||||||
|
: checkSlashesInUrl(`/api/public/v1/${endpoint}`)
|
||||||
|
return { headers: extraHeaders, url }
|
||||||
|
}
|
||||||
|
|
||||||
export function generateMakeRequest(
|
export function generateMakeRequest(
|
||||||
apiKey: string,
|
apiKey: string,
|
||||||
isInternal = false
|
isInternal = false
|
||||||
|
@ -23,18 +49,8 @@ export function generateMakeRequest(
|
||||||
body?: any,
|
body?: any,
|
||||||
intAppId: string | null = config.getAppId()
|
intAppId: string | null = config.getAppId()
|
||||||
) => {
|
) => {
|
||||||
const extraHeaders: any = {
|
const { headers, url } = base(apiKey, endpoint, intAppId, isInternal)
|
||||||
"x-budibase-api-key": apiKey,
|
const req = request[method](url).set(config.defaultHeaders(headers))
|
||||||
}
|
|
||||||
if (intAppId) {
|
|
||||||
extraHeaders["x-budibase-app-id"] = intAppId
|
|
||||||
}
|
|
||||||
|
|
||||||
const url = isInternal
|
|
||||||
? endpoint
|
|
||||||
: checkSlashesInUrl(`/api/public/v1/${endpoint}`)
|
|
||||||
|
|
||||||
const req = request[method](url).set(config.defaultHeaders(extraHeaders))
|
|
||||||
if (body) {
|
if (body) {
|
||||||
req.send(body)
|
req.send(body)
|
||||||
}
|
}
|
||||||
|
@ -43,3 +59,30 @@ export function generateMakeRequest(
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function generateMakeRequestWithFormData(
|
||||||
|
apiKey: string,
|
||||||
|
isInternal = false
|
||||||
|
): MakeRequestWithFormDataResponse {
|
||||||
|
const request = setup.getRequest()!
|
||||||
|
const config = setup.getConfig()!
|
||||||
|
return async (
|
||||||
|
method: HttpMethod,
|
||||||
|
endpoint: string,
|
||||||
|
fields: Record<string, string | { path: string }>,
|
||||||
|
intAppId: string | null = config.getAppId()
|
||||||
|
) => {
|
||||||
|
const { headers, url } = base(apiKey, endpoint, intAppId, isInternal)
|
||||||
|
const req = request[method](url).set(config.defaultHeaders(headers))
|
||||||
|
for (let [field, value] of Object.entries(fields)) {
|
||||||
|
if (typeof value === "string") {
|
||||||
|
req.field(field, value)
|
||||||
|
} else {
|
||||||
|
req.attach(field, value.path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const res = await req
|
||||||
|
expect(res.body).toBeDefined()
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
import * as setup from "./utilities"
|
||||||
|
import path from "path"
|
||||||
|
|
||||||
|
jest.setTimeout(15000)
|
||||||
|
const PASSWORD = "testtest"
|
||||||
|
|
||||||
|
describe("/applications/:appId/import", () => {
|
||||||
|
let request = setup.getRequest()
|
||||||
|
let config = setup.getConfig()
|
||||||
|
|
||||||
|
afterAll(setup.afterAll)
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await config.init()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should be able to perform import", async () => {
|
||||||
|
const appId = config.getAppId()
|
||||||
|
const res = await request
|
||||||
|
.post(`/api/applications/${appId}/import`)
|
||||||
|
.field("encryptionPassword", PASSWORD)
|
||||||
|
.attach("appExport", path.join(__dirname, "assets", "export.tar.gz"))
|
||||||
|
.set(config.defaultHeaders())
|
||||||
|
.expect("Content-Type", /json/)
|
||||||
|
.expect(200)
|
||||||
|
expect(res.body.message).toBe("app updated")
|
||||||
|
const screens = await config.api.screen.list()
|
||||||
|
expect(screens.length).toBe(2)
|
||||||
|
expect(screens[0].routing.route).toBe("/derp")
|
||||||
|
expect(screens[1].routing.route).toBe("/blank")
|
||||||
|
})
|
||||||
|
})
|
Binary file not shown.
|
@ -18,6 +18,14 @@ export interface paths {
|
||||||
"/applications/{appId}/publish": {
|
"/applications/{appId}/publish": {
|
||||||
post: operations["appPublish"];
|
post: operations["appPublish"];
|
||||||
};
|
};
|
||||||
|
"/applications/{appId}/import": {
|
||||||
|
/** This endpoint is only available on a business or enterprise license. */
|
||||||
|
post: operations["appImport"];
|
||||||
|
};
|
||||||
|
"/applications/{appId}/export": {
|
||||||
|
/** This endpoint is only available on a business or enterprise license. */
|
||||||
|
post: operations["appExport"];
|
||||||
|
};
|
||||||
"/applications/search": {
|
"/applications/search": {
|
||||||
/** Based on application properties (currently only name) search for applications. */
|
/** Based on application properties (currently only name) search for applications. */
|
||||||
post: operations["appSearch"];
|
post: operations["appSearch"];
|
||||||
|
@ -158,6 +166,12 @@ export interface components {
|
||||||
appUrl: string;
|
appUrl: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
appExport: {
|
||||||
|
/** @description An optional password used to encrypt the export. */
|
||||||
|
encryptPassword: string;
|
||||||
|
/** @description Set whether the internal table rows should be excluded from the export. */
|
||||||
|
excludeRows: boolean;
|
||||||
|
};
|
||||||
/** @description The row to be created/updated, based on the table schema. */
|
/** @description The row to be created/updated, based on the table schema. */
|
||||||
row: { [key: string]: unknown };
|
row: { [key: string]: unknown };
|
||||||
searchOutput: {
|
searchOutput: {
|
||||||
|
@ -889,6 +903,54 @@ export interface operations {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
/** This endpoint is only available on a business or enterprise license. */
|
||||||
|
appImport: {
|
||||||
|
parameters: {
|
||||||
|
path: {
|
||||||
|
/** The ID of the app which this request is targeting. */
|
||||||
|
appId: components["parameters"]["appIdUrl"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** Application has been updated. */
|
||||||
|
204: never;
|
||||||
|
};
|
||||||
|
requestBody: {
|
||||||
|
content: {
|
||||||
|
"multipart/form-data": {
|
||||||
|
/** @description Password for the export if it is encrypted. */
|
||||||
|
encryptedPassword?: string;
|
||||||
|
/**
|
||||||
|
* Format: binary
|
||||||
|
* @description The app export to import.
|
||||||
|
*/
|
||||||
|
appExport: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** This endpoint is only available on a business or enterprise license. */
|
||||||
|
appExport: {
|
||||||
|
parameters: {
|
||||||
|
path: {
|
||||||
|
/** The ID of the app which this request is targeting. */
|
||||||
|
appId: components["parameters"]["appIdUrl"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** A gzip tarball containing the app export, encrypted if password provided. */
|
||||||
|
200: {
|
||||||
|
content: {
|
||||||
|
"application/gzip": string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
requestBody: {
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["appExport"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
/** Based on application properties (currently only name) search for applications. */
|
/** Based on application properties (currently only name) search for applications. */
|
||||||
appSearch: {
|
appSearch: {
|
||||||
responses: {
|
responses: {
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
import { db as dbCore } from "@budibase/backend-core"
|
||||||
|
import {
|
||||||
|
DocumentTypesToImport,
|
||||||
|
Document,
|
||||||
|
Database,
|
||||||
|
RowValue,
|
||||||
|
} from "@budibase/types"
|
||||||
|
import backups from "../backups"
|
||||||
|
|
||||||
|
export type FileAttributes = {
|
||||||
|
type: string
|
||||||
|
path: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeUpdateAndDeleteDocuments(
|
||||||
|
updateDocs: Document[],
|
||||||
|
deleteDocs: Document[]
|
||||||
|
) {
|
||||||
|
// compress the documents to create and to delete (if same ID, then just update the rev)
|
||||||
|
const finalToDelete = []
|
||||||
|
for (let deleteDoc of deleteDocs) {
|
||||||
|
const found = updateDocs.find(doc => doc._id === deleteDoc._id)
|
||||||
|
if (found) {
|
||||||
|
found._rev = deleteDoc._rev
|
||||||
|
} else {
|
||||||
|
finalToDelete.push(deleteDoc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [...updateDocs, ...finalToDelete]
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removeImportableDocuments(db: Database) {
|
||||||
|
// get the references to the documents, not the whole document
|
||||||
|
const docPromises = []
|
||||||
|
for (let docType of DocumentTypesToImport) {
|
||||||
|
docPromises.push(db.allDocs(dbCore.getDocParams(docType)))
|
||||||
|
}
|
||||||
|
let documentRefs: { _id: string; _rev: string }[] = []
|
||||||
|
for (let response of await Promise.all(docPromises)) {
|
||||||
|
documentRefs = documentRefs.concat(
|
||||||
|
response.rows.map(row => ({
|
||||||
|
_id: row.id,
|
||||||
|
_rev: (row.value as RowValue).rev,
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// add deletion key
|
||||||
|
return documentRefs.map(ref => ({ _deleted: true, ...ref }))
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getImportableDocuments(db: Database) {
|
||||||
|
// get the whole document
|
||||||
|
const docPromises = []
|
||||||
|
for (let docType of DocumentTypesToImport) {
|
||||||
|
docPromises.push(
|
||||||
|
db.allDocs(dbCore.getDocParams(docType, null, { include_docs: true }))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// map the responses to the document itself
|
||||||
|
let documents: Document[] = []
|
||||||
|
for (let response of await Promise.all(docPromises)) {
|
||||||
|
documents = documents.concat(response.rows.map(row => row.doc))
|
||||||
|
}
|
||||||
|
// remove the _rev, stops it being written
|
||||||
|
documents.forEach(doc => {
|
||||||
|
delete doc._rev
|
||||||
|
})
|
||||||
|
return documents
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateWithExport(
|
||||||
|
appId: string,
|
||||||
|
file: FileAttributes,
|
||||||
|
password?: string
|
||||||
|
) {
|
||||||
|
const devId = dbCore.getDevAppID(appId)
|
||||||
|
const tempAppName = `temp_${devId}`
|
||||||
|
const tempDb = dbCore.getDB(tempAppName)
|
||||||
|
const appDb = dbCore.getDB(devId)
|
||||||
|
try {
|
||||||
|
const template = {
|
||||||
|
file: {
|
||||||
|
type: file.type!,
|
||||||
|
path: file.path!,
|
||||||
|
password,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
// get a temporary version of the import
|
||||||
|
// don't need obj store, the existing app already has everything we need
|
||||||
|
await backups.importApp(devId, tempDb, template, {
|
||||||
|
importObjStoreContents: false,
|
||||||
|
})
|
||||||
|
// get the documents to copy
|
||||||
|
const toUpdate = await getImportableDocuments(tempDb)
|
||||||
|
// clear out the old documents
|
||||||
|
const toDelete = await removeImportableDocuments(appDb)
|
||||||
|
// now bulk update documents - add new ones, delete old ones and update common ones
|
||||||
|
await appDb.bulkDocs(mergeUpdateAndDeleteDocuments(toUpdate, toDelete))
|
||||||
|
} finally {
|
||||||
|
await tempDb.destroy()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,11 @@
|
||||||
import * as sync from "./sync"
|
import * as sync from "./sync"
|
||||||
import * as utils from "./utils"
|
import * as utils from "./utils"
|
||||||
import * as applications from "./applications"
|
import * as applications from "./applications"
|
||||||
|
import * as imports from "./import"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
...sync,
|
...sync,
|
||||||
...utils,
|
...utils,
|
||||||
...applications,
|
...applications,
|
||||||
|
...imports,
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,11 +8,7 @@ import {
|
||||||
TABLE_ROW_PREFIX,
|
TABLE_ROW_PREFIX,
|
||||||
USER_METDATA_PREFIX,
|
USER_METDATA_PREFIX,
|
||||||
} from "../../../db/utils"
|
} from "../../../db/utils"
|
||||||
import {
|
import { DB_EXPORT_FILE, STATIC_APP_FILES } from "./constants"
|
||||||
DB_EXPORT_FILE,
|
|
||||||
GLOBAL_DB_EXPORT_FILE,
|
|
||||||
STATIC_APP_FILES,
|
|
||||||
} from "./constants"
|
|
||||||
import fs from "fs"
|
import fs from "fs"
|
||||||
import { join } from "path"
|
import { join } from "path"
|
||||||
import env from "../../../environment"
|
import env from "../../../environment"
|
||||||
|
|
|
@ -151,7 +151,8 @@ export function getListOfAppsInMulti(tmpPath: string) {
|
||||||
export async function importApp(
|
export async function importApp(
|
||||||
appId: string,
|
appId: string,
|
||||||
db: Database,
|
db: Database,
|
||||||
template: TemplateType
|
template: TemplateType,
|
||||||
|
opts: { importObjStoreContents: boolean } = { importObjStoreContents: true }
|
||||||
) {
|
) {
|
||||||
let prodAppId = dbCore.getProdAppID(appId)
|
let prodAppId = dbCore.getProdAppID(appId)
|
||||||
let dbStream: any
|
let dbStream: any
|
||||||
|
@ -165,7 +166,7 @@ export async function importApp(
|
||||||
}
|
}
|
||||||
const contents = fs.readdirSync(tmpPath)
|
const contents = fs.readdirSync(tmpPath)
|
||||||
// have to handle object import
|
// have to handle object import
|
||||||
if (contents.length) {
|
if (contents.length && opts.importObjStoreContents) {
|
||||||
let promises = []
|
let promises = []
|
||||||
let excludedFiles = [GLOBAL_DB_EXPORT_FILE, DB_EXPORT_FILE]
|
let excludedFiles = [GLOBAL_DB_EXPORT_FILE, DB_EXPORT_FILE]
|
||||||
for (let filename of contents) {
|
for (let filename of contents) {
|
||||||
|
|
|
@ -53,7 +53,6 @@ import {
|
||||||
View,
|
View,
|
||||||
FieldType,
|
FieldType,
|
||||||
RelationshipType,
|
RelationshipType,
|
||||||
ViewV2,
|
|
||||||
CreateViewRequest,
|
CreateViewRequest,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { TableAPI } from "./table"
|
||||||
import { ViewV2API } from "./viewV2"
|
import { ViewV2API } from "./viewV2"
|
||||||
import { DatasourceAPI } from "./datasource"
|
import { DatasourceAPI } from "./datasource"
|
||||||
import { LegacyViewAPI } from "./legacyView"
|
import { LegacyViewAPI } from "./legacyView"
|
||||||
|
import { ScreenAPI } from "./screen"
|
||||||
|
|
||||||
export default class API {
|
export default class API {
|
||||||
table: TableAPI
|
table: TableAPI
|
||||||
|
@ -13,6 +14,7 @@ export default class API {
|
||||||
row: RowAPI
|
row: RowAPI
|
||||||
permission: PermissionAPI
|
permission: PermissionAPI
|
||||||
datasource: DatasourceAPI
|
datasource: DatasourceAPI
|
||||||
|
screen: ScreenAPI
|
||||||
|
|
||||||
constructor(config: TestConfiguration) {
|
constructor(config: TestConfiguration) {
|
||||||
this.table = new TableAPI(config)
|
this.table = new TableAPI(config)
|
||||||
|
@ -21,5 +23,6 @@ export default class API {
|
||||||
this.row = new RowAPI(config)
|
this.row = new RowAPI(config)
|
||||||
this.permission = new PermissionAPI(config)
|
this.permission = new PermissionAPI(config)
|
||||||
this.datasource = new DatasourceAPI(config)
|
this.datasource = new DatasourceAPI(config)
|
||||||
|
this.screen = new ScreenAPI(config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
import TestConfiguration from "../TestConfiguration"
|
||||||
|
import { Screen } from "@budibase/types"
|
||||||
|
import { TestAPI } from "./base"
|
||||||
|
|
||||||
|
export class ScreenAPI extends TestAPI {
|
||||||
|
constructor(config: TestConfiguration) {
|
||||||
|
super(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
list = async (): Promise<Screen[]> => {
|
||||||
|
const res = await this.request
|
||||||
|
.get(`/api/screens`)
|
||||||
|
.set(this.config.defaultHeaders())
|
||||||
|
.expect("Content-Type", /json/)
|
||||||
|
.expect(200)
|
||||||
|
return res.body as Screen[]
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,7 +19,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"concurrently": "^7.6.0",
|
"concurrently": "^7.6.0",
|
||||||
"rimraf": "3.0.2",
|
"rimraf": "3.0.2",
|
||||||
"typescript": "4.7.3"
|
"typescript": "5.2.2"
|
||||||
},
|
},
|
||||||
"nx": {
|
"nx": {
|
||||||
"targets": {
|
"targets": {
|
||||||
|
@ -43,7 +43,6 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,6 @@
|
||||||
"rollup-plugin-node-globals": "^1.4.0",
|
"rollup-plugin-node-globals": "^1.4.0",
|
||||||
"rollup-plugin-node-resolve": "^5.2.0",
|
"rollup-plugin-node-resolve": "^5.2.0",
|
||||||
"rollup-plugin-terser": "^7.0.2",
|
"rollup-plugin-terser": "^7.0.2",
|
||||||
"typescript": "4.7.3"
|
"typescript": "5.2.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
"concurrently": "^7.6.0",
|
"concurrently": "^7.6.0",
|
||||||
"koa-body": "4.2.0",
|
"koa-body": "4.2.0",
|
||||||
"rimraf": "3.0.2",
|
"rimraf": "3.0.2",
|
||||||
"typescript": "4.7.3"
|
"typescript": "5.2.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"scim-patch": "^0.7.0"
|
"scim-patch": "^0.7.0"
|
||||||
|
|
|
@ -39,6 +39,25 @@ export enum DocumentType {
|
||||||
AUDIT_LOG = "al",
|
AUDIT_LOG = "al",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// these are the core documents that make up the data, design
|
||||||
|
// and automation sections of an app. This excludes any internal
|
||||||
|
// rows as we shouldn't import data.
|
||||||
|
export const DocumentTypesToImport: DocumentType[] = [
|
||||||
|
DocumentType.ROLE,
|
||||||
|
DocumentType.DATASOURCE,
|
||||||
|
DocumentType.DATASOURCE_PLUS,
|
||||||
|
DocumentType.TABLE,
|
||||||
|
DocumentType.AUTOMATION,
|
||||||
|
DocumentType.WEBHOOK,
|
||||||
|
DocumentType.SCREEN,
|
||||||
|
DocumentType.QUERY,
|
||||||
|
DocumentType.METADATA,
|
||||||
|
DocumentType.MEM_VIEW,
|
||||||
|
// Deprecated but still copied
|
||||||
|
DocumentType.INSTANCE,
|
||||||
|
DocumentType.LAYOUT,
|
||||||
|
]
|
||||||
|
|
||||||
// these documents don't really exist, they are part of other
|
// these documents don't really exist, they are part of other
|
||||||
// documents or enriched into existence as part of get requests
|
// documents or enriched into existence as part of get requests
|
||||||
export enum VirtualDocumentType {
|
export enum VirtualDocumentType {
|
||||||
|
|
|
@ -11,7 +11,7 @@ export enum Feature {
|
||||||
SYNC_AUTOMATIONS = "syncAutomations",
|
SYNC_AUTOMATIONS = "syncAutomations",
|
||||||
APP_BUILDERS = "appBuilders",
|
APP_BUILDERS = "appBuilders",
|
||||||
OFFLINE = "offline",
|
OFFLINE = "offline",
|
||||||
USER_ROLE_PUBLIC_API = "userRolePublicApi",
|
EXPANDED_PUBLIC_API = "expandedPublicApi",
|
||||||
VIEW_PERMISSIONS = "viewPermissions",
|
VIEW_PERMISSIONS = "viewPermissions",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -88,7 +88,6 @@
|
||||||
"@types/server-destroy": "1.0.1",
|
"@types/server-destroy": "1.0.1",
|
||||||
"@types/supertest": "2.0.12",
|
"@types/supertest": "2.0.12",
|
||||||
"@types/uuid": "8.3.4",
|
"@types/uuid": "8.3.4",
|
||||||
"@typescript-eslint/parser": "5.45.0",
|
|
||||||
"copyfiles": "2.4.1",
|
"copyfiles": "2.4.1",
|
||||||
"eslint": "6.8.0",
|
"eslint": "6.8.0",
|
||||||
"jest": "29.6.2",
|
"jest": "29.6.2",
|
||||||
|
@ -100,7 +99,7 @@
|
||||||
"timekeeper": "2.2.0",
|
"timekeeper": "2.2.0",
|
||||||
"ts-node": "10.8.1",
|
"ts-node": "10.8.1",
|
||||||
"tsconfig-paths": "4.0.0",
|
"tsconfig-paths": "4.0.0",
|
||||||
"typescript": "4.7.3",
|
"typescript": "5.2.2",
|
||||||
"update-dotenv": "1.1.1"
|
"update-dotenv": "1.1.1"
|
||||||
},
|
},
|
||||||
"nx": {
|
"nx": {
|
||||||
|
|
|
@ -38,7 +38,7 @@
|
||||||
"ts-jest": "29.1.1",
|
"ts-jest": "29.1.1",
|
||||||
"ts-node": "10.8.1",
|
"ts-node": "10.8.1",
|
||||||
"tsconfig-paths": "4.0.0",
|
"tsconfig-paths": "4.0.0",
|
||||||
"typescript": "4.7.3"
|
"typescript": "5.2.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/backend-core": "^2.3.17",
|
"@budibase/backend-core": "^2.3.17",
|
||||||
|
|
89
yarn.lock
89
yarn.lock
|
@ -6696,51 +6696,52 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/yargs-parser" "*"
|
"@types/yargs-parser" "*"
|
||||||
|
|
||||||
"@typescript-eslint/parser@5.45.0":
|
"@typescript-eslint/parser@6.7.2":
|
||||||
version "5.45.0"
|
version "6.7.2"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.45.0.tgz#b18a5f6b3cf1c2b3e399e9d2df4be40d6b0ddd0e"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.7.2.tgz#e0ae93771441b9518e67d0660c79e3a105497af4"
|
||||||
integrity sha512-brvs/WSM4fKUmF5Ot/gEve6qYiCMjm6w4HkHPfS6ZNmxTS0m0iNN4yOChImaCkqc1hRwFGqUyanMXuGal6oyyQ==
|
integrity sha512-KA3E4ox0ws+SPyxQf9iSI25R6b4Ne78ORhNHeVKrPQnoYsb9UhieoiRoJgrzgEeKGOXhcY1i8YtOeCHHTDa6Fw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@typescript-eslint/scope-manager" "5.45.0"
|
"@typescript-eslint/scope-manager" "6.7.2"
|
||||||
"@typescript-eslint/types" "5.45.0"
|
"@typescript-eslint/types" "6.7.2"
|
||||||
"@typescript-eslint/typescript-estree" "5.45.0"
|
"@typescript-eslint/typescript-estree" "6.7.2"
|
||||||
|
"@typescript-eslint/visitor-keys" "6.7.2"
|
||||||
debug "^4.3.4"
|
debug "^4.3.4"
|
||||||
|
|
||||||
"@typescript-eslint/scope-manager@5.45.0":
|
"@typescript-eslint/scope-manager@6.7.2":
|
||||||
version "5.45.0"
|
version "6.7.2"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.45.0.tgz#7a4ac1bfa9544bff3f620ab85947945938319a96"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.7.2.tgz#cf59a2095d2f894770c94be489648ad1c78dc689"
|
||||||
integrity sha512-noDMjr87Arp/PuVrtvN3dXiJstQR1+XlQ4R1EvzG+NMgXi8CuMCXpb8JqNtFHKceVSQ985BZhfRdowJzbv4yKw==
|
integrity sha512-bgi6plgyZjEqapr7u2mhxGR6E8WCzKNUFWNh6fkpVe9+yzRZeYtDTbsIBzKbcxI+r1qVWt6VIoMSNZ4r2A+6Yw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@typescript-eslint/types" "5.45.0"
|
"@typescript-eslint/types" "6.7.2"
|
||||||
"@typescript-eslint/visitor-keys" "5.45.0"
|
"@typescript-eslint/visitor-keys" "6.7.2"
|
||||||
|
|
||||||
"@typescript-eslint/types@4.33.0":
|
"@typescript-eslint/types@4.33.0":
|
||||||
version "4.33.0"
|
version "4.33.0"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.33.0.tgz#a1e59036a3b53ae8430ceebf2a919dc7f9af6d72"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.33.0.tgz#a1e59036a3b53ae8430ceebf2a919dc7f9af6d72"
|
||||||
integrity sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ==
|
integrity sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ==
|
||||||
|
|
||||||
"@typescript-eslint/types@5.45.0":
|
|
||||||
version "5.45.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.45.0.tgz#794760b9037ee4154c09549ef5a96599621109c5"
|
|
||||||
integrity sha512-QQij+u/vgskA66azc9dCmx+rev79PzX8uDHpsqSjEFtfF2gBUTRCpvYMh2gw2ghkJabNkPlSUCimsyBEQZd1DA==
|
|
||||||
|
|
||||||
"@typescript-eslint/types@5.53.0":
|
"@typescript-eslint/types@5.53.0":
|
||||||
version "5.53.0"
|
version "5.53.0"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.53.0.tgz#f79eca62b97e518ee124086a21a24f3be267026f"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.53.0.tgz#f79eca62b97e518ee124086a21a24f3be267026f"
|
||||||
integrity sha512-5kcDL9ZUIP756K6+QOAfPkigJmCPHcLN7Zjdz76lQWWDdzfOhZDTj1irs6gPBKiXx5/6O3L0+AvupAut3z7D2A==
|
integrity sha512-5kcDL9ZUIP756K6+QOAfPkigJmCPHcLN7Zjdz76lQWWDdzfOhZDTj1irs6gPBKiXx5/6O3L0+AvupAut3z7D2A==
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree@5.45.0":
|
"@typescript-eslint/types@6.7.2":
|
||||||
version "5.45.0"
|
version "6.7.2"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.45.0.tgz#f70a0d646d7f38c0dfd6936a5e171a77f1e5291d"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.7.2.tgz#75a615a6dbeca09cafd102fe7f465da1d8a3c066"
|
||||||
integrity sha512-maRhLGSzqUpFcZgXxg1qc/+H0bT36lHK4APhp0AEUVrpSwXiRAomm/JGjSG+kNUio5kAa3uekCYu/47cnGn5EQ==
|
integrity sha512-flJYwMYgnUNDAN9/GAI3l8+wTmvTYdv64fcH8aoJK76Y+1FCZ08RtI5zDerM/FYT5DMkAc+19E4aLmd5KqdFyg==
|
||||||
|
|
||||||
|
"@typescript-eslint/typescript-estree@6.7.2":
|
||||||
|
version "6.7.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.2.tgz#ce5883c23b581a5caf878af641e49dd0349238c7"
|
||||||
|
integrity sha512-kiJKVMLkoSciGyFU0TOY0fRxnp9qq1AzVOHNeN1+B9erKFCJ4Z8WdjAkKQPP+b1pWStGFqezMLltxO+308dJTQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@typescript-eslint/types" "5.45.0"
|
"@typescript-eslint/types" "6.7.2"
|
||||||
"@typescript-eslint/visitor-keys" "5.45.0"
|
"@typescript-eslint/visitor-keys" "6.7.2"
|
||||||
debug "^4.3.4"
|
debug "^4.3.4"
|
||||||
globby "^11.1.0"
|
globby "^11.1.0"
|
||||||
is-glob "^4.0.3"
|
is-glob "^4.0.3"
|
||||||
semver "^7.3.7"
|
semver "^7.5.4"
|
||||||
tsutils "^3.21.0"
|
ts-api-utils "^1.0.1"
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree@^4.33.0":
|
"@typescript-eslint/typescript-estree@^4.33.0":
|
||||||
version "4.33.0"
|
version "4.33.0"
|
||||||
|
@ -6776,14 +6777,6 @@
|
||||||
"@typescript-eslint/types" "4.33.0"
|
"@typescript-eslint/types" "4.33.0"
|
||||||
eslint-visitor-keys "^2.0.0"
|
eslint-visitor-keys "^2.0.0"
|
||||||
|
|
||||||
"@typescript-eslint/visitor-keys@5.45.0":
|
|
||||||
version "5.45.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.45.0.tgz#e0d160e9e7fdb7f8da697a5b78e7a14a22a70528"
|
|
||||||
integrity sha512-jc6Eccbn2RtQPr1s7th6jJWQHBHI6GBVQkCHoJFQ5UreaKm59Vxw+ynQUPPY2u2Amquc+7tmEoC2G52ApsGNNg==
|
|
||||||
dependencies:
|
|
||||||
"@typescript-eslint/types" "5.45.0"
|
|
||||||
eslint-visitor-keys "^3.3.0"
|
|
||||||
|
|
||||||
"@typescript-eslint/visitor-keys@5.53.0":
|
"@typescript-eslint/visitor-keys@5.53.0":
|
||||||
version "5.53.0"
|
version "5.53.0"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.53.0.tgz#8a5126623937cdd909c30d8fa72f79fa56cc1a9f"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.53.0.tgz#8a5126623937cdd909c30d8fa72f79fa56cc1a9f"
|
||||||
|
@ -6792,6 +6785,14 @@
|
||||||
"@typescript-eslint/types" "5.53.0"
|
"@typescript-eslint/types" "5.53.0"
|
||||||
eslint-visitor-keys "^3.3.0"
|
eslint-visitor-keys "^3.3.0"
|
||||||
|
|
||||||
|
"@typescript-eslint/visitor-keys@6.7.2":
|
||||||
|
version "6.7.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.2.tgz#4cb2bd786f1f459731b0ad1584c9f73e1c7a4d5c"
|
||||||
|
integrity sha512-uVw9VIMFBUTz8rIeaUT3fFe8xIUx8r4ywAdlQv1ifH+6acn/XF8Y6rwJ7XNmkNMDrTW+7+vxFFPIF40nJCVsMQ==
|
||||||
|
dependencies:
|
||||||
|
"@typescript-eslint/types" "6.7.2"
|
||||||
|
eslint-visitor-keys "^3.4.1"
|
||||||
|
|
||||||
"@vitest/expect@0.29.8":
|
"@vitest/expect@0.29.8":
|
||||||
version "0.29.8"
|
version "0.29.8"
|
||||||
resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-0.29.8.tgz#6ecdd031b4ea8414717d10b65ccd800908384612"
|
resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-0.29.8.tgz#6ecdd031b4ea8414717d10b65ccd800908384612"
|
||||||
|
@ -21927,6 +21928,13 @@ semver@^7.3.2, semver@^7.3.7, semver@^7.3.8:
|
||||||
dependencies:
|
dependencies:
|
||||||
lru-cache "^6.0.0"
|
lru-cache "^6.0.0"
|
||||||
|
|
||||||
|
semver@^7.5.4:
|
||||||
|
version "7.5.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"
|
||||||
|
integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
|
||||||
|
dependencies:
|
||||||
|
lru-cache "^6.0.0"
|
||||||
|
|
||||||
semver@~2.3.1:
|
semver@~2.3.1:
|
||||||
version "2.3.2"
|
version "2.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/semver/-/semver-2.3.2.tgz#b9848f25d6cf36333073ec9ef8856d42f1233e52"
|
resolved "https://registry.yarnpkg.com/semver/-/semver-2.3.2.tgz#b9848f25d6cf36333073ec9ef8856d42f1233e52"
|
||||||
|
@ -23882,6 +23890,11 @@ triple-beam@^1.3.0:
|
||||||
resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9"
|
resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9"
|
||||||
integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==
|
integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==
|
||||||
|
|
||||||
|
ts-api-utils@^1.0.1:
|
||||||
|
version "1.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.0.3.tgz#f12c1c781d04427313dbac808f453f050e54a331"
|
||||||
|
integrity sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==
|
||||||
|
|
||||||
ts-graphviz@^1.5.0:
|
ts-graphviz@^1.5.0:
|
||||||
version "1.5.4"
|
version "1.5.4"
|
||||||
resolved "https://registry.yarnpkg.com/ts-graphviz/-/ts-graphviz-1.5.4.tgz#61a3059afeac4f6d4be3c6729a4d88546ca9e095"
|
resolved "https://registry.yarnpkg.com/ts-graphviz/-/ts-graphviz-1.5.4.tgz#61a3059afeac4f6d4be3c6729a4d88546ca9e095"
|
||||||
|
@ -24120,10 +24133,10 @@ typeof@^1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/typeof/-/typeof-1.0.0.tgz#9c84403f2323ae5399167275497638ea1d2f2440"
|
resolved "https://registry.yarnpkg.com/typeof/-/typeof-1.0.0.tgz#9c84403f2323ae5399167275497638ea1d2f2440"
|
||||||
integrity sha512-Pze0mIxYXhaJdpw1ayMzOA7rtGr1OmsTY/Z+FWtRKIqXFz6aoDLjqdbWE/tcIBSC8nhnVXiRrEXujodR/xiFAA==
|
integrity sha512-Pze0mIxYXhaJdpw1ayMzOA7rtGr1OmsTY/Z+FWtRKIqXFz6aoDLjqdbWE/tcIBSC8nhnVXiRrEXujodR/xiFAA==
|
||||||
|
|
||||||
typescript@4.7.3:
|
typescript@5.2.2:
|
||||||
version "4.7.3"
|
version "5.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.3.tgz#8364b502d5257b540f9de4c40be84c98e23a129d"
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78"
|
||||||
integrity sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA==
|
integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==
|
||||||
|
|
||||||
"typescript@>=3 < 6":
|
"typescript@>=3 < 6":
|
||||||
version "5.0.4"
|
version "5.0.4"
|
||||||
|
|
Loading…
Reference in New Issue