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

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more