forked from tsai/budibase
46 changed files with 523 additions and 131 deletions
@ -1,6 +1,6 @@ |
|||||
#!/bin/bash |
#!/bin/bash |
||||
echo "**** WARNING - not for production environments ****" |
echo "**** WARNING - not for production environments ****" |
||||
# warning this is a convience script, for production installations install docker |
# warning this is a convenience script, for production installations install docker |
||||
# properly for your environment! |
# properly for your environment! |
||||
curl -fsSL https://get.docker.com -o get-docker.sh |
curl -fsSL https://get.docker.com -o get-docker.sh |
||||
sudo sh get-docker.sh |
sudo sh get-docker.sh |
||||
|
|||||
@ -0,0 +1,120 @@ |
|||||
|
<script> |
||||
|
import { |
||||
|
Icon, |
||||
|
Modal, |
||||
|
notifications, |
||||
|
ModalContent, |
||||
|
Body, |
||||
|
Button, |
||||
|
} from "@budibase/bbui" |
||||
|
import { store } from "builderStore" |
||||
|
import api from "builderStore/api" |
||||
|
import clientPackage from "@budibase/client/package.json" |
||||
|
|
||||
|
let updateModal |
||||
|
|
||||
|
$: appId = $store.appId |
||||
|
$: updateAvailable = clientPackage.version !== $store.version |
||||
|
$: revertAvailable = $store.revertableVersion != null |
||||
|
|
||||
|
const refreshAppPackage = async () => { |
||||
|
const applicationPkg = await api.get( |
||||
|
`/api/applications/${appId}/appPackage` |
||||
|
) |
||||
|
const pkg = await applicationPkg.json() |
||||
|
if (applicationPkg.ok) { |
||||
|
await store.actions.initialise(pkg) |
||||
|
} else { |
||||
|
throw new Error(pkg) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const update = async () => { |
||||
|
try { |
||||
|
const response = await api.post( |
||||
|
`/api/applications/${appId}/client/update` |
||||
|
) |
||||
|
const json = await response.json() |
||||
|
if (response.status !== 200) { |
||||
|
throw json.message |
||||
|
} |
||||
|
|
||||
|
// Don't wait for the async refresh, since this causes modal flashing |
||||
|
refreshAppPackage() |
||||
|
notifications.success( |
||||
|
`App updated successfully to version ${clientPackage.version}` |
||||
|
) |
||||
|
} catch (err) { |
||||
|
notifications.error(`Error updating app: ${err}`) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const revert = async () => { |
||||
|
try { |
||||
|
const revertableVersion = $store.revertableVersion |
||||
|
const response = await api.post( |
||||
|
`/api/applications/${appId}/client/revert` |
||||
|
) |
||||
|
const json = await response.json() |
||||
|
if (response.status !== 200) { |
||||
|
throw json.message |
||||
|
} |
||||
|
|
||||
|
// Don't wait for the async refresh, since this causes modal flashing |
||||
|
refreshAppPackage() |
||||
|
notifications.success( |
||||
|
`App reverted successfully to version ${revertableVersion}` |
||||
|
) |
||||
|
} catch (err) { |
||||
|
notifications.error(`Error reverting app: ${err}`) |
||||
|
} |
||||
|
updateModal.hide() |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<div class="icon-wrapper" class:highlight={updateAvailable}> |
||||
|
<Icon name="Refresh" hoverable on:click={updateModal.show} /> |
||||
|
</div> |
||||
|
<Modal bind:this={updateModal}> |
||||
|
<ModalContent |
||||
|
title="App version" |
||||
|
confirmText="Update" |
||||
|
cancelText={updateAvailable ? "Cancel" : "Close"} |
||||
|
onConfirm={update} |
||||
|
showConfirmButton={updateAvailable} |
||||
|
> |
||||
|
<div slot="footer"> |
||||
|
{#if revertAvailable} |
||||
|
<Button quiet secondary on:click={revert}>Revert</Button> |
||||
|
{/if} |
||||
|
</div> |
||||
|
{#if updateAvailable} |
||||
|
<Body size="S"> |
||||
|
This app is currently using version <b>{$store.version}</b>, but version |
||||
|
<b>{clientPackage.version}</b> is available. Updates can contain new features, |
||||
|
performance improvements and bug fixes. |
||||
|
</Body> |
||||
|
{:else} |
||||
|
<Body size="S"> |
||||
|
This app is currently using version <b>{$store.version}</b> which is the |
||||
|
latest version available. |
||||
|
</Body> |
||||
|
{/if} |
||||
|
{#if revertAvailable} |
||||
|
<Body size="S"> |
||||
|
You can revert this app to version |
||||
|
<b>{$store.revertableVersion}</b> |
||||
|
if you're experiencing issues with the current version. |
||||
|
</Body> |
||||
|
{/if} |
||||
|
</ModalContent> |
||||
|
</Modal> |
||||
|
|
||||
|
<style> |
||||
|
.icon-wrapper { |
||||
|
display: contents; |
||||
|
} |
||||
|
.icon-wrapper.highlight :global(svg) { |
||||
|
color: var(--spectrum-global-color-blue-600); |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,154 @@ |
|||||
|
const { join } = require("path") |
||||
|
const { ObjectStoreBuckets } = require("../../constants") |
||||
|
const fs = require("fs") |
||||
|
const { upload, retrieveToTmp, streamUpload } = require("./utilities") |
||||
|
const { resolve } = require("../centralPath") |
||||
|
const env = require("../../environment") |
||||
|
const TOP_LEVEL_PATH = join(__dirname, "..", "..", "..") |
||||
|
|
||||
|
/** |
||||
|
* Client library paths in the object store: |
||||
|
* Previously, the entire standard-components package was downloaded from NPM |
||||
|
* as a tarball and extracted to the object store, even though only the manifest |
||||
|
* was ever needed. Therefore we need to support old apps which may still have |
||||
|
* the manifest at this location for the first update. |
||||
|
* |
||||
|
* The new paths for the in-use version are: |
||||
|
* {appId}/manifest.json |
||||
|
* {appId}/budibase-client.js |
||||
|
* |
||||
|
* The paths for the backups are: |
||||
|
* {appId}/manifest.json.bak |
||||
|
* {appId}/budibase-client.js.bak |
||||
|
* |
||||
|
* We don't rely on NPM at all any more, as when updating to the latest version |
||||
|
* we pull both the manifest and client bundle from the server's dependencies |
||||
|
* in the local file system. |
||||
|
*/ |
||||
|
|
||||
|
/** |
||||
|
* Backs up the current client library version by copying both the manifest |
||||
|
* and client bundle to .bak extensions in the object store. Only the one |
||||
|
* previous version is stored as a backup, which can be reverted to. |
||||
|
* @param appId The app ID to backup |
||||
|
* @returns {Promise<void>} |
||||
|
*/ |
||||
|
exports.backupClientLibrary = async appId => { |
||||
|
// Copy existing manifest to tmp
|
||||
|
let tmpManifestPath |
||||
|
try { |
||||
|
// Try to load the manifest from the new file location
|
||||
|
tmpManifestPath = await retrieveToTmp( |
||||
|
ObjectStoreBuckets.APPS, |
||||
|
join(appId, "manifest.json") |
||||
|
) |
||||
|
} catch (error) { |
||||
|
// Fallback to loading it from the old location for old apps
|
||||
|
tmpManifestPath = await retrieveToTmp( |
||||
|
ObjectStoreBuckets.APPS, |
||||
|
join( |
||||
|
appId, |
||||
|
"node_modules", |
||||
|
"budibase", |
||||
|
"standard-components", |
||||
|
"package", |
||||
|
"manifest.json" |
||||
|
) |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
// Copy existing client lib to tmp
|
||||
|
const tmpClientPath = await retrieveToTmp( |
||||
|
ObjectStoreBuckets.APPS, |
||||
|
join(appId, "budibase-client.js") |
||||
|
) |
||||
|
|
||||
|
// Upload manifest and client library as backups
|
||||
|
const manifestUpload = upload({ |
||||
|
bucket: ObjectStoreBuckets.APPS, |
||||
|
filename: join(appId, "manifest.json.bak"), |
||||
|
path: tmpManifestPath, |
||||
|
type: "application/json", |
||||
|
}) |
||||
|
const clientUpload = upload({ |
||||
|
bucket: ObjectStoreBuckets.APPS, |
||||
|
filename: join(appId, "budibase-client.js.bak"), |
||||
|
path: tmpClientPath, |
||||
|
type: "application/javascript", |
||||
|
}) |
||||
|
await Promise.all([manifestUpload, clientUpload]) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Uploads the latest version of the component manifest and the client library |
||||
|
* to the object store, overwriting the existing version. |
||||
|
* @param appId The app ID to update |
||||
|
* @returns {Promise<void>} |
||||
|
*/ |
||||
|
exports.updateClientLibrary = async appId => { |
||||
|
let manifest, client |
||||
|
|
||||
|
if (env.isDev()) { |
||||
|
// Load the symlinked version in dev which is always the newest
|
||||
|
manifest = require.resolve("@budibase/standard-components/manifest.json") |
||||
|
client = require.resolve("@budibase/client") |
||||
|
} else { |
||||
|
// Load the bundled version in prod
|
||||
|
manifest = resolve(TOP_LEVEL_PATH, "client", "manifest.json") |
||||
|
client = resolve(TOP_LEVEL_PATH, "client", "budibase-client.js") |
||||
|
} |
||||
|
|
||||
|
// Upload latest manifest and client library
|
||||
|
const manifestUpload = streamUpload( |
||||
|
ObjectStoreBuckets.APPS, |
||||
|
join(appId, "manifest.json"), |
||||
|
fs.createReadStream(manifest), |
||||
|
{ |
||||
|
ContentType: "application/json", |
||||
|
} |
||||
|
) |
||||
|
const clientUpload = streamUpload( |
||||
|
ObjectStoreBuckets.APPS, |
||||
|
join(appId, "budibase-client.js"), |
||||
|
fs.createReadStream(client), |
||||
|
{ |
||||
|
ContentType: "application/javascript", |
||||
|
} |
||||
|
) |
||||
|
await Promise.all([manifestUpload, clientUpload]) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Reverts the version of the client library and manifest to the previously |
||||
|
* used version for an app. |
||||
|
* @param appId The app ID to revert |
||||
|
* @returns {Promise<void>} |
||||
|
*/ |
||||
|
exports.revertClientLibrary = async appId => { |
||||
|
// Copy backups manifest to tmp directory
|
||||
|
const tmpManifestPath = await retrieveToTmp( |
||||
|
ObjectStoreBuckets.APPS, |
||||
|
join(appId, "manifest.json.bak") |
||||
|
) |
||||
|
|
||||
|
// Copy backup client lib to tmp
|
||||
|
const tmpClientPath = await retrieveToTmp( |
||||
|
ObjectStoreBuckets.APPS, |
||||
|
join(appId, "budibase-client.js.bak") |
||||
|
) |
||||
|
|
||||
|
// Upload backups as new versions
|
||||
|
const manifestUpload = upload({ |
||||
|
bucket: ObjectStoreBuckets.APPS, |
||||
|
filename: join(appId, "manifest.json"), |
||||
|
path: tmpManifestPath, |
||||
|
type: "application/json", |
||||
|
}) |
||||
|
const clientUpload = upload({ |
||||
|
bucket: ObjectStoreBuckets.APPS, |
||||
|
filename: join(appId, "budibase-client.js"), |
||||
|
path: tmpClientPath, |
||||
|
type: "application/javascript", |
||||
|
}) |
||||
|
await Promise.all([manifestUpload, clientUpload]) |
||||
|
} |
||||
@ -1,36 +0,0 @@ |
|||||
const packageJson = require("../../../package.json") |
|
||||
const { join } = require("path") |
|
||||
const { ObjectStoreBuckets } = require("../../constants") |
|
||||
const { streamUpload, downloadTarball } = require("./utilities") |
|
||||
const fs = require("fs") |
|
||||
|
|
||||
const BUCKET_NAME = ObjectStoreBuckets.APPS |
|
||||
|
|
||||
// can't really test this due to the downloading nature of it, wouldn't be a great test case
|
|
||||
/* istanbul ignore next */ |
|
||||
exports.downloadLibraries = async appId => { |
|
||||
const LIBRARIES = ["standard-components"] |
|
||||
|
|
||||
const paths = {} |
|
||||
// Need to download tarballs directly from NPM as our users may not have node on their machine
|
|
||||
for (let lib of LIBRARIES) { |
|
||||
// download tarball
|
|
||||
const registryUrl = `https://registry.npmjs.org/@budibase/${lib}/-/${lib}-${packageJson.version}.tgz` |
|
||||
const path = join(appId, "node_modules", "@budibase", lib) |
|
||||
paths[`@budibase/${lib}`] = await downloadTarball( |
|
||||
registryUrl, |
|
||||
BUCKET_NAME, |
|
||||
path |
|
||||
) |
|
||||
} |
|
||||
return paths |
|
||||
} |
|
||||
|
|
||||
exports.uploadClientLibrary = async appId => { |
|
||||
const sourcepath = require.resolve("@budibase/client") |
|
||||
const destPath = join(appId, "budibase-client.js") |
|
||||
|
|
||||
await streamUpload(BUCKET_NAME, destPath, fs.createReadStream(sourcepath), { |
|
||||
ContentType: "application/javascript", |
|
||||
}) |
|
||||
} |
|
||||
Loading…
Reference in new issue