extra index template for production

This commit is contained in:
Martin McKeaveney 2020-07-06 19:43:40 +01:00
parent 63ca4c094e
commit f7d65deb5e
13 changed files with 177 additions and 84 deletions

View File

@ -32,4 +32,36 @@ jobs:
- run: yarn test
env:
CI: true
name: Budibase CI
name: Budibase CI
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Build, tag, and push image to Amazon ECR
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: my-ecr-repo
IMAGE_TAG: ${{ github.sha }}
run: |
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"
- name: Fill in the new image ID in the Amazon ECS task definition
id: task-def
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: my-container
image: ${{ steps.build-image.outputs.image }}
- name: Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def.outputs.task-definition }}
service: my-service
cluster: my-cluster
wait-for-service-stability: true

View File

@ -23,8 +23,7 @@ export const fetchComponentLibModules = async application => {
const allLibraries = {}
for (let libraryName of application.componentLibraries) {
// const LIBRARY_URL = `/${application._id}/componentlibrary?library=${libraryName}`
const LIBRARY_URL = `/assets/componentlibrary/${libraryName}/dist/index.js`
const LIBRARY_URL = `/${application._id}/componentlibrary?library=${libraryName}`
const libraryModule = await import(LIBRARY_URL)
allLibraries[libraryName] = libraryModule
}

View File

@ -3,10 +3,15 @@
import { store } from "builderStore"
import { notifier } from "builderStore/store/notifications"
import api from "builderStore/api"
import Spinner from "components/common/Spinner.svelte"
let deployed = false
let loading = false
$: appId = $store.appId
async function deployApp() {
loading = true
const DEPLOY_URL = `/deploy`
try {
@ -14,12 +19,15 @@
const response = await api.post(DEPLOY_URL)
const json = await response.json()
if (response.status !== 200) {
throw new Error
throw new Error()
}
notifier.success(`Deployment Complete. View your app at blah URL https://${appId}.app.budi.live/${appId}`)
notifier.success(`Your Deployment is Complete.`)
deployed = true
loading = false
} catch (err) {
notifier.danger("Deployment unsuccessful. Please try again later.")
loading = false
}
}
</script>
@ -27,9 +35,18 @@
<section>
<div>
<h4>It's time to shine!</h4>
<Button secondary medium on:click={deployApp}>
Deploy App
</Button>
{#if deployed}
<a target="_blank" href={`https://${appId}.app.budi.live/${appId}`}>
View App
</a>
{:else}
<Button secondary medium on:click={deployApp}>
Deploy App
{#if loading}
<Spinner ratio={"0.5"} />
{/if}
</Button>
{/if}
</div>
<img src="/_builder/assets/deploy-rocket.jpg" />
</section>
@ -53,11 +70,11 @@
flex-direction: column;
align-items: center;
justify-content: center;
left: 0;
right: 0;
left: 0;
right: 0;
top: 20%;
margin-left: auto;
margin-right: auto;
margin-left: auto;
margin-right: auto;
width: 50%;
}
</style>

View File

@ -8,7 +8,7 @@ module.exports = ({ dir }) => {
// dont make this a variable or top level require
// ti will cause environment module to be loaded prematurely
require("@budibase/server/src/app")().then(server => {
return require("@budibase/server/src/app")().then(server => {
server.on("close", () => console.log("Server Closed"))
console.log(`Budibase running on ${JSON.stringify(server.address())}`)
})

View File

@ -20,10 +20,10 @@ export const loadBudibase = async opts => {
for (let library of libraries) {
// fetch the JavaScript for the component libraries from the server
// componentLibraryModules[library] = await import(
// `/componentlibrary?library=${encodeURI(library)}`
// )
componentLibraryModules[library] = await import(`/assets/componentlibrary/${library}/dist/index.js`)
componentLibraryModules[library] = await import(
`/componentlibrary?library=${encodeURI(library)}`
)
// componentLibraryModules[library] = await import(`/assets/componentlibrary/${library}/dist/index.js`)
}
componentLibraryModules[builtinLibName] = builtins(_window)

View File

@ -12,4 +12,8 @@ JWT_SECRET={{cookieKey1}}
PORT=4001
# error level for koa-pino
LOG_LEVEL=error
LOG_LEVEL=error
DEPLOYMENT_CF_DISTRIBUTION_ID=
DEPLOYMENT_APP_ASSETS_BUCKET=g
DEPLOYMENT_CREDENTIALS_URL="https://dt4mpwwap8.execute-api.eu-west-1.amazonaws.com/prod/"

View File

@ -2,6 +2,8 @@ FROM node:12-alpine
WORKDIR /app
ENV CLOUD=1
# copy files and install dependencies
COPY . ./
RUN yarn

17
packages/server/envfile Normal file
View File

@ -0,0 +1,17 @@
# url of couch db, including username and password
# http://admin:password@localhost:5984
COUCH_DB_URL=
# identifies a client database - i.e. group of apps
CLIENT_ID=1
# used to create cookie hashes
JWT_SECRET=4715888e-144f-4802-b8d8-862a1b8365dd
# port to run http server on
PORT=4001
# error level for koa-pino
LOG_LEVEL=error
COUCH_DB_REMOTE=https://admin:afasdgafgF342G@couchdb.budi.live:5984
BUDIBASE_APP_ASSETS_BUCKET=prod-budi-app-assets
BUDIBASE_API_KEY=d498278c-4ab4-144b-c212-b8f9e6da5c2b

View File

@ -5,15 +5,28 @@ const {
budibaseAppsDir,
} = require("../../../utilities/budibaseDir")
async function invalidateCDN(appId) {
const cf = new AWS.CloudFront({})
return cf.createInvalidation({
DistributionId: process.env.DEPLOYMENT_CF_DISTRIBUTION_ID,
InvalidationBatch: {
CallerReference: appId,
Paths: {
Quantity: 1,
Items: [
`/assets/${appId}/*`
]
}
}
}).promise()
}
async function fetchTemporaryCredentials() {
const CREDENTIALS_URL = "https://dt4mpwwap8.execute-api.eu-west-1.amazonaws.com/prod/"
const BUDIBASE_API_KEY = process.env.BUDIBASE_API_KEY
const response = await fetch(CREDENTIALS_URL, {
const response = await fetch(process.env.DEPLOYMENT_CREDENTIALS_URL, {
method: "POST",
body: JSON.stringify({
apiKey: BUDIBASE_API_KEY
apiKey: process.env.BUDIBASE_API_KEY
})
})
@ -32,6 +45,27 @@ const CONTENT_TYPE_MAP = {
js: "application/javascript"
};
/**
* Recursively walk a directory tree and execute a callback on all files.
* @param {Re} dirPath - Directory to traverse
* @param {*} callback - callback to execute on files
*/
function walkDir(dirPath, callback) {
for (let filename of fs.readdirSync(dirPath)) {
const filePath = `${dirPath}/${filename}`
const stat = fs.lstatSync(filePath)
if (stat.isFile()) {
callback({
bytes: fs.readFileSync(filePath),
filename
})
} else {
walkDir(filePath, callback)
}
}
}
exports.uploadAppAssets = async function ({ appId }) {
const { credentials, accountId } = await fetchTemporaryCredentials()
@ -43,7 +77,7 @@ exports.uploadAppAssets = async function ({ appId }) {
const s3 = new AWS.S3({
params: {
Bucket: process.env.BUDIBASE_APP_ASSETS_BUCKET
Bucket: process.env.DEPLOYMENT_APP_ASSETS_BUCKET
}
})
@ -54,34 +88,27 @@ exports.uploadAppAssets = async function ({ appId }) {
const uploads = []
for (let page of appPages) {
for (let filename of fs.readdirSync(`${appAssetsPath}/${page}`)) {
const filePath = `${appAssetsPath}/${page}/${filename}`
const stat = await fs.lstatSync(filePath)
// TODO: need to account for recursively traversing dirs
if (stat.isFile()) {
const fileBytes = fs.readFileSync(`${appAssetsPath}/${page}/${filename}`)
walkDir(`${appAssetsPath}/${page}`, function prepareUploadsForS3({ bytes, filename }) {
const fileExtension = [...filename.split(".")].pop()
console.log(`${appId}/${page}/${filename}`)
const upload = s3.upload({
Key: `assets/${appId}/${page}/${filename}`,
Body: bytes,
ContentType: CONTENT_TYPE_MAP[fileExtension],
Metadata: {
accountId
}
}).promise()
const fileExtension = [...filename.split(".")].pop()
const upload = s3.upload({
Key: `assets/${appId}/${page}/${filename}`,
Body: fileBytes,
ContentType: CONTENT_TYPE_MAP[fileExtension],
Metadata: {
accountId
}
}).promise()
uploads.push(upload)
}
}
uploads.push(upload)
})
}
try {
return Promise.all(uploads)
const uploadAllFiles = Promise.all(uploads)
const invalidateCloudfront = invalidateCDN(appId)
await uploadAllFiles
await invalidateCloudfront
} catch (err) {
console.error("Error uploading budibase app assets to s3", err)
throw err

View File

@ -44,19 +44,15 @@ exports.serveApp = async function(ctx) {
})
}
const { file = "index.html" } = ctx
if (ctx.isCloud) {
const S3_URL = `https://${ctx.params.appId}.app.budi.live/assets/${ctx.params.appId}/${mainOrAuth}/${file}`
console.log("Serving: " + S3_URL)
if (process.env.CLOUD) {
const S3_URL = `https://${ctx.params.appId}.app.budi.live/assets/${ctx.params.appId}/${mainOrAuth}/${ctx.file || "index.production.html"}`
const response = await fetch(S3_URL)
const body = await response.text()
ctx.body = body
return
}
await send(ctx, file, { root: ctx.devPath || appPath })
await send(ctx, ctx.file || "index.html", { root: ctx.devPath || appPath })
}
exports.serveAppAsset = async function(ctx) {
@ -70,15 +66,7 @@ exports.serveAppAsset = async function(ctx) {
mainOrAuth
)
// if (ctx.isCloud) {
// const requestUrl = `${S3_URL_PREFIX}/${appId}/public/${mainOrAuth}/${ctx.file || "index.html"}`
// console.log('request url:' , requestUrl)
// const response = await fetch(requestUrl)
// const body = await response.text()
// ctx.body = body
// } else {
await send(ctx, ctx.file, { root: ctx.devPath || appPath })
// }
await send(ctx, ctx.file, { root: ctx.devPath || appPath })
}
exports.serveComponentLibrary = async function(ctx) {
@ -99,16 +87,15 @@ exports.serveComponentLibrary = async function(ctx) {
)
}
// if (ctx.isCloud) {
// const appId = ctx.user.appId
// const requestUrl = encodeURI(`${S3_URL_PREFIX}/${appId}/node_modules/${ctx.query.library}/dist/index.js`)
// console.log('request url components: ', requestUrl)
// const response = await fetch(requestUrl)
// const body = await response.text()
// ctx.type = 'application/javascript'
// ctx.body = body;
// return
// }
if (process.env.CLOUD) {
const appId = ctx.user.appId
const S3_URL = encodeURI(`https://${appId}.app.budi.live/assets/componentlibrary/${ctx.query.library}/dist/index.js`)
const response = await fetch(S3_URL)
const body = await response.text()
ctx.type = 'application/javascript'
ctx.body = body;
return
}
await send(ctx, "/index.js", { root: componentLibraryPath })
}

View File

@ -43,7 +43,6 @@ router
useAppRootPath: true,
}
ctx.isDev = env.NODE_ENV !== "production" && env.NODE_ENV !== "jest"
// ctx.isCloud = true
await next()
})
.use(authenticated)

View File

@ -57,8 +57,6 @@ const buildIndexHtml = async (config, appId, pageName, appPath, pkg) => {
pageStyle: pkg.page._css,
appId,
pageName,
// TODO: don't hardcode
production: true
}
const indexHtmlTemplate = await readFile(
@ -67,10 +65,16 @@ const buildIndexHtml = async (config, appId, pageName, appPath, pkg) => {
)
const indexHtmlPath = join(appPublicPath, "index.html")
const deployableHtmlPath = join(appPublicPath, "index.production.html")
const indexHtml = sqrl.Render(indexHtmlTemplate, templateObj)
const deployableHtml = sqrl.Render(indexHtmlTemplate, {
...templateObj,
production: true
})
await writeFile(indexHtmlPath, indexHtml, { flag: "w+" })
await writeFile(deployableHtmlPath, deployableHtml, { flag: "w+" })
}
const buildFrontendAppDefinition = async (config, appId, pageName, pkg) => {

View File

@ -27,29 +27,34 @@
{{ /each }}
{{ each(options.screenStyles) }}
{{ if(options.production) }}
<link rel='stylesheet' href='/assets/{{ appId }}/{{ pageName }}{{ @this }}'>
{{#else}}
<link rel='stylesheet' href='/assets{{ @this }}'>
{{ /if }}
{{ /each }}
{{ if(options.pageStyle) }}
{{ if(options.production) }}
<link rel='stylesheet' href='/assets/{{ appId }}/{{ pageName }}{{ pageStyle }}'>
{{#else}}
<link rel='stylesheet' href='/assets{{ pageStyle }}'>
{{ /if }}
{{ /if }}
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto+Mono">
{{ if(options.production) }}
<script src='/assets/{{ options.appId }}/{{ pageName }}/clientFrontendDefinition.js'></script>
<script src='/assets/{{ options.appId }}/{{ pageName }}/budibase-client.js'></script>
{{ else }}
<script src='/assets/clientFrontendDefinition.js'></script>
<script src='/assets/budibase-client.js'></script>
{{ /if }}
<script src='/assets/{{ appId }}/{{ pageName }}/clientFrontendDefinition.js'></script>
<script src='/assets/{{ appId }}/{{ pageName }}/budibase-client.js'></script>
{{#else}}
<script src='/assets/clientFrontendDefinition.js'></script>
<script src='/assets/budibase-client.js'></script>
{{ /if }}
</head>
<body id="app">
<script>
loadBudibase();
</script>