Published
Edited
Feb 4, 2022
Importers
17 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
signature(deployStaticFiles, {
description: 'Deploys several files on Netlify hosted domain.',
open: true
})
Insert cell
async function deployStaticFiles({
app_id, // Netlify api_id (typod to app_id)
immediate = false, // auto-deploy (no button click)
files = [
//target, // Path on domain to deploy this file to
//source, // URL where to fecth file contents.
//tags = [], // Search tags for content queries
//dependsOnTags = [], // Describe depedencies between content
//...metadata // Additional user fields to be saved in CMS
]
} = {}) {
const id = files[0].target;
if (spinners[id]) return html`${spinners[id]}`;
// Ensure user is logged in
if (!(viewof user).value.uid) return html`${viewof user}`;
// If there is no session data
if (!session.access_token) {
"deployStaticFiles: no access token"
if (nextNonce !== session.nonce) {
await firebase.firestore()
.doc(`/services/netlify-proxy/sessions/${(viewof user).value.uid}`)
.set({
nonce: nextNonce
}, {merge: true});
}
async function authorize(evt) {
const url = 'https://app.netlify.com/authorize?' +
'client_id=' + client_id +
'&response_type=token' +
'&redirect_uri=https://observablehq.com/@endpointservices/netlify' +
'&state=' + encodeURIComponent(JSON.stringify(session));
evt.target.href = url;
}
return html`
<a class="button"
href="https://app.netlify.com/authorize"
onclick=${authorize}>authorize with Netlify</a>
`;
}
// have access token
const subdomain = location.host.split(".")[0];
async function digestFileAndDeploySite(evt) {
const unitsPromise = files.map(async file => {
const unit = ({
...file,
source: file.source,
tags: file.tags || [],
dependsOnTags: file.dependsOnTags || [],
app_id,
target: file.target,
type: "file",
safeTarget: encodeURIComponent(file.target),
});

const unitURI = `/services/netlify-proxy/subdomains/${subdomain}/apps/${app_id}/units/${unit.safeTarget}`;

console.log("deployStaticFile: fetching previous deploy");
const existing = (await firebase.firestore().doc(unitURI).get()).data()


if (existing && existing.creationDate === undefined) {
console.log("deployStaticFile: backfilling creationDate");
await firebase.firestore()
.doc(`/services/netlify-proxy/subdomains/${subdomain}/apps/${app_id}/units/${unit.safeTarget}`)
.set({creationDate: firebase.firebase_.firestore.FieldValue.serverTimestamp()}, {merge: true})
}

console.log("deployStaticFile: syncing paramaters");
await firebase.firestore()
.doc(`/services/netlify-proxy/subdomains/${subdomain}/apps/${app_id}/units/${unit.safeTarget}`)
.set({
...(!existing && {
creationDate: firebase.firebase_.firestore.FieldValue.serverTimestamp()
}),
...unit
}, {merge: true})

return unit;
});

var units = await promiseRecursive(unitsPromise);

var tags = units.reduce(
(list, unit) => list.concat(unit.tags)
,[]
)


const cache = {};
message(id, "Deploying...\nQuerying dependents");
// For now we do one round of dependencies
const dependees = tags.length > 0 ? (await firebase.firestore()
.collection(`/services/netlify-proxy/subdomains/${subdomain}/apps/${app_id}/units`)
.where("type", "==", "file")
.where("dependsOnTags", "array-contains-any", [...new Set(tags || [])])
.limit(100)
.get()).docs.map(d => d.data())
: [];
const updates = dependees.concat(units).map(unit => updateDigest({
app_id, subdomain
}, unit, cache));
message(id, `${updates.length} files to update`)
await Promise.all(updates);
message(id, `Retreiving existing ${dependees.length} files metadata`)
const existingFiles = (await firebase.firestore()
.collection(`/services/netlify-proxy/subdomains/${subdomain}/apps/${app_id}/units`)
.where("type", "==", "file")
.get()).docs.map(d => d.data());
// console.log(existingFiles, cache)
message(id, `Creating deploy with Netlify`)
const deploy_json = await createDeploy(app_id, existingFiles);
const requiredDigests = deploy_json.required;
deployments[id] = deploy_json.ssl_url;
message(id, `Syncing required files ${requiredDigests}`)
const uploads = requiredDigests.map(digest => {
if (!cache[digest]) {
console.error(`Netlify has requested digest ${digest} which was not part of our sync`);
// So lets find it
let record;
firebase.firestore()
.collection(`/services/netlify-proxy/subdomains/${subdomain}/apps/${app_id}/units`)
.where("digest", "==", digest)
.get()
.then(snap => {
record = snap.docs[0].data()
return fetch(record.source);
}).then(response => response.text())
.then(content => {
return uploadContent(deploy_json.id, record.target, content);
})
} else {
return uploadContent(deploy_json.id, cache[digest].target, cache[digest].content);
}
});
try {
await Promise.all(uploads);
message(id, null);
} catch (err) {
message(id, err.message);
}
}
if (immediate && (!deployments[id])) {
deployments[id] = 'inprogress'
digestFileAndDeploySite()
} else if (deployments[id] === 'inprogress') {
return md`Deployment in progress`
} else {
return html`
${Inputs.table(files)}
${deployments[id] ?
html`<p>Deployed to <a target="_blank" href=${deployments[id] + files[0].target}>${deployments[id] + files[0].target}</a>`
: null}
<button class="button" onclick=${digestFileAndDeploySite}>deploy</button>
`
}
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function message(id, message) {
mutable spinners = ({
...mutable spinners,
id: message
})
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
import {promiseRecursive} from '@tomlarkworthy/utils'
Insert cell
Insert cell
Insert cell
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more