fauna-gql-upload
Version:
Manage your FaunaDB resources in within your project and upload them using a single command
142 lines (111 loc) • 4.1 kB
text/typescript
import fs from "fs";
import path from "path";
import createOrUpdateResources from "../util/createOrUpdateResources";
import * as esbuild from "esbuild";
import { IndexResource, ProviderResource, Resource, ResourceType, UploadResourcesOptions } from "../types";
import { detailedError, status } from "../util/logger";
import getProviders from "../util/getProviders";
import updateIndexes from "../util/updateIndexes";
import CLISpinner from "cli-spinner";
import { errors } from "faunadb";
const Spinner = CLISpinner.Spinner;
const cwd = process.cwd();
const allowedExts = [".js", ".ts"];
async function uploadResources(dir: string, type: ResourceType, options?: UploadResourcesOptions){
const failedUploadError = `Failed to upload ${type}\n`;
const resourceDir = path.join(dir, "output");
const files = await fs.promises.readdir(dir).catch(err => {
return null;
});
if(!files || (files && files.length === 0)){
return;
}
const entries = files.filter(file =>{
const ext = getExt(file)
if(allowedExts.includes(ext)){
return true
}
return false
}).map( file => path.join(dir, file));
const containsTs = files.some( file => getExt(file) === ".ts");
if(containsTs) {
const { typeCheck } = require("../util/typescript");
const ok = await typeCheck(entries, dir);
if(!ok){
// Log status message for failed upload.
status(failedUploadError, "error");
// Cancel execution.
return;
}
}
// Build resources before reading data.
try{
await esbuild.build({
entryPoints: entries,
outdir: resourceDir,
bundle: true,
platform: "node",
outExtension: { ".js": ".cjs" },
target: ["esnext"]
});
}catch(err){
status(failedUploadError, "error");
return;
}
const resourceFiles: Resource[] = (await Promise.all(files.map( async file => {
try{
if(!allowedExts.includes(getExt(file))) return null;
const resourcePath = path.join(cwd, resourceDir, removeExt(file) + ".cjs");
const resource = await import(resourcePath);
// Handle default exports
return resource.default || resource;
}catch(err){
status(`Error reading file ${dir}/${file}`, "error");
console.error(err);
return null;
}
}))).filter( value => value !== null);
// Remove out directory after reading files
await fs.promises.rm(resourceDir, { recursive: true });
try{
const result = await createOrUpdateResources(resourceFiles, type, options);
// Don't log success message on first function pass.
if(type === "functions" && !options?.fnsWithRoles) return;
status(`successfully uploaded ${type}`, "success");
// Get and log the audience value of the uploaded providers
if(type === "providers"){
const providers = await getProviders(result as ProviderResource[]);
providers.forEach( provider => {
status(`audience for ${provider.name}: ${provider.audience}`, "info");
})
}
}catch(err){
// Don't log error message on first function pass.
if(type === "functions" && !options?.fnsWithRoles) return;
if(type === "indexes" && (err as any)?.requestResult?.responseRaw?.includes("Index sources, terms, values, and partition count may not be updated.")){
status("Index sources, terms, values, and partition count cannot be updated normally. We must first delete the index and create it again. This will happen automatically, hold on...", "info");
try{
const indexes = resourceFiles.filter(val => !!val.source);
const spinner = new Spinner("Updating mutated indexes... %s");
spinner.start();
await updateIndexes(indexes as IndexResource[]);
spinner.stop();
console.log();
status("Successfully updated mutated indexes", "success");
return;
}catch(err){
status("Failed to update mutated indexes", "error");
console.error(err);
}
}
detailedError(err as errors.FaunaHTTPError, type);
throw "Upload operation failed. See logged errors above for more info."
}
}
function removeExt(str: string){
return str.split(".").slice(0, -1).join("");
}
function getExt(str: string){
return path.extname(str);
}
export default uploadResources;