diff --git a/packages/server/.vscode/launch.json b/packages/server/.vscode/launch.json index 023c59ffd3..23fcc70dc3 100644 --- a/packages/server/.vscode/launch.json +++ b/packages/server/.vscode/launch.json @@ -13,9 +13,22 @@ { "type": "node", "request": "launch", - "name": "Jest All", + "name": "Jest App Server", "program": "${workspaceFolder}/node_modules/.bin/jest", - "args": ["--runInBand"], + "args": ["apps", "--runInBand"], + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen", + "disableOptimisticBPs": true, + "windows": { + "program": "${workspaceFolder}/node_modules/jest-cli/bin/jest", + } + }, + { + "type": "node", + "request": "launch", + "name": "Jest Builder", + "program": "${workspaceFolder}/node_modules/.bin/jest", + "args": ["builder", "--runInBand"], "console": "integratedTerminal", "internalConsoleOptions": "neverOpen", "disableOptimisticBPs": true, diff --git a/packages/server/appPackages/testApp/components/myTextBox.json b/packages/server/appPackages/testApp/components/myTextBox.json new file mode 100644 index 0000000000..e76040fb63 --- /dev/null +++ b/packages/server/appPackages/testApp/components/myTextBox.json @@ -0,0 +1,4 @@ +{ + "_component": "./customComponents/textbox", + "label": "hello" +} \ No newline at end of file diff --git a/packages/server/appPackages/testApp/components/subfolder/otherTextBox.json b/packages/server/appPackages/testApp/components/subfolder/otherTextBox.json new file mode 100644 index 0000000000..038c1f094b --- /dev/null +++ b/packages/server/appPackages/testApp/components/subfolder/otherTextBox.json @@ -0,0 +1,4 @@ +{ + "_component": "./moreCustomComponents/textbox", + "label": "hello" +} \ No newline at end of file diff --git a/packages/server/appPackages/testApp/myComponents/components.json b/packages/server/appPackages/testApp/customComponents/components.json similarity index 100% rename from packages/server/appPackages/testApp/myComponents/components.json rename to packages/server/appPackages/testApp/customComponents/components.json diff --git a/packages/server/appPackages/testApp/myComponents/textbox/index.js b/packages/server/appPackages/testApp/customComponents/textbox/index.js similarity index 100% rename from packages/server/appPackages/testApp/myComponents/textbox/index.js rename to packages/server/appPackages/testApp/customComponents/textbox/index.js diff --git a/packages/server/appPackages/testApp/dist/package.tar.gz b/packages/server/appPackages/testApp/dist/package.tar.gz index 43818edf6a..1af8254fa4 100644 Binary files a/packages/server/appPackages/testApp/dist/package.tar.gz and b/packages/server/appPackages/testApp/dist/package.tar.gz differ diff --git a/packages/server/appPackages/testApp/moreCustomComponents/components.json b/packages/server/appPackages/testApp/moreCustomComponents/components.json new file mode 100644 index 0000000000..3b11bb76a3 --- /dev/null +++ b/packages/server/appPackages/testApp/moreCustomComponents/components.json @@ -0,0 +1,11 @@ +{ + "textbox" : { + "path": "./textbox", + "name": "Textbox", + "description": "A text input, with a label", + "props": { + "label": "string" + }, + "tags": ["textboxt", "input", "text"] + } +} \ No newline at end of file diff --git a/packages/server/appPackages/testApp/moreCustomComponents/textbox/index.js b/packages/server/appPackages/testApp/moreCustomComponents/textbox/index.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/server/appPackages/testApp/pages.json b/packages/server/appPackages/testApp/pages.json index 9d38c2c9de..2ac68a995d 100644 --- a/packages/server/appPackages/testApp/pages.json +++ b/packages/server/appPackages/testApp/pages.json @@ -15,5 +15,5 @@ }, "appBody" : "./unauthenticated.app.json" }, - "componentLibraries": ["./myComponents"] + "componentLibraries": ["./customComponents", "./moreCustomComponents"] } \ No newline at end of file diff --git a/packages/server/jest.config.js.later b/packages/server/jest.config.js.later deleted file mode 100644 index e683c93c32..0000000000 --- a/packages/server/jest.config.js.later +++ /dev/null @@ -1,12 +0,0 @@ -const Sequencer = require('@jest/test-sequencer').default; - -const testOrder = [""] - -class CustomSequencer extends Sequencer { - sort(tests) { - // Test structure information - // https://github.com/facebook/jest/blob/6b8b1404a1d9254e7d5d90a8934087a9c9899dab/packages/jest-runner/src/types.ts#L17-L21 - const copyTests = Array.from(tests); - return copyTests.sort((testA, testB) => (testA.path > testB.path ? 1 : -1)); - } -} diff --git a/packages/server/middleware/routers.js b/packages/server/middleware/routers.js index 42ed712caa..2c496e0754 100644 --- a/packages/server/middleware/routers.js +++ b/packages/server/middleware/routers.js @@ -4,13 +4,10 @@ const StatusCodes = require("../utilities/statusCodes"); const fs = require("fs"); const { resolve } = require("path"); const send = require('koa-send'); -const { getPackageForBuilder, getComponents, +const { getPackageForBuilder, getRootComponents: getComponents, savePackage, getApps } = require("../utilities/builder"); const builderPath = resolve(__dirname, "../builder"); -const appUiPath = appname => { - -} module.exports = (config, app) => { @@ -33,21 +30,27 @@ module.exports = (config, app) => { ctx.throw(StatusCodes.NOT_FOUND, "App Name not declared"); } - const instance = await ctx.master.getInstanceApiForSession( - pathParts[1], - ctx.sessionId); - + const appname = pathParts[1]; - ctx.instance = instance.instance; - ctx.publicPath = instance.publicPath; - ctx.isAuthenticated = !!instance.instance; - - await next(); + if(appname === "_builder") { + await next(); + } else { + const instance = await ctx.master.getInstanceApiForSession( + appname, + ctx.sessionId); + + + ctx.instance = instance.instance; + ctx.publicPath = instance.publicPath; + ctx.isAuthenticated = !!instance.instance; + + await next(); + } }) .get("/_builder", async (ctx) => { if(!config.dev) { - ctx.request.status = StatusCodes.FORBIDDEN; - ctx.request.body = "run in dev mode to access builder"; + ctx.response.status = StatusCodes.FORBIDDEN; + ctx.body = "run in dev mode to access builder"; return; } @@ -56,8 +59,8 @@ module.exports = (config, app) => { }) .get("/_builder/*", async (ctx, next) => { if(!config.dev) { - ctx.request.status = StatusCodes.FORBIDDEN; - ctx.request.body = "run in dev mode to access builder"; + ctx.response.status = StatusCodes.FORBIDDEN; + ctx.body = "run in dev mode to access builder"; return; } @@ -117,8 +120,8 @@ module.exports = (config, app) => { }) .get("/_builder/api/apps", async (ctx) => { if(!config.dev) { - ctx.request.status = StatusCodes.FORBIDDEN; - ctx.request.body = "run in dev mode to access builder"; + ctx.response.status = StatusCodes.FORBIDDEN; + ctx.response.body = "run in dev mode to access builder"; return; } @@ -128,8 +131,8 @@ module.exports = (config, app) => { }) .get("/_builder/api/:appname/appPackage", async (ctx) => { if(!config.dev) { - ctx.request.status = StatusCodes.FORBIDDEN; - ctx.request.body = "run in dev mode to access builder"; + ctx.response.status = StatusCodes.FORBIDDEN; + ctx.body = "run in dev mode to access builder"; return; } @@ -141,7 +144,7 @@ module.exports = (config, app) => { }) .post("/_builder/api/:appname/appPackage", async (ctx) => { if(!config.dev) { - ctx.request.status = StatusCodes.FORBIDDEN; + ctx.response.status = StatusCodes.FORBIDDEN; ctx.body = "run in dev mode to access builder"; return; } @@ -154,7 +157,7 @@ module.exports = (config, app) => { }) .get("/_builder/api/:appname/components", async (ctx) => { if(!config.dev) { - ctx.request.status = StatusCodes.FORBIDDEN; + ctx.response.status = StatusCodes.FORBIDDEN; ctx.body = "run in dev mode to access builder"; return; } diff --git a/packages/server/tests/all.spec.js b/packages/server/tests/apps.spec.js similarity index 100% rename from packages/server/tests/all.spec.js rename to packages/server/tests/apps.spec.js diff --git a/packages/server/tests/builder.spec.js b/packages/server/tests/builder.spec.js new file mode 100644 index 0000000000..91c67f9a1b --- /dev/null +++ b/packages/server/tests/builder.spec.js @@ -0,0 +1,66 @@ + +const testAppDef = require("../appPackages/testApp/appDefinition.json"); +const testAccessLevels = require("../appPackages/testApp/access_levels.json"); +const testPages = require("../appPackages/testApp/pages.json"); +const testComponents = require("../appPackages/testApp/customComponents/components.json"); +const testMoreComponents = require("../appPackages/testApp/moreCustomComponents/components.json"); +const statusCodes = require("../utilities/statusCodes"); +const derivedComponent1 = require("../appPackages/testApp/components/myTextBox.json"); +const derivedComponent2 = require("../appPackages/testApp/components/subfolder/otherTextBox.json"); + +const app = require("./testApp")(); + +beforeAll(async () => await app.start()); +afterAll(async () => await app.destroy()); + + +it("/apppackage should get appDefinition", async () => { + + const {body} = await app.get("/_builder/api/testApp/appPackage") + .expect(statusCodes.OK); + + expect(body.appDefinition).toEqual(testAppDef); +}); + +it("/apppackage should get access levels", async () => { + + const {body} = await app.get("/_builder/api/testApp/appPackage") + .expect(statusCodes.OK); + + expect(body.accessLevels).toEqual(testAccessLevels); +}); + +it("/apppackage should get pages", async () => { + + const {body} = await app.get("/_builder/api/testApp/appPackage") + .expect(statusCodes.OK); + expect(body.pages).toEqual(testPages); +}); + +it("/apppackage should get rootComponents", async () => { + + const {body} = await app.get("/_builder/api/testApp/appPackage") + .expect(statusCodes.OK); + + expect(body.rootComponents["./customComponents/textbox"]).toBeDefined(); + expect(body.rootComponents["./moreCustomComponents/textbox"]).toBeDefined(); + + expect(body.rootComponents["./customComponents/textbox"]) + .toEqual(testComponents.textbox); + + expect(body.rootComponents["./moreCustomComponents/textbox"]) + .toEqual(testMoreComponents.textbox); +}); + +it("/apppackage should get derivedComponents", async () => { + + const {body} = await app.get("/_builder/api/testApp/appPackage") + .expect(statusCodes.OK); + + const expectedComponents = { + "myTextBox" : {...derivedComponent1, _name:"myTextBox"}, + "subfolder/otherTextBox": {...derivedComponent2, _name:"subfolder/otherTextBox"} + }; + + expect(body.derivedComponents).toEqual(expectedComponents); +}); diff --git a/packages/server/tests/testApp.js b/packages/server/tests/testApp.js index 2009a9aa69..e7cd9adac5 100644 --- a/packages/server/tests/testApp.js +++ b/packages/server/tests/testApp.js @@ -46,7 +46,8 @@ const config = { port: 4002, latestPackagesFolder: "./appPackages", extraMasterPlugins, - customizeMaster + customizeMaster, + dev:true } diff --git a/packages/server/utilities/builder.js b/packages/server/utilities/builder.js index f98ca2df87..724d2b1009 100644 --- a/packages/server/utilities/builder.js +++ b/packages/server/utilities/builder.js @@ -6,20 +6,29 @@ const { writeFile, readFile, readdir, - exists + exists, + stat } = require("./fsawait"); -const { resolve } = require("path"); +const { + resolve, + join +} = require("path"); const { $ } = require("budibase-core").common; const { keys, reduce, - map, - flatten, - some + some, + keyBy } = require("lodash/fp"); +const {merge} = require("lodash"); module.exports.getPackageForBuilder = async (config, appname) => { const appPath = appPackageFolder(config, appname); + + const pages = JSON.parse(await readFile( + `${appPath}/pages.json`, + "utf8")); + return ({ appDefinition: JSON.parse(await readFile( `${appPath}/appDefinition.json`, @@ -29,9 +38,12 @@ module.exports.getPackageForBuilder = async (config, appname) => { `${appPath}/access_levels.json`, "utf8")), - pages: JSON.parse(await readFile( - `${appPath}/pages.json`, - "utf8")) + pages, + + rootComponents: await getRootComponents(appPath, pages), + + derivedComponents: keyBy("_name")( + await fetchDerivedComponents(appPath)) }) } @@ -50,7 +62,7 @@ module.exports.savePackage = async (config, appname, pkg) => { await writeFile( `${appPath}/pages.json`, - JSON.stringify(pkg.accessLevels), + JSON.stringify(pkg.pages), "utf8"); } @@ -58,9 +70,9 @@ module.exports.getApps = async (config) => await readdir(appsFolder(config)); -module.exports.getComponents = async (config, appname, lib) => { +const getRootComponents = async (appPath, pages ,lib) => { - const componentsInLibrary = (libname) => { + const componentsInLibrary = async (libname) => { const isRelative = some(c => c === libname.substring(0,1)) ("./~\\".split("")); @@ -77,10 +89,10 @@ module.exports.getComponents = async (config, appname, lib) => { let components; try { components = JSON.parse( - readFile(componentsPath, "utf8")); + await readFile(componentsPath, "utf8")); } catch(e) { - const e = new Error(`could not parse JSON - ${componentsPath} `); - throw e; + const err = `could not parse JSON - ${componentsPath} : ${e.message}`; + throw new Error(err); } return $(components, [ @@ -94,9 +106,7 @@ module.exports.getComponents = async (config, appname, lib) => { let libs; if(!lib) { - const appPath = appPackageFolder(config, appname); - - const pages = JSON.parse(await readFile( + pages = pages || JSON.parse(await readFile( `${appPath}/pages.json`, "utf8")); @@ -107,9 +117,51 @@ module.exports.getComponents = async (config, appname, lib) => { libs = [lib]; } - return $(libs, [ - map(componentsInLibrary), - flatten - ]); + const components = {}; + for(let l of libs) { + merge(components, await componentsInLibrary(l)) + } + + return components; } +const fetchDerivedComponents = async (appPath, relativePath = "") => { + + const currentDir = join(appPath, "components", relativePath); + + const contents = await readdir(currentDir); + + const components = []; + + for(let item of contents) { + const itemRelativePath = join(relativePath, item); + const itemFullPath = join(currentDir, item); + const stats = await stat(itemFullPath); + + if(stats.isFile()) { + + if(!item.endsWith(".json")) continue; + + const component = JSON.parse( + await readFile(itemFullPath, "utf8")); + + component._name = itemRelativePath + .substring(0, itemRelativePath.length - 5) + .replace(/\\/g, "/"); + + components.push(component); + } else { + const childComponents = await fetchDerivedComponents( + appPath, join(relativePath, item) + ); + + for(let c of childComponents) { + components.push(c); + } + } + } + + return components; +} + +module.exports.getRootComponents = getRootComponents; \ No newline at end of file