forked from tsai/budibase
committed by
GitHub
17 changed files with 432 additions and 77 deletions
@ -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