From 8b9bb784c49d56aeb1d04b67f6a8bc048fbf430e Mon Sep 17 00:00:00 2001
From: Adria Navarro <adria@budibase.com>
Date: Wed, 16 Oct 2024 17:06:15 +0200
Subject: [PATCH 01/10] Initial appMetadata sdk usage

---
 .../server/src/api/controllers/application.ts | 19 ++++++++-----------
 .../server/src/sdk/app/appMetadata/index.ts   |  5 +++++
 .../src/sdk/app/appMetadata/metadata.ts       |  8 ++++++++
 packages/server/src/sdk/index.ts              |  2 ++
 4 files changed, 23 insertions(+), 11 deletions(-)
 create mode 100644 packages/server/src/sdk/app/appMetadata/index.ts
 create mode 100644 packages/server/src/sdk/app/appMetadata/metadata.ts

diff --git a/packages/server/src/api/controllers/application.ts b/packages/server/src/api/controllers/application.ts
index 830acc55bf..660d35c29d 100644
--- a/packages/server/src/api/controllers/application.ts
+++ b/packages/server/src/api/controllers/application.ts
@@ -208,9 +208,8 @@ export async function fetchAppDefinition(
 export async function fetchAppPackage(
   ctx: UserCtx<void, FetchAppPackageResponse>
 ) {
-  const db = context.getAppDB()
   const appId = context.getAppId()
-  let application = await db.get<App>(DocumentType.APP_METADATA)
+  const application = await sdk.appMetadata.get()
   const layouts = await getLayouts()
   let screens = await getScreens()
   const license = await licensing.cache.getCachedLicense()
@@ -315,7 +314,7 @@ async function performAppCreate(ctx: UserCtx<CreateAppRequest, App>) {
     // If we used a template or imported an app there will be an existing doc.
     // Fetch and migrate some metadata from the existing app.
     try {
-      const existing: App = await db.get(DocumentType.APP_METADATA)
+      const existing = await sdk.appMetadata.get()
       const keys: (keyof App)[] = [
         "_rev",
         "navigation",
@@ -489,8 +488,7 @@ export async function update(
 
 export async function updateClient(ctx: UserCtx) {
   // Get current app version
-  const db = context.getAppDB()
-  const application = await db.get<App>(DocumentType.APP_METADATA)
+  const application = await sdk.appMetadata.get()
   const currentVersion = application.version
 
   let manifest
@@ -518,8 +516,7 @@ export async function updateClient(ctx: UserCtx) {
 
 export async function revertClient(ctx: UserCtx) {
   // Check app can be reverted
-  const db = context.getAppDB()
-  const application = await db.get<App>(DocumentType.APP_METADATA)
+  const application = await sdk.appMetadata.get()
   if (!application.revertableVersion) {
     ctx.throw(400, "There is no version to revert to")
   }
@@ -577,7 +574,7 @@ async function destroyApp(ctx: UserCtx) {
 
   const db = dbCore.getDB(devAppId)
   // standard app deletion flow
-  const app = await db.get<App>(DocumentType.APP_METADATA)
+  const app = await sdk.appMetadata.get()
   const result = await db.destroy()
   await quotas.removeApp()
   await events.app.deleted(app)
@@ -728,7 +725,7 @@ export async function updateAppPackage(
 ) {
   return context.doInAppContext(appId, async () => {
     const db = context.getAppDB()
-    const application = await db.get<App>(DocumentType.APP_METADATA)
+    const application = await sdk.appMetadata.get()
 
     const newAppPackage: App = { ...application, ...appPackage }
     if (appPackage._rev !== application._rev) {
@@ -754,7 +751,7 @@ export async function setRevertableVersion(
     return
   }
   const db = context.getAppDB()
-  const app = await db.get<App>(DocumentType.APP_METADATA)
+  const app = await sdk.appMetadata.get()
   app.revertableVersion = ctx.request.body.revertableVersion
   await db.put(app)
 
@@ -763,7 +760,7 @@ export async function setRevertableVersion(
 
 async function migrateAppNavigation() {
   const db = context.getAppDB()
-  const existing: App = await db.get(DocumentType.APP_METADATA)
+  const existing = await sdk.appMetadata.get()
   const layouts: Layout[] = await getLayouts()
   const screens: Screen[] = await getScreens()
 
diff --git a/packages/server/src/sdk/app/appMetadata/index.ts b/packages/server/src/sdk/app/appMetadata/index.ts
new file mode 100644
index 0000000000..267899a214
--- /dev/null
+++ b/packages/server/src/sdk/app/appMetadata/index.ts
@@ -0,0 +1,5 @@
+import * as metadata from "./metadata"
+
+export default {
+  ...metadata,
+}
diff --git a/packages/server/src/sdk/app/appMetadata/metadata.ts b/packages/server/src/sdk/app/appMetadata/metadata.ts
new file mode 100644
index 0000000000..78d2a040b9
--- /dev/null
+++ b/packages/server/src/sdk/app/appMetadata/metadata.ts
@@ -0,0 +1,8 @@
+import { context, DocumentType } from "@budibase/backend-core"
+import { App } from "@budibase/types"
+
+export async function get() {
+  const db = context.getAppDB()
+  const application = await db.get<App>(DocumentType.APP_METADATA)
+  return application
+}
diff --git a/packages/server/src/sdk/index.ts b/packages/server/src/sdk/index.ts
index a871546b60..44df8b50fb 100644
--- a/packages/server/src/sdk/index.ts
+++ b/packages/server/src/sdk/index.ts
@@ -11,6 +11,7 @@ import { default as plugins } from "./plugins"
 import * as views from "./app/views"
 import * as permissions from "./app/permissions"
 import * as rowActions from "./app/rowActions"
+import { default as appMetadata } from "./app/appMetadata"
 
 const sdk = {
   backups,
@@ -26,6 +27,7 @@ const sdk = {
   permissions,
   links,
   rowActions,
+  appMetadata,
 }
 
 // default export for TS

From ab517bf86da556af6f0c5ca9cd8dabb0e5d87dc8 Mon Sep 17 00:00:00 2001
From: Adria Navarro <adria@budibase.com>
Date: Thu, 17 Oct 2024 10:32:17 +0200
Subject: [PATCH 02/10] Persist created version

---
 .../server/src/api/controllers/application.ts | 24 +++++++++++--------
 .../src/sdk/app/appMetadata/metadata.ts       | 10 ++++++++
 packages/types/src/documents/app/app.ts       |  1 +
 3 files changed, 25 insertions(+), 10 deletions(-)

diff --git a/packages/server/src/api/controllers/application.ts b/packages/server/src/api/controllers/application.ts
index 660d35c29d..b8f523c331 100644
--- a/packages/server/src/api/controllers/application.ts
+++ b/packages/server/src/api/controllers/application.ts
@@ -271,6 +271,7 @@ async function performAppCreate(ctx: UserCtx<CreateAppRequest, App>) {
       path: ctx.request.body.file?.path,
     }
   }
+
   const tenantId = tenancy.isMultiTenant() ? tenancy.getTenantId() : null
   const appId = generateDevAppID(generateAppID(tenantId))
 
@@ -278,7 +279,7 @@ async function performAppCreate(ctx: UserCtx<CreateAppRequest, App>) {
     const instance = await createInstance(appId, instanceConfig)
     const db = context.getAppDB()
 
-    let newApplication: App = {
+    const newApplication: App = {
       _id: DocumentType.APP_METADATA,
       _rev: undefined,
       appId,
@@ -309,12 +310,18 @@ async function performAppCreate(ctx: UserCtx<CreateAppRequest, App>) {
         disableUserMetadata: true,
         skeletonLoader: true,
       },
+      creationVersion: undefined,
     }
 
+    const isImport = !!instanceConfig.file
+    if (!isImport) {
+      newApplication.creationVersion = envCore.VERSION
+    }
+
+    const existing = await sdk.appMetadata.tryGet()
     // If we used a template or imported an app there will be an existing doc.
     // Fetch and migrate some metadata from the existing app.
-    try {
-      const existing = await sdk.appMetadata.get()
+    if (existing) {
       const keys: (keyof App)[] = [
         "_rev",
         "navigation",
@@ -322,6 +329,7 @@ async function performAppCreate(ctx: UserCtx<CreateAppRequest, App>) {
         "customTheme",
         "icon",
         "snippets",
+        "creationVersion",
       ]
       keys.forEach(key => {
         if (existing[key]) {
@@ -339,14 +347,10 @@ async function performAppCreate(ctx: UserCtx<CreateAppRequest, App>) {
       }
 
       // Migrate navigation settings and screens if required
-      if (existing) {
-        const navigation = await migrateAppNavigation()
-        if (navigation) {
-          newApplication.navigation = navigation
-        }
+      const navigation = await migrateAppNavigation()
+      if (navigation) {
+        newApplication.navigation = navigation
       }
-    } catch (err) {
-      // Nothing to do
     }
 
     const response = await db.put(newApplication, { force: true })
diff --git a/packages/server/src/sdk/app/appMetadata/metadata.ts b/packages/server/src/sdk/app/appMetadata/metadata.ts
index 78d2a040b9..bbda55c085 100644
--- a/packages/server/src/sdk/app/appMetadata/metadata.ts
+++ b/packages/server/src/sdk/app/appMetadata/metadata.ts
@@ -1,8 +1,18 @@
 import { context, DocumentType } from "@budibase/backend-core"
 import { App } from "@budibase/types"
 
+/**
+ * @deprecated the plan is to get everything using `tryGet` instead, then rename
+ * `tryGet` to `get`.
+ */
 export async function get() {
   const db = context.getAppDB()
   const application = await db.get<App>(DocumentType.APP_METADATA)
   return application
 }
+
+export async function tryGet() {
+  const db = context.getAppDB()
+  const application = await db.tryGet<App>(DocumentType.APP_METADATA)
+  return application
+}
diff --git a/packages/types/src/documents/app/app.ts b/packages/types/src/documents/app/app.ts
index 69bf3489e1..06fca8307c 100644
--- a/packages/types/src/documents/app/app.ts
+++ b/packages/types/src/documents/app/app.ts
@@ -27,6 +27,7 @@ export interface App extends Document {
   usedPlugins?: Plugin[]
   upgradableVersion?: string
   snippets?: Snippet[]
+  creationVersion?: string
 }
 
 export interface AppInstance {

From 8cdc5be38e27cb8555d8c0aae14eacdcfa867d9a Mon Sep 17 00:00:00 2001
From: Adria Navarro <adria@budibase.com>
Date: Thu, 17 Oct 2024 11:36:02 +0200
Subject: [PATCH 03/10] Store proper version even on local

---
 packages/backend-core/src/environment.ts | 50 ++++++++++++++++--------
 1 file changed, 33 insertions(+), 17 deletions(-)

diff --git a/packages/backend-core/src/environment.ts b/packages/backend-core/src/environment.ts
index 2ab8c550cc..87dd2c5e87 100644
--- a/packages/backend-core/src/environment.ts
+++ b/packages/backend-core/src/environment.ts
@@ -54,30 +54,46 @@ function getPackageJsonFields(): {
   VERSION: string
   SERVICE_NAME: string
 } {
-  function findFileInAncestors(
-    fileName: string,
-    currentDir: string
-  ): string | null {
-    const filePath = `${currentDir}/${fileName}`
-    if (existsSync(filePath)) {
-      return filePath
+  function getParentFile(file: string) {
+    function findFileInAncestors(
+      fileName: string,
+      currentDir: string
+    ): string | null {
+      const filePath = `${currentDir}/${fileName}`
+      if (existsSync(filePath)) {
+        return filePath
+      }
+
+      const parentDir = `${currentDir}/..`
+      if (parentDir === currentDir) {
+        // reached root directory
+        return null
+      }
+
+      return findFileInAncestors(fileName, parentDir)
     }
 
-    const parentDir = `${currentDir}/..`
-    if (parentDir === currentDir) {
-      // reached root directory
-      return null
-    }
+    const packageJsonFile = findFileInAncestors(file, process.cwd())
+    const content = readFileSync(packageJsonFile!, "utf-8")
+    const parsedContent = JSON.parse(content)
+    return parsedContent
+  }
 
-    return findFileInAncestors(fileName, parentDir)
+  let localVersion: string | undefined
+  if (isDev()) {
+    try {
+      const lerna = getParentFile("lerna.json")
+      localVersion = lerna.version
+    } catch {
+      //
+    }
   }
 
   try {
-    const packageJsonFile = findFileInAncestors("package.json", process.cwd())
-    const content = readFileSync(packageJsonFile!, "utf-8")
-    const parsedContent = JSON.parse(content)
+    const parsedContent = getParentFile("package.json")
     return {
-      VERSION: process.env.BUDIBASE_VERSION || parsedContent.version,
+      VERSION:
+        localVersion || process.env.BUDIBASE_VERSION || parsedContent.version,
       SERVICE_NAME: parsedContent.name,
     }
   } catch {

From 15bb730c591f4b8879485ee20e36cc74c8414bc1 Mon Sep 17 00:00:00 2001
From: Adria Navarro <adria@budibase.com>
Date: Thu, 17 Oct 2024 11:37:41 +0200
Subject: [PATCH 04/10] Remove power role for apps created at >= 3.0.0

---
 packages/backend-core/src/security/roles.ts | 49 ++++++++++++++++-----
 1 file changed, 38 insertions(+), 11 deletions(-)

diff --git a/packages/backend-core/src/security/roles.ts b/packages/backend-core/src/security/roles.ts
index fa2d114d7d..ea4cb1b38a 100644
--- a/packages/backend-core/src/security/roles.ts
+++ b/packages/backend-core/src/security/roles.ts
@@ -1,3 +1,4 @@
+import semver from "semver"
 import { BuiltinPermissionID, PermissionLevel } from "./permissions"
 import {
   prefixRoleID,
@@ -7,7 +8,13 @@ import {
   doWithDB,
 } from "../db"
 import { getAppDB } from "../context"
-import { Screen, Role as RoleDoc, RoleUIMetadata } from "@budibase/types"
+import {
+  Screen,
+  Role as RoleDoc,
+  RoleUIMetadata,
+  Database,
+  App,
+} from "@budibase/types"
 import cloneDeep from "lodash/fp/cloneDeep"
 import { RoleColor } from "@budibase/shared-core"
 
@@ -23,14 +30,6 @@ const BUILTIN_IDS = {
   BUILDER: "BUILDER",
 }
 
-// exclude internal roles like builder
-const EXTERNAL_BUILTIN_ROLE_IDS = [
-  BUILTIN_IDS.ADMIN,
-  BUILTIN_IDS.POWER,
-  BUILTIN_IDS.BASIC,
-  BUILTIN_IDS.PUBLIC,
-]
-
 export const RoleIDVersion = {
   // original version, with a UUID based ID
   UUID: undefined,
@@ -319,7 +318,7 @@ export async function getAllRoles(appId?: string): Promise<RoleDoc[]> {
     }
     return internal(appDB)
   }
-  async function internal(db: any) {
+  async function internal(db: Database | undefined) {
     let roles: RoleDoc[] = []
     if (db) {
       const body = await db.allDocs(
@@ -334,8 +333,26 @@ export async function getAllRoles(appId?: string): Promise<RoleDoc[]> {
     }
     const builtinRoles = getBuiltinRoles()
 
+    // exclude internal roles like builder
+    let externalBuiltinRoles = []
+
+    if (db && !(await shouldIncludePowerRole(db))) {
+      externalBuiltinRoles = [
+        BUILTIN_IDS.ADMIN,
+        BUILTIN_IDS.POWER,
+        BUILTIN_IDS.BASIC,
+        BUILTIN_IDS.PUBLIC,
+      ]
+    } else {
+      externalBuiltinRoles = [
+        BUILTIN_IDS.ADMIN,
+        BUILTIN_IDS.BASIC,
+        BUILTIN_IDS.PUBLIC,
+      ]
+    }
+
     // need to combine builtin with any DB record of them (for sake of permissions)
-    for (let builtinRoleId of EXTERNAL_BUILTIN_ROLE_IDS) {
+    for (let builtinRoleId of externalBuiltinRoles) {
       const builtinRole = builtinRoles[builtinRoleId]
       const dbBuiltin = roles.filter(
         dbRole =>
@@ -366,6 +383,16 @@ export async function getAllRoles(appId?: string): Promise<RoleDoc[]> {
   }
 }
 
+async function shouldIncludePowerRole(db: Database) {
+  const app = await db.get<App>(DocumentType.APP_METADATA)
+  const { creationVersion } = app
+  if (semver.gte(creationVersion || "", "3.0.0")) {
+    return true
+  }
+
+  return false
+}
+
 export class AccessController {
   userHierarchies: { [key: string]: string[] }
   constructor() {

From 1155be45304ca24eb24850a3341b6d33729c5fe9 Mon Sep 17 00:00:00 2001
From: Adria Navarro <adria@budibase.com>
Date: Thu, 17 Oct 2024 11:52:03 +0200
Subject: [PATCH 05/10] Fix

---
 packages/backend-core/src/security/roles.ts | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/packages/backend-core/src/security/roles.ts b/packages/backend-core/src/security/roles.ts
index ea4cb1b38a..cd9217d600 100644
--- a/packages/backend-core/src/security/roles.ts
+++ b/packages/backend-core/src/security/roles.ts
@@ -336,7 +336,7 @@ export async function getAllRoles(appId?: string): Promise<RoleDoc[]> {
     // exclude internal roles like builder
     let externalBuiltinRoles = []
 
-    if (db && !(await shouldIncludePowerRole(db))) {
+    if (!db || (await shouldIncludePowerRole(db))) {
       externalBuiltinRoles = [
         BUILTIN_IDS.ADMIN,
         BUILTIN_IDS.POWER,
@@ -386,11 +386,13 @@ export async function getAllRoles(appId?: string): Promise<RoleDoc[]> {
 async function shouldIncludePowerRole(db: Database) {
   const app = await db.get<App>(DocumentType.APP_METADATA)
   const { creationVersion } = app
-  if (semver.gte(creationVersion || "", "3.0.0")) {
+  if (!creationVersion) {
+    // Old apps don't have creationVersion, so we should include it for backward compatibility
     return true
   }
 
-  return false
+  const isGreaterThan3x = semver.gte(creationVersion, "3.0.0")
+  return !isGreaterThan3x
 }
 
 export class AccessController {

From 8008d2ced1fa11b0e77b99bd98ddf7c27f801e37 Mon Sep 17 00:00:00 2001
From: Adria Navarro <adria@budibase.com>
Date: Thu, 17 Oct 2024 11:53:20 +0200
Subject: [PATCH 06/10] Fix all references

---
 packages/backend-core/src/security/roles.ts | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/packages/backend-core/src/security/roles.ts b/packages/backend-core/src/security/roles.ts
index cd9217d600..2fcde768bc 100644
--- a/packages/backend-core/src/security/roles.ts
+++ b/packages/backend-core/src/security/roles.ts
@@ -384,8 +384,8 @@ export async function getAllRoles(appId?: string): Promise<RoleDoc[]> {
 }
 
 async function shouldIncludePowerRole(db: Database) {
-  const app = await db.get<App>(DocumentType.APP_METADATA)
-  const { creationVersion } = app
+  const app = await db.tryGet<App>(DocumentType.APP_METADATA)
+  const creationVersion = app?.creationVersion
   if (!creationVersion) {
     // Old apps don't have creationVersion, so we should include it for backward compatibility
     return true

From 7bb69d7ffdb61353c12006d7356c87e82e7b09d6 Mon Sep 17 00:00:00 2001
From: Adria Navarro <adria@budibase.com>
Date: Thu, 17 Oct 2024 12:17:01 +0200
Subject: [PATCH 07/10] Add tests

---
 packages/backend-core/src/security/roles.ts   |  2 +-
 .../src/api/routes/global/tests/roles.spec.ts | 57 ++++++++++++++++++-
 2 files changed, 57 insertions(+), 2 deletions(-)

diff --git a/packages/backend-core/src/security/roles.ts b/packages/backend-core/src/security/roles.ts
index 2fcde768bc..fad5f7cb74 100644
--- a/packages/backend-core/src/security/roles.ts
+++ b/packages/backend-core/src/security/roles.ts
@@ -386,7 +386,7 @@ export async function getAllRoles(appId?: string): Promise<RoleDoc[]> {
 async function shouldIncludePowerRole(db: Database) {
   const app = await db.tryGet<App>(DocumentType.APP_METADATA)
   const creationVersion = app?.creationVersion
-  if (!creationVersion) {
+  if (!creationVersion || !semver.valid(creationVersion)) {
     // Old apps don't have creationVersion, so we should include it for backward compatibility
     return true
   }
diff --git a/packages/worker/src/api/routes/global/tests/roles.spec.ts b/packages/worker/src/api/routes/global/tests/roles.spec.ts
index 11de06328e..1ca9ff7e29 100644
--- a/packages/worker/src/api/routes/global/tests/roles.spec.ts
+++ b/packages/worker/src/api/routes/global/tests/roles.spec.ts
@@ -1,6 +1,6 @@
 import { structures, TestConfiguration } from "../../../../tests"
 import { context, db, permissions, roles } from "@budibase/backend-core"
-import { Database } from "@budibase/types"
+import { App, Database } from "@budibase/types"
 
 jest.mock("@budibase/backend-core", () => {
   const core = jest.requireActual("@budibase/backend-core")
@@ -30,6 +30,14 @@ async function addAppMetadata() {
   })
 }
 
+async function updateAppMetadata(update: Partial<Omit<App, "_id" | "_rev">>) {
+  const app = await appDb.get("app_metadata")
+  await appDb.put({
+    ...app,
+    ...update,
+  })
+}
+
 describe("/api/global/roles", () => {
   const config = new TestConfiguration()
 
@@ -69,6 +77,53 @@ describe("/api/global/roles", () => {
       expect(res.body[appId].roles.length).toEqual(5)
       expect(res.body[appId].roles.map((r: any) => r._id)).toContain(ROLE_NAME)
     })
+
+    it.each(["3.0.0", "3.0.1", "3.1.0"])(
+      "exclude POWER roles after v3 (%s)",
+      async creationVersion => {
+        await updateAppMetadata({ creationVersion })
+        const res = await config.api.roles.get()
+        expect(res.body).toBeDefined()
+        expect(res.body[appId].roles.map((r: any) => r._id)).toEqual([
+          ROLE_NAME,
+          roles.BUILTIN_ROLE_IDS.ADMIN,
+          roles.BUILTIN_ROLE_IDS.BASIC,
+          roles.BUILTIN_ROLE_IDS.PUBLIC,
+        ])
+      }
+    )
+
+    it.each(["2.9.0", "1.0.0"])(
+      "include POWER roles before v3 (%s)",
+      async creationVersion => {
+        await updateAppMetadata({ creationVersion })
+        const res = await config.api.roles.get()
+        expect(res.body).toBeDefined()
+        expect(res.body[appId].roles.map((r: any) => r._id)).toEqual([
+          ROLE_NAME,
+          roles.BUILTIN_ROLE_IDS.ADMIN,
+          roles.BUILTIN_ROLE_IDS.POWER,
+          roles.BUILTIN_ROLE_IDS.BASIC,
+          roles.BUILTIN_ROLE_IDS.PUBLIC,
+        ])
+      }
+    )
+
+    it.each(["invalid", ""])(
+      "include POWER roles when the version is corrupted (%s)",
+      async creationVersion => {
+        await updateAppMetadata({ creationVersion })
+        const res = await config.api.roles.get()
+
+        expect(res.body[appId].roles.map((r: any) => r._id)).toEqual([
+          ROLE_NAME,
+          roles.BUILTIN_ROLE_IDS.ADMIN,
+          roles.BUILTIN_ROLE_IDS.POWER,
+          roles.BUILTIN_ROLE_IDS.BASIC,
+          roles.BUILTIN_ROLE_IDS.PUBLIC,
+        ])
+      }
+    )
   })
 
   describe("GET api/global/roles/:appId", () => {

From c1128ffe2a953f3f4f1e1056083541eb8787d2cc Mon Sep 17 00:00:00 2001
From: Adria Navarro <adria@budibase.com>
Date: Thu, 17 Oct 2024 12:20:17 +0200
Subject: [PATCH 08/10] Fix test

---
 packages/backend-core/src/environment.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/backend-core/src/environment.ts b/packages/backend-core/src/environment.ts
index 87dd2c5e87..4e93e8d9ee 100644
--- a/packages/backend-core/src/environment.ts
+++ b/packages/backend-core/src/environment.ts
@@ -80,7 +80,7 @@ function getPackageJsonFields(): {
   }
 
   let localVersion: string | undefined
-  if (isDev()) {
+  if (isDev() && !isTest()) {
     try {
       const lerna = getParentFile("lerna.json")
       localVersion = lerna.version

From ca974cf2f509f4497f571ef8723de994da29261a Mon Sep 17 00:00:00 2001
From: Adria Navarro <adria@budibase.com>
Date: Thu, 17 Oct 2024 16:30:22 +0200
Subject: [PATCH 09/10] Renames

---
 .../server/src/api/controllers/application.ts    | 16 ++++++++--------
 packages/server/src/sdk/app/appMetadata/index.ts |  5 -----
 .../server/src/sdk/app/applications/index.ts     |  2 ++
 .../{appMetadata => applications}/metadata.ts    |  0
 packages/server/src/sdk/index.ts                 |  2 --
 5 files changed, 10 insertions(+), 15 deletions(-)
 delete mode 100644 packages/server/src/sdk/app/appMetadata/index.ts
 rename packages/server/src/sdk/app/{appMetadata => applications}/metadata.ts (100%)

diff --git a/packages/server/src/api/controllers/application.ts b/packages/server/src/api/controllers/application.ts
index b8f523c331..59f67540fe 100644
--- a/packages/server/src/api/controllers/application.ts
+++ b/packages/server/src/api/controllers/application.ts
@@ -209,7 +209,7 @@ export async function fetchAppPackage(
   ctx: UserCtx<void, FetchAppPackageResponse>
 ) {
   const appId = context.getAppId()
-  const application = await sdk.appMetadata.get()
+  const application = await sdk.applications.metadata.get()
   const layouts = await getLayouts()
   let screens = await getScreens()
   const license = await licensing.cache.getCachedLicense()
@@ -318,7 +318,7 @@ async function performAppCreate(ctx: UserCtx<CreateAppRequest, App>) {
       newApplication.creationVersion = envCore.VERSION
     }
 
-    const existing = await sdk.appMetadata.tryGet()
+    const existing = await sdk.applications.metadata.tryGet()
     // If we used a template or imported an app there will be an existing doc.
     // Fetch and migrate some metadata from the existing app.
     if (existing) {
@@ -492,7 +492,7 @@ export async function update(
 
 export async function updateClient(ctx: UserCtx) {
   // Get current app version
-  const application = await sdk.appMetadata.get()
+  const application = await sdk.applications.metadata.get()
   const currentVersion = application.version
 
   let manifest
@@ -520,7 +520,7 @@ export async function updateClient(ctx: UserCtx) {
 
 export async function revertClient(ctx: UserCtx) {
   // Check app can be reverted
-  const application = await sdk.appMetadata.get()
+  const application = await sdk.applications.metadata.get()
   if (!application.revertableVersion) {
     ctx.throw(400, "There is no version to revert to")
   }
@@ -578,7 +578,7 @@ async function destroyApp(ctx: UserCtx) {
 
   const db = dbCore.getDB(devAppId)
   // standard app deletion flow
-  const app = await sdk.appMetadata.get()
+  const app = await sdk.applications.metadata.get()
   const result = await db.destroy()
   await quotas.removeApp()
   await events.app.deleted(app)
@@ -729,7 +729,7 @@ export async function updateAppPackage(
 ) {
   return context.doInAppContext(appId, async () => {
     const db = context.getAppDB()
-    const application = await sdk.appMetadata.get()
+    const application = await sdk.applications.metadata.get()
 
     const newAppPackage: App = { ...application, ...appPackage }
     if (appPackage._rev !== application._rev) {
@@ -755,7 +755,7 @@ export async function setRevertableVersion(
     return
   }
   const db = context.getAppDB()
-  const app = await sdk.appMetadata.get()
+  const app = await sdk.applications.metadata.get()
   app.revertableVersion = ctx.request.body.revertableVersion
   await db.put(app)
 
@@ -764,7 +764,7 @@ export async function setRevertableVersion(
 
 async function migrateAppNavigation() {
   const db = context.getAppDB()
-  const existing = await sdk.appMetadata.get()
+  const existing = await sdk.applications.metadata.get()
   const layouts: Layout[] = await getLayouts()
   const screens: Screen[] = await getScreens()
 
diff --git a/packages/server/src/sdk/app/appMetadata/index.ts b/packages/server/src/sdk/app/appMetadata/index.ts
deleted file mode 100644
index 267899a214..0000000000
--- a/packages/server/src/sdk/app/appMetadata/index.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import * as metadata from "./metadata"
-
-export default {
-  ...metadata,
-}
diff --git a/packages/server/src/sdk/app/applications/index.ts b/packages/server/src/sdk/app/applications/index.ts
index 04ed3b2919..f315e84896 100644
--- a/packages/server/src/sdk/app/applications/index.ts
+++ b/packages/server/src/sdk/app/applications/index.ts
@@ -2,10 +2,12 @@ import * as sync from "./sync"
 import * as utils from "./utils"
 import * as applications from "./applications"
 import * as imports from "./import"
+import * as metadata from "./metadata"
 
 export default {
   ...sync,
   ...utils,
   ...applications,
   ...imports,
+  metadata,
 }
diff --git a/packages/server/src/sdk/app/appMetadata/metadata.ts b/packages/server/src/sdk/app/applications/metadata.ts
similarity index 100%
rename from packages/server/src/sdk/app/appMetadata/metadata.ts
rename to packages/server/src/sdk/app/applications/metadata.ts
diff --git a/packages/server/src/sdk/index.ts b/packages/server/src/sdk/index.ts
index 44df8b50fb..a871546b60 100644
--- a/packages/server/src/sdk/index.ts
+++ b/packages/server/src/sdk/index.ts
@@ -11,7 +11,6 @@ import { default as plugins } from "./plugins"
 import * as views from "./app/views"
 import * as permissions from "./app/permissions"
 import * as rowActions from "./app/rowActions"
-import { default as appMetadata } from "./app/appMetadata"
 
 const sdk = {
   backups,
@@ -27,7 +26,6 @@ const sdk = {
   permissions,
   links,
   rowActions,
-  appMetadata,
 }
 
 // default export for TS

From 4c688b9734a95b19110b79419b5208928fa19a8d Mon Sep 17 00:00:00 2001
From: Adria Navarro <adria@budibase.com>
Date: Thu, 17 Oct 2024 16:40:26 +0200
Subject: [PATCH 10/10] Add more tests

---
 packages/worker/src/api/routes/global/tests/roles.spec.ts | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/packages/worker/src/api/routes/global/tests/roles.spec.ts b/packages/worker/src/api/routes/global/tests/roles.spec.ts
index 1ca9ff7e29..28977d8521 100644
--- a/packages/worker/src/api/routes/global/tests/roles.spec.ts
+++ b/packages/worker/src/api/routes/global/tests/roles.spec.ts
@@ -78,7 +78,7 @@ describe("/api/global/roles", () => {
       expect(res.body[appId].roles.map((r: any) => r._id)).toContain(ROLE_NAME)
     })
 
-    it.each(["3.0.0", "3.0.1", "3.1.0"])(
+    it.each(["3.0.0", "3.0.1", "3.1.0", "3.0.0+2146.b125a7c"])(
       "exclude POWER roles after v3 (%s)",
       async creationVersion => {
         await updateAppMetadata({ creationVersion })
@@ -93,7 +93,7 @@ describe("/api/global/roles", () => {
       }
     )
 
-    it.each(["2.9.0", "1.0.0"])(
+    it.each(["2.9.0", "1.0.0", "0.0.0", "2.32.17+2146.b125a7c"])(
       "include POWER roles before v3 (%s)",
       async creationVersion => {
         await updateAppMetadata({ creationVersion })