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.
109 lines (108 loc) • 4.36 kB
JavaScript
import fs from 'node:fs';
import path from 'node:path';
import yaml from 'js-yaml';
import { homedir } from 'node:os';
import { AppwriteFunctionSchema } from 'appwrite-utils';
import { shouldIgnoreDirectory } from '../utils/directoryUtils.js';
import { MessageFormatter } from '../shared/messageFormatter.js';
function findGitRoot(startDir) {
let dir = path.resolve(startDir);
while (dir !== path.parse(dir).root) {
if (fs.existsSync(path.join(dir, '.git')))
return dir;
const parent = path.dirname(dir);
if (parent === dir)
break;
dir = parent;
}
return path.resolve(startDir);
}
function expandTilde(p) {
if (!p)
return p;
if (p === '~' || p.startsWith('~/'))
return p.replace(/^~(?=$|\/|\\)/, homedir());
return p;
}
export function discoverFnConfigs(startDir) {
const root = findGitRoot(startDir);
const results = [];
const visit = (dir, depth = 0) => {
if (depth > 5)
return; // cap depth
const base = path.basename(dir);
if (shouldIgnoreDirectory(base))
return;
let entries = [];
try {
entries = fs.readdirSync(dir, { withFileTypes: true });
}
catch {
return;
}
// Check for .fnconfig.yaml / .fnconfig.yml
for (const fname of ['.fnconfig.yaml', '.fnconfig.yml']) {
const cfgPath = path.join(dir, fname);
if (fs.existsSync(cfgPath)) {
try {
const raw = fs.readFileSync(cfgPath, 'utf8');
const data = yaml.load(raw);
const parsed = AppwriteFunctionSchema.parse({
$id: data.id || data.$id,
name: data.name,
runtime: data.runtime,
execute: data.execute || [],
events: data.events || [],
schedule: data.schedule,
timeout: data.timeout,
enabled: data.enabled,
logging: data.logging,
entrypoint: data.entrypoint,
commands: data.commands,
scopes: data.scopes,
installationId: data.installationId,
providerRepositoryId: data.providerRepositoryId,
providerBranch: data.providerBranch,
providerSilentMode: data.providerSilentMode,
providerRootDirectory: data.providerRootDirectory,
templateRepository: data.templateRepository,
templateOwner: data.templateOwner,
templateRootDirectory: data.templateRootDirectory,
templateVersion: data.templateVersion,
specification: data.specification,
dirPath: data.dirPath,
predeployCommands: data.predeployCommands,
deployDir: data.deployDir,
ignore: data.ignore,
});
// Resolve dirPath relative to the config file directory
let dirPath = parsed.dirPath || '.';
dirPath = expandTilde(dirPath);
if (!path.isAbsolute(dirPath))
dirPath = path.resolve(path.dirname(cfgPath), dirPath);
const merged = { ...parsed, dirPath };
results.push(merged);
}
catch (e) {
MessageFormatter.warning(`Failed to parse ${cfgPath}: ${e instanceof Error ? e.message : String(e)}`, { prefix: 'Functions' });
}
}
}
for (const entry of entries) {
if (entry.isDirectory())
visit(path.join(dir, entry.name), depth + 1);
}
};
visit(root, 0);
return results;
}
export function mergeDiscoveredFunctions(central = [], discovered = []) {
const map = new Map();
for (const f of central)
if (f?.$id)
map.set(f.$id, f);
for (const f of discovered)
if (f?.$id)
map.set(f.$id, f); // discovered overrides
return Array.from(map.values());
}