Merge branch 'binding-ts-improvements' of github.com:Budibase/budibase into binding-ts-improvements
This commit is contained in:
commit
ff78610fe4
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
|
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
|
||||||
"version": "3.2.42",
|
"version": "3.2.44",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"concurrency": 20,
|
"concurrency": 20,
|
||||||
"command": {
|
"command": {
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
import { writable } from "svelte/store"
|
|
||||||
import { API } from "@/api"
|
|
||||||
|
|
||||||
export function createPermissionStore() {
|
|
||||||
const { subscribe } = writable([])
|
|
||||||
|
|
||||||
return {
|
|
||||||
subscribe,
|
|
||||||
save: async ({ level, role, resource }) => {
|
|
||||||
return await API.updatePermissionForResource(resource, role, level)
|
|
||||||
},
|
|
||||||
remove: async ({ level, role, resource }) => {
|
|
||||||
return await API.removePermissionFromResource(resource, role, level)
|
|
||||||
},
|
|
||||||
forResource: async resourceId => {
|
|
||||||
return (await API.getPermissionForResource(resourceId)).permissions
|
|
||||||
},
|
|
||||||
forResourceDetailed: async resourceId => {
|
|
||||||
return await API.getPermissionForResource(resourceId)
|
|
||||||
},
|
|
||||||
getDependantsInfo: async resourceId => {
|
|
||||||
return await API.getDependants(resourceId)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const permissions = createPermissionStore()
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
import { BudiStore } from "../BudiStore"
|
||||||
|
import { API } from "@/api"
|
||||||
|
import {
|
||||||
|
PermissionLevel,
|
||||||
|
GetResourcePermsResponse,
|
||||||
|
GetDependantResourcesResponse,
|
||||||
|
ResourcePermissionInfo,
|
||||||
|
} from "@budibase/types"
|
||||||
|
|
||||||
|
interface Permission {
|
||||||
|
level: PermissionLevel
|
||||||
|
role: string
|
||||||
|
resource: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PermissionStore extends BudiStore<Permission[]> {
|
||||||
|
constructor() {
|
||||||
|
super([])
|
||||||
|
}
|
||||||
|
|
||||||
|
save = async (permission: Permission) => {
|
||||||
|
const { level, role, resource } = permission
|
||||||
|
return await API.updatePermissionForResource(resource, role, level)
|
||||||
|
}
|
||||||
|
|
||||||
|
remove = async (permission: Permission) => {
|
||||||
|
const { level, role, resource } = permission
|
||||||
|
return await API.removePermissionFromResource(resource, role, level)
|
||||||
|
}
|
||||||
|
|
||||||
|
forResource = async (
|
||||||
|
resourceId: string
|
||||||
|
): Promise<Record<string, ResourcePermissionInfo>> => {
|
||||||
|
return (await API.getPermissionForResource(resourceId)).permissions
|
||||||
|
}
|
||||||
|
|
||||||
|
forResourceDetailed = async (
|
||||||
|
resourceId: string
|
||||||
|
): Promise<GetResourcePermsResponse> => {
|
||||||
|
return await API.getPermissionForResource(resourceId)
|
||||||
|
}
|
||||||
|
|
||||||
|
getDependantsInfo = async (
|
||||||
|
resourceId: string
|
||||||
|
): Promise<GetDependantResourcesResponse> => {
|
||||||
|
return await API.getDependants(resourceId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const permissions = new PermissionStore()
|
|
@ -1,67 +0,0 @@
|
||||||
import { writable, derived } from "svelte/store"
|
|
||||||
import { tables } from "./tables"
|
|
||||||
import { API } from "@/api"
|
|
||||||
|
|
||||||
export function createViewsStore() {
|
|
||||||
const store = writable({
|
|
||||||
selectedViewName: null,
|
|
||||||
})
|
|
||||||
const derivedStore = derived([store, tables], ([$store, $tables]) => {
|
|
||||||
let list = []
|
|
||||||
$tables.list?.forEach(table => {
|
|
||||||
const views = Object.values(table?.views || {}).filter(view => {
|
|
||||||
return view.version !== 2
|
|
||||||
})
|
|
||||||
list = list.concat(views)
|
|
||||||
})
|
|
||||||
return {
|
|
||||||
...$store,
|
|
||||||
list,
|
|
||||||
selected: list.find(view => view.name === $store.selectedViewName),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const select = name => {
|
|
||||||
store.update(state => ({
|
|
||||||
...state,
|
|
||||||
selectedViewName: name,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
const deleteView = async view => {
|
|
||||||
await API.deleteView(view.name)
|
|
||||||
|
|
||||||
// Update tables
|
|
||||||
tables.update(state => {
|
|
||||||
const table = state.list.find(table => table._id === view.tableId)
|
|
||||||
delete table.views[view.name]
|
|
||||||
return { ...state }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const save = async view => {
|
|
||||||
const savedView = await API.saveView(view)
|
|
||||||
select(view.name)
|
|
||||||
|
|
||||||
// Update tables
|
|
||||||
tables.update(state => {
|
|
||||||
const table = state.list.find(table => table._id === view.tableId)
|
|
||||||
if (table) {
|
|
||||||
if (view.originalName) {
|
|
||||||
delete table.views[view.originalName]
|
|
||||||
}
|
|
||||||
table.views[view.name] = savedView
|
|
||||||
}
|
|
||||||
return { ...state }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
subscribe: derivedStore.subscribe,
|
|
||||||
select,
|
|
||||||
delete: deleteView,
|
|
||||||
save,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const views = createViewsStore()
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
import { DerivedBudiStore } from "../BudiStore"
|
||||||
|
import { tables } from "./tables"
|
||||||
|
import { API } from "@/api"
|
||||||
|
import { View } from "@budibase/types"
|
||||||
|
import { helpers } from "@budibase/shared-core"
|
||||||
|
import { derived, Writable } from "svelte/store"
|
||||||
|
|
||||||
|
interface BuilderViewStore {
|
||||||
|
selectedViewName: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DerivedViewStore extends BuilderViewStore {
|
||||||
|
list: View[]
|
||||||
|
selected?: View
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ViewsStore extends DerivedBudiStore<
|
||||||
|
BuilderViewStore,
|
||||||
|
DerivedViewStore
|
||||||
|
> {
|
||||||
|
constructor() {
|
||||||
|
const makeDerivedStore = (store: Writable<BuilderViewStore>) => {
|
||||||
|
return derived([store, tables], ([$store, $tables]): DerivedViewStore => {
|
||||||
|
let list: View[] = []
|
||||||
|
$tables.list?.forEach(table => {
|
||||||
|
const views = Object.values(table?.views || {}).filter(
|
||||||
|
(view): view is View => !helpers.views.isV2(view)
|
||||||
|
)
|
||||||
|
list = list.concat(views)
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
selectedViewName: $store.selectedViewName,
|
||||||
|
list,
|
||||||
|
selected: list.find(view => view.name === $store.selectedViewName),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
super(
|
||||||
|
{
|
||||||
|
selectedViewName: null,
|
||||||
|
},
|
||||||
|
makeDerivedStore
|
||||||
|
)
|
||||||
|
|
||||||
|
this.select = this.select.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
select = (name: string) => {
|
||||||
|
this.store.update(state => ({
|
||||||
|
...state,
|
||||||
|
selectedViewName: name,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
delete = async (view: View) => {
|
||||||
|
if (!view.name) {
|
||||||
|
throw new Error("View name is required")
|
||||||
|
}
|
||||||
|
await API.deleteView(view.name)
|
||||||
|
|
||||||
|
// Update tables
|
||||||
|
tables.update(state => {
|
||||||
|
const table = state.list.find(table => table._id === view.tableId)
|
||||||
|
if (table?.views && view.name) {
|
||||||
|
delete table.views[view.name]
|
||||||
|
}
|
||||||
|
return { ...state }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
save = async (view: View & { originalName?: string }) => {
|
||||||
|
if (!view.name) {
|
||||||
|
throw new Error("View name is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
const savedView = await API.saveView(view)
|
||||||
|
this.select(view.name)
|
||||||
|
|
||||||
|
// Update tables
|
||||||
|
tables.update(state => {
|
||||||
|
const table = state.list.find(table => table._id === view.tableId)
|
||||||
|
if (table?.views && view.name) {
|
||||||
|
if (view.originalName) {
|
||||||
|
delete table.views[view.originalName]
|
||||||
|
}
|
||||||
|
table.views[view.name] = savedView
|
||||||
|
}
|
||||||
|
return { ...state }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const views = new ViewsStore()
|
|
@ -1 +1 @@
|
||||||
Subproject commit 193476cdfade6d3c613e6972f16ee0c527e01ff6
|
Subproject commit 43a5785ccb4f83ce929b29f05ea0a62199fcdf23
|
|
@ -2043,6 +2043,101 @@ if (descriptions.length) {
|
||||||
expect(rows[0].name).toEqual("Clare updated")
|
expect(rows[0].name).toEqual("Clare updated")
|
||||||
expect(rows[1].name).toEqual("Jeff updated")
|
expect(rows[1].name).toEqual("Jeff updated")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should reject bulkImport date only fields with wrong format", async () => {
|
||||||
|
const table = await config.api.table.save(
|
||||||
|
saveTableRequest({
|
||||||
|
schema: {
|
||||||
|
date: {
|
||||||
|
type: FieldType.DATETIME,
|
||||||
|
dateOnly: true,
|
||||||
|
name: "date",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
await config.api.row.bulkImport(
|
||||||
|
table._id!,
|
||||||
|
{
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
date: "01.02.2024",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: 400,
|
||||||
|
body: {
|
||||||
|
message:
|
||||||
|
'Invalid format for field "date": "01.02.2024". Date-only fields must be in the format "YYYY-MM-DD".',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should reject bulkImport date time fields with wrong format", async () => {
|
||||||
|
const table = await config.api.table.save(
|
||||||
|
saveTableRequest({
|
||||||
|
schema: {
|
||||||
|
date: {
|
||||||
|
type: FieldType.DATETIME,
|
||||||
|
name: "date",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
await config.api.row.bulkImport(
|
||||||
|
table._id!,
|
||||||
|
{
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
date: "01.02.2024",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: 400,
|
||||||
|
body: {
|
||||||
|
message:
|
||||||
|
'Invalid format for field "date": "01.02.2024". Datetime fields must be in ISO format, e.g. "YYYY-MM-DDTHH:MM:SSZ".',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should reject bulkImport time fields with wrong format", async () => {
|
||||||
|
const table = await config.api.table.save(
|
||||||
|
saveTableRequest({
|
||||||
|
schema: {
|
||||||
|
time: {
|
||||||
|
type: FieldType.DATETIME,
|
||||||
|
timeOnly: true,
|
||||||
|
name: "time",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
await config.api.row.bulkImport(
|
||||||
|
table._id!,
|
||||||
|
{
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
time: "3pm",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: 400,
|
||||||
|
body: {
|
||||||
|
message:
|
||||||
|
'Invalid format for field "time": "3pm". Time-only fields must be in the format "HH:MM:SS".',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("enrich", () => {
|
describe("enrich", () => {
|
||||||
|
|
|
@ -1705,7 +1705,10 @@ if (descriptions.length) {
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
tableOrViewId = await createTableOrView({
|
tableOrViewId = await createTableOrView({
|
||||||
dateid: { name: "dateid", type: FieldType.STRING },
|
dateid: {
|
||||||
|
name: "dateid",
|
||||||
|
type: FieldType.STRING,
|
||||||
|
},
|
||||||
date: {
|
date: {
|
||||||
name: "date",
|
name: "date",
|
||||||
type: FieldType.DATETIME,
|
type: FieldType.DATETIME,
|
||||||
|
@ -1751,7 +1754,9 @@ if (descriptions.length) {
|
||||||
describe("notEqual", () => {
|
describe("notEqual", () => {
|
||||||
it("successfully finds a row", async () => {
|
it("successfully finds a row", async () => {
|
||||||
await expectQuery({
|
await expectQuery({
|
||||||
notEqual: { date: `${JAN_1ST}${SEARCH_SUFFIX}` },
|
notEqual: {
|
||||||
|
date: `${JAN_1ST}${SEARCH_SUFFIX}`,
|
||||||
|
},
|
||||||
}).toContainExactly([
|
}).toContainExactly([
|
||||||
{ date: JAN_10TH },
|
{ date: JAN_10TH },
|
||||||
{ dateid: NULL_DATE__ID },
|
{ dateid: NULL_DATE__ID },
|
||||||
|
@ -1760,7 +1765,9 @@ if (descriptions.length) {
|
||||||
|
|
||||||
it("fails to find nonexistent row", async () => {
|
it("fails to find nonexistent row", async () => {
|
||||||
await expectQuery({
|
await expectQuery({
|
||||||
notEqual: { date: `${JAN_30TH}${SEARCH_SUFFIX}` },
|
notEqual: {
|
||||||
|
date: `${JAN_30TH}${SEARCH_SUFFIX}`,
|
||||||
|
},
|
||||||
}).toContainExactly([
|
}).toContainExactly([
|
||||||
{ date: JAN_1ST },
|
{ date: JAN_1ST },
|
||||||
{ date: JAN_10TH },
|
{ date: JAN_10TH },
|
||||||
|
@ -1822,6 +1829,60 @@ if (descriptions.length) {
|
||||||
}).toFindNothing()
|
}).toFindNothing()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("sort", () => {
|
||||||
|
it("sorts ascending", async () => {
|
||||||
|
await expectSearch({
|
||||||
|
query: {},
|
||||||
|
sort: "date",
|
||||||
|
sortOrder: SortOrder.ASCENDING,
|
||||||
|
}).toMatchExactly([
|
||||||
|
{ dateid: NULL_DATE__ID },
|
||||||
|
{ date: JAN_1ST },
|
||||||
|
{ date: JAN_10TH },
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("sorts descending", async () => {
|
||||||
|
await expectSearch({
|
||||||
|
query: {},
|
||||||
|
sort: "date",
|
||||||
|
sortOrder: SortOrder.DESCENDING,
|
||||||
|
}).toMatchExactly([
|
||||||
|
{ date: JAN_10TH },
|
||||||
|
{ date: JAN_1ST },
|
||||||
|
{ dateid: NULL_DATE__ID },
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("sortType STRING", () => {
|
||||||
|
it("sorts ascending", async () => {
|
||||||
|
await expectSearch({
|
||||||
|
query: {},
|
||||||
|
sort: "date",
|
||||||
|
sortType: SortType.STRING,
|
||||||
|
sortOrder: SortOrder.ASCENDING,
|
||||||
|
}).toMatchExactly([
|
||||||
|
{ dateid: NULL_DATE__ID },
|
||||||
|
{ date: JAN_1ST },
|
||||||
|
{ date: JAN_10TH },
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("sorts descending", async () => {
|
||||||
|
await expectSearch({
|
||||||
|
query: {},
|
||||||
|
sort: "date",
|
||||||
|
sortType: SortType.STRING,
|
||||||
|
sortOrder: SortOrder.DESCENDING,
|
||||||
|
}).toMatchExactly([
|
||||||
|
{ date: JAN_10TH },
|
||||||
|
{ date: JAN_1ST },
|
||||||
|
{ dateid: NULL_DATE__ID },
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {
|
||||||
Table,
|
Table,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { ValidColumnNameRegex, helpers, utils } from "@budibase/shared-core"
|
import { ValidColumnNameRegex, helpers, utils } from "@budibase/shared-core"
|
||||||
import { db } from "@budibase/backend-core"
|
import { db, HTTPError, sql } from "@budibase/backend-core"
|
||||||
|
|
||||||
type Rows = Array<Row>
|
type Rows = Array<Row>
|
||||||
|
|
||||||
|
@ -175,15 +175,27 @@ export function parse(rows: Rows, table: Table): Rows {
|
||||||
if ([FieldType.NUMBER].includes(columnType)) {
|
if ([FieldType.NUMBER].includes(columnType)) {
|
||||||
// If provided must be a valid number
|
// If provided must be a valid number
|
||||||
parsedRow[columnName] = columnData ? Number(columnData) : columnData
|
parsedRow[columnName] = columnData ? Number(columnData) : columnData
|
||||||
} else if (
|
} else if (columnType === FieldType.DATETIME) {
|
||||||
columnType === FieldType.DATETIME &&
|
if (columnData && !columnSchema.timeOnly) {
|
||||||
!columnSchema.timeOnly &&
|
if (!sql.utils.isValidISODateString(columnData)) {
|
||||||
!columnSchema.dateOnly
|
let message = `Invalid format for field "${columnName}": "${columnData}".`
|
||||||
) {
|
if (columnSchema.dateOnly) {
|
||||||
// If provided must be a valid date
|
message += ` Date-only fields must be in the format "YYYY-MM-DD".`
|
||||||
|
} else {
|
||||||
|
message += ` Datetime fields must be in ISO format, e.g. "YYYY-MM-DDTHH:MM:SSZ".`
|
||||||
|
}
|
||||||
|
throw new HTTPError(message, 400)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (columnData && columnSchema.timeOnly) {
|
||||||
|
if (!sql.utils.isValidTime(columnData)) {
|
||||||
|
throw new HTTPError(
|
||||||
|
`Invalid format for field "${columnName}": "${columnData}". Time-only fields must be in the format "HH:MM:SS".`,
|
||||||
|
400
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
parsedRow[columnName] = columnData
|
parsedRow[columnName] = columnData
|
||||||
? new Date(columnData).toISOString()
|
|
||||||
: columnData
|
|
||||||
} else if (
|
} else if (
|
||||||
columnType === FieldType.JSON &&
|
columnType === FieldType.JSON &&
|
||||||
typeof columnData === "string"
|
typeof columnData === "string"
|
||||||
|
|
|
@ -46,7 +46,10 @@ const svelteCompilePlugin = {
|
||||||
let { argv } = require("yargs")
|
let { argv } = require("yargs")
|
||||||
|
|
||||||
async function runBuild(entry, outfile) {
|
async function runBuild(entry, outfile) {
|
||||||
const isDev = process.env.NODE_ENV !== "production"
|
const isDev = !process.env.CI
|
||||||
|
|
||||||
|
console.log(`Building in mode dev mode: ${isDev}`)
|
||||||
|
|
||||||
const tsconfig = argv["p"] || `tsconfig.build.json`
|
const tsconfig = argv["p"] || `tsconfig.build.json`
|
||||||
|
|
||||||
const { data: tsconfigPathPluginContent } = loadTsConfig(
|
const { data: tsconfigPathPluginContent } = loadTsConfig(
|
||||||
|
@ -58,7 +61,7 @@ async function runBuild(entry, outfile) {
|
||||||
entryPoints: [entry],
|
entryPoints: [entry],
|
||||||
bundle: true,
|
bundle: true,
|
||||||
minify: !isDev,
|
minify: !isDev,
|
||||||
sourcemap: isDev,
|
sourcemap: tsconfigPathPluginContent.compilerOptions.sourceMap,
|
||||||
tsconfig,
|
tsconfig,
|
||||||
plugins: [
|
plugins: [
|
||||||
svelteCompilePlugin,
|
svelteCompilePlugin,
|
||||||
|
@ -125,10 +128,12 @@ async function runBuild(entry, outfile) {
|
||||||
|
|
||||||
await Promise.all([hbsFiles, mainBuild, oldClientVersions])
|
await Promise.all([hbsFiles, mainBuild, oldClientVersions])
|
||||||
|
|
||||||
fs.writeFileSync(
|
if (isDev) {
|
||||||
`dist/${path.basename(outfile)}.meta.json`,
|
fs.writeFileSync(
|
||||||
JSON.stringify((await mainBuild).metafile)
|
`dist/${path.basename(outfile)}.meta.json`,
|
||||||
)
|
JSON.stringify((await mainBuild).metafile)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
"\x1b[32m%s\x1b[0m",
|
"\x1b[32m%s\x1b[0m",
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
|
"sourceMap": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"@budibase/types": ["./packages/types/src"],
|
"@budibase/types": ["./packages/types/src"],
|
||||||
"@budibase/backend-core": ["./packages/backend-core/src"],
|
"@budibase/backend-core": ["./packages/backend-core/src"],
|
||||||
|
|
Loading…
Reference in New Issue