UNPKG

convex

Version:

Client for the Convex Cloud

364 lines (363 loc) 10.9 kB
"use strict"; import chalk from "chalk"; import axios from "axios"; import equal from "deep-equal"; import path from "path"; import { bundle, databaseEntryPoints, actionsEntryPoints, actionsDir } from "../../bundler/index.js"; import { version } from "../../index.js"; import axiosRetry from "axios-retry"; import { deprecationCheckWarning, formatSize, functionsDir, fatalServerErr } from "./utils.js"; export { provisionHost, productionProvisionHost } from "./utils.js"; function isAuthInfo(object) { return "applicationID" in object && typeof object.applicationID === "string" && "domain" in object && typeof object.domain === "string"; } function isAuthInfos(object) { return Array.isArray(object) && object.every((item) => isAuthInfo(item)); } class ParseError extends Error { } export function parseProjectConfig(obj) { if (typeof obj !== "object") { throw new ParseError("Expected an object"); } if (typeof obj.team !== "string") { if (obj.instanceName && obj.origin) { throw new ParseError( 'If upgrading from convex 0.1.8 or below, please delete "convex.json" and reinitialize using `npx convex reinit`' ); } throw new ParseError("Expected team to be a string"); } if (typeof obj.project !== "string") { throw new ParseError("Expected project to be a string"); } if (typeof obj.prodUrl !== "string") { throw new ParseError("Expected prodUrl to be a string"); } if (typeof obj.functions !== "string") { throw new ParseError("Expected functions to be a string"); } obj.authInfo = obj.authInfo ?? []; if (!isAuthInfos(obj.authInfo)) { throw new ParseError("Expected authInfo to be type AuthInfo[]"); } return obj; } function parseBackendConfig(obj) { if (typeof obj !== "object") { throw new ParseError("Expected an object"); } if (typeof obj.functions !== "string") { throw new ParseError("Expected functions to be a string"); } obj.authInfo = obj.authInfo ?? []; if (!isAuthInfos(obj.authInfo)) { throw new ParseError("Expected authInfo to be type AuthInfo[]"); } return obj; } export function configName() { return "convex.json"; } export async function configFilepath(ctx) { const configFn = configName(); const preferredLocation = configFn; const wrongLocation = path.join("src", configFn); const preferredLocationExists = ctx.fs.exists(preferredLocation); const wrongLocationExists = ctx.fs.exists(wrongLocation); if (preferredLocationExists && wrongLocationExists) { console.error( chalk.red( `Error: both ${preferredLocation} and ${wrongLocation} files exist!` ) ); console.error(`Consolidate these and remove ${wrongLocation}.`); return await ctx.fatalError(1, "fs"); } if (!preferredLocationExists && wrongLocationExists) { console.error( chalk.red( `Error: Please move ${wrongLocation} to the root of your project` ) ); return await ctx.fatalError(1, "fs"); } return preferredLocation; } export async function readProjectConfig(ctx) { let projectConfig; const configPath = await configFilepath(ctx); try { projectConfig = parseProjectConfig( JSON.parse(ctx.fs.readUtf8File(configPath)) ); } catch (err) { if (err instanceof ParseError || err instanceof SyntaxError) { console.error(chalk.red(`Error: Parsing "${configPath}" failed`)); console.error(chalk.gray(err.toString())); } else { console.error( chalk.red(`Error: Unable to read project config file "${configPath}"`) ); console.error( "Are you running this command from the root directory of a Convex project?" ); if (err instanceof Error) { console.error(chalk.gray(err.message)); } } return await ctx.fatalError(1, "fs", err); } return { projectConfig, configPath }; } export async function configFromProjectConfig(ctx, projectConfig, configPath, verbose) { let modules; try { const baseDir = functionsDir(configPath, projectConfig); const entryPoints = await databaseEntryPoints(ctx.fs, baseDir, verbose); modules = await bundle(ctx.fs, baseDir, entryPoints, true, "browser"); if (verbose) { console.log( "Queries and mutations modules: ", modules.map((m) => m.path) ); } const nodeEntryPoints = await actionsEntryPoints(ctx.fs, baseDir, verbose); const nodeModules = await bundle( ctx.fs, baseDir, nodeEntryPoints, true, "node", path.join(actionsDir, "_deps") ); if (verbose) { console.log( "Actions modules: ", nodeModules.map((m) => m.path) ); } modules.push(...nodeModules); } catch (err) { console.error(chalk.red("Error: Unable to bundle Convex modules")); if (err instanceof Error) { console.error(chalk.gray(err.message)); } return await ctx.fatalError(1, "fs", err); } return { projectConfig, modules, udfServerVersion: version }; } export async function readConfig(ctx, verbose) { const { projectConfig, configPath } = await readProjectConfig(ctx); const config = await configFromProjectConfig( ctx, projectConfig, configPath, verbose ); return { config, configPath }; } export async function writeProjectConfig(ctx, projectConfig) { const configPath = await configFilepath(ctx); try { const contents = JSON.stringify(projectConfig, void 0, 2) + "\n"; ctx.fs.writeUtf8File(configPath, contents, 420); } catch (err) { console.error( chalk.red( `Error: Unable to write project config file "${configPath}" in current directory` ) ); console.error( "Are you running this command from the root directory of a Convex project?" ); return await ctx.fatalError(1, "fs", err); } ctx.fs.mkdir(functionsDir(configPath, projectConfig), { allowExisting: true }); } export async function pullConfig(ctx, project, team, origin, adminKey) { const client = axios.create(); axiosRetry(client, { retries: 4, retryDelay: axiosRetry.exponentialDelay, retryCondition: (error) => { return error.response?.status === 404 || false; } }); try { const res = await client.post( `${origin}/api/${version}/get_config`, { version, adminKey }, { maxContentLength: Infinity } ); deprecationCheckWarning(ctx, res); const { functions, authInfo } = parseBackendConfig(res.data.config); const projectConfig = { project, team, prodUrl: origin, functions, authInfo }; return { projectConfig, modules: res.data.modules, udfServerVersion: res.data.udfServerVersion }; } catch (err) { console.error( chalk.red("Error: Unable to pull deployment config from", origin) ); return await fatalServerErr(ctx, err); } } export function configJSON(config, adminKey) { const projectConfig = { projectSlug: config.projectConfig.project, teamSlug: config.projectConfig.team, functions: config.projectConfig.functions, authInfo: config.projectConfig.authInfo }; return { config: projectConfig, modules: config.modules, udfServerVersion: config.udfServerVersion, adminKey }; } export async function pushConfig(ctx, config, adminKey, url) { const serializedConfig = configJSON(config, adminKey); try { await axios.post(`${url}/api/${version}/push_config`, serializedConfig, { maxContentLength: Infinity, maxBodyLength: Infinity }); } catch (err) { console.error(chalk.red("Error: Unable to push deployment config to", url)); return await fatalServerErr(ctx, err); } } function renderModule(module) { const sourceMapSize = formatSize(module.sourceMap?.length ?? 0); return module.path + ` (${formatSize(module.source.length)}, source map ${sourceMapSize})`; } function compareModules(oldModules, newModules) { let diff = ""; const droppedModules = []; for (const oldModule of oldModules) { let matches = false; for (const newModule of newModules) { if (oldModule.path === newModule.path && oldModule.source === newModule.source && oldModule.sourceMap === newModule.sourceMap) { matches = true; break; } } if (!matches) { droppedModules.push(oldModule); } } if (droppedModules.length > 0) { diff += "Delete the following modules:\n"; for (const module of droppedModules) { diff += "[-] " + renderModule(module) + "\n"; } } const addedModules = []; for (const newModule of newModules) { let matches = false; for (const oldModule of oldModules) { if (oldModule.path === newModule.path && oldModule.source === newModule.source && oldModule.sourceMap === newModule.sourceMap) { matches = true; break; } } if (!matches) { addedModules.push(newModule); } } if (addedModules.length > 0) { diff += "Add the following modules:\n"; for (const module of addedModules) { diff += "[+] " + renderModule(module) + "\n"; } } return diff; } export function diffConfig(oldConfig, newConfig) { let diff = compareModules(oldConfig.modules, newConfig.modules); const droppedAuth = []; for (const oldAuth of oldConfig.projectConfig.authInfo) { let matches2 = false; for (const newAuth of newConfig.projectConfig.authInfo) { if (equal(oldAuth, newAuth)) { matches2 = true; break; } } if (!matches2) { droppedAuth.push(oldAuth); } } if (droppedAuth.length > 0) { diff += "Remove the following auth providers:\n"; for (const authInfo of droppedAuth) { diff += "[-] " + JSON.stringify(authInfo) + "\n"; } } const addedAuth = []; for (const newAuth of newConfig.projectConfig.authInfo) { let matches2 = false; for (const oldAuth of oldConfig.projectConfig.authInfo) { if (equal(newAuth, oldAuth)) { matches2 = true; break; } } if (!matches2) { addedAuth.push(newAuth); } } if (addedAuth.length > 0) { diff += "Add the following auth providers:\n"; for (const auth of addedAuth) { diff += "[+] " + JSON.stringify(auth) + "\n"; } } let versionMessage = ""; const matches = oldConfig.udfServerVersion === newConfig.udfServerVersion; if (oldConfig.udfServerVersion && (!newConfig.udfServerVersion || !matches)) { versionMessage += `[-] ${oldConfig.udfServerVersion} `; } if (newConfig.udfServerVersion && (!oldConfig.udfServerVersion || !matches)) { versionMessage += `[+] ${newConfig.udfServerVersion} `; } if (versionMessage) { diff += "Change the server's function version:\n"; diff += versionMessage; } return diff; } //# sourceMappingURL=config.js.map