UNPKG

appwrite-utils-cli

Version:

Appwrite Utility Functions to help with database management, data conversion, data import, migrations, and much more. Meant to be used as a CLI tool, I do not recommend installing this in frontend environments.

147 lines (146 loc) 6.62 kB
import { Client, Functions, Runtime } from "node-appwrite"; import { InputFile } from "node-appwrite/file"; import { create as createTarball } from "tar"; import { join, relative } from "node:path"; import fs from "node:fs"; import { platform } from "node:os"; import {} from "appwrite-utils"; import chalk from "chalk"; import cliProgress from "cli-progress"; import { execSync } from "child_process"; import { createFunction, getFunction, updateFunction, updateFunctionSpecifications, } from "./methods.js"; import ignore from "ignore"; import { MessageFormatter } from "../shared/messageFormatter.js"; import { resolveFunctionDirectory, validateFunctionDirectory } from './pathResolution.js'; export const deployFunction = async (client, functionId, codePath, activate = true, entrypoint = "index.js", commands = "npm install", ignored = [ "node_modules", ".git", ".vscode", ".DS_Store", "__pycache__", ".venv", ]) => { const functions = new Functions(client); MessageFormatter.processing("Preparing function deployment...", { prefix: "Deployment" }); // Convert ignored patterns to lowercase for case-insensitive comparison const ignoredLower = ignored.map((pattern) => pattern.toLowerCase()); const tarPath = join(process.cwd(), `function-${functionId}.tar.gz`); // Verify codePath exists and is a directory if (!fs.existsSync(codePath)) { throw new Error(`Function directory not found at ${codePath}`); } const stats = await fs.promises.stat(codePath); if (!stats.isDirectory()) { throw new Error(`${codePath} is not a directory`); } MessageFormatter.processing(`Creating tarball from ${codePath}`, { prefix: "Deployment" }); const progressBar = new cliProgress.SingleBar({ format: "Uploading |" + chalk.cyan("{bar}") + "| {percentage}% | {value}/{total} Chunks", barCompleteChar: "█", barIncompleteChar: "░", hideCursor: true, }); await createTarball({ gzip: true, file: tarPath, cwd: codePath, filter: (path, stat) => { const relativePath = relative(codePath, join(codePath, path)).toLowerCase(); // Skip if path matches any ignored pattern if (ignoredLower.some((pattern) => relativePath.startsWith(pattern) || relativePath.includes(`/${pattern}`) || relativePath.includes(`\\${pattern}`))) { MessageFormatter.debug(`Ignoring ${path}`, undefined, { prefix: "Deployment" }); return false; } return true; }, }, ["."] // This now only includes contents of codePath since we set cwd to codePath ); const fileBuffer = await fs.promises.readFile(tarPath); const fileObject = InputFile.fromBuffer(new Uint8Array(fileBuffer), `function-${functionId}.tar.gz`); try { MessageFormatter.processing("Creating deployment...", { prefix: "Deployment" }); // Start with 1 as default total since we don't know the chunk size yet progressBar.start(1, 0); const functionResponse = await functions.createDeployment(functionId, fileObject, activate, entrypoint, commands, (progress) => { const chunks = progress.chunksUploaded; const total = progress.chunksTotal; if (chunks !== undefined && total !== undefined) { // First chunk, initialize the bar with correct total if (chunks === 0) { progressBar.start(total || 100, 0); } else { progressBar.update(chunks); // Check if upload is complete if (chunks === total) { progressBar.update(total); progressBar.stop(); MessageFormatter.success("Upload complete!", { prefix: "Deployment" }); } } } }); // Ensure progress bar completes even if callback never fired if (progressBar.getProgress() === 0) { progressBar.update(1); progressBar.stop(); } await fs.promises.unlink(tarPath); return functionResponse; } catch (error) { progressBar.stop(); MessageFormatter.error("Deployment failed", error instanceof Error ? error : undefined, { prefix: "Deployment" }); throw error; } }; export const deployLocalFunction = async (client, functionName, functionConfig, functionPath) => { let functionExists = true; let functionThatExists; try { functionThatExists = await getFunction(client, functionConfig.$id); } catch (error) { functionExists = false; } const configDirPath = process.cwd(); // TODO: This should be passed from caller const resolvedPath = resolveFunctionDirectory(functionName, configDirPath, functionConfig.dirPath, functionPath); if (!validateFunctionDirectory(resolvedPath)) { throw new Error(`Function directory is invalid or missing required files: ${resolvedPath}`); } if (functionConfig.predeployCommands?.length) { MessageFormatter.processing("Executing predeploy commands...", { prefix: "Deployment" }); const isWindows = platform() === "win32"; for (const command of functionConfig.predeployCommands) { try { MessageFormatter.debug(`Executing: ${command}`, undefined, { prefix: "Deployment" }); execSync(command, { cwd: resolvedPath, stdio: "inherit", shell: isWindows ? "cmd.exe" : "/bin/sh", windowsHide: true, }); } catch (error) { MessageFormatter.error(`Failed to execute predeploy command: ${command}`, error instanceof Error ? error : undefined, { prefix: "Deployment" }); throw new Error(``); } } } // Only create function if it doesn't exist if (!functionExists) { await createFunction(client, functionConfig); } else { MessageFormatter.processing("Updating function...", { prefix: "Deployment" }); await updateFunction(client, functionConfig); } const deployPath = functionConfig.deployDir ? join(resolvedPath, functionConfig.deployDir) : resolvedPath; return deployFunction(client, functionConfig.$id, deployPath, true, functionConfig.entrypoint, functionConfig.commands, functionConfig.ignore); };