@nuxfly/cli
Version:
CLI tool for deploying Nuxt applications to Fly.io
253 lines (223 loc) • 6.65 kB
JavaScript
/**
* Generate fly.toml content from nuxfly configuration
*/
export function generateFlyToml(config = {}) {
const {
app,
region,
memory,
cpu_kind,
cpus,
instances = { min: 1 },
env = {},
volumes = [],
build = {},
statics = [],
} = config;
// Add default nuxfly configuration
volumes.push({ name: 'sqlite_data', mount: '/data' })
build.dockerfile = '.nuxfly/Dockerfile'
statics.push(...[
{ guest_path: '/app/public/_fonts', url_prefix: '/_fonts' },
{ guest_path: '/app/public/_nuxt', url_prefix: '/_nuxt' },
{ guest_path: '/app/public/favicon.ico', url_prefix: '/favicon.ico' },
])
let toml = '';
// Header comment
toml += `# fly.toml app configuration file generated for ${app || 'nuxfly-app'} on ${new Date().toISOString()}\n`;
toml += '# This file was generated by nuxfly. Feel free to edit it as you see fit.\n';
toml += '# See https://fly.io/docs/reference/configuration/ for information about how to use this file.\n\n';
// App configuration
if (app) {
toml += `app = "${app}"\n`;
}
toml += `primary_region = "${region}"\n\n`;
// Build configuration
toml += '[build]\n';
if (build.dockerfile) {
toml += ` dockerfile = "${build.dockerfile}"\n`;
} else {
toml += ' dockerfile = ".nuxfly/Dockerfile"\n';
}
toml += '\n';
// Volumes configuration
if (volumes.length > 0) {
for (const volume of volumes) {
toml += '[[mounts]]\n';
toml += ` source = "${volume.name}"\n`;
toml += ` destination = "${volume.mount}"\n\n`;
}
}
// HTTP service configuration
toml += '[http_service]\n';
toml += ' internal_port = 3000\n';
toml += ' force_https = true\n';
toml += ' auto_stop_machines = "stop"\n';
toml += ' auto_start_machines = true\n';
toml += ` min_machines_running = ${instances.min}\n`;
toml += ' processes = ["app"]\n\n';
// HTTP service ports
toml += ' [http_service.concurrency]\n';
toml += ' type = "requests"\n';
toml += ' hard_limit = 500\n';
toml += ' soft_limit = 400\n\n';
// VM configuration
toml += '[[vm]]\n';
toml += ` memory = "${memory || '512mb'}"\n`;
toml += ` cpu_kind = "${cpu_kind || 'shared'}"\n`;
toml += ` cpus = ${cpus || 1}\n\n`;
// HTTP service health checks
toml += '# [services.http_checks]\n';
toml += '# [services.http_checks.health]\n';
toml += '# grace_period = "5s"\n';
toml += '# interval = "10s"\n';
toml += '# method = "GET"\n';
toml += '# path = "/"\n';
toml += '# timeout = "5s"\n\n';
// Environment variables
if (Object.keys(env).length > 0) {
toml += '[env]\n';
for (const [key, value] of Object.entries(env)) {
// Handle different value types
if (typeof value === 'string') {
toml += ` ${key} = "${value}"\n`;
} else if (typeof value === 'number' || typeof value === 'boolean') {
toml += ` ${key} = ${value}\n`;
} else {
toml += ` ${key} = "${String(value)}"\n`;
}
}
toml += '\n';
}
// Statics
if (statics.length > 0) {
for (const static_ of statics) {
toml += `[[statics]]\n`;
toml += ` guest_path = "${static_.guest_path}"\n`;
toml += ` url_prefix = "${static_.url_prefix}"\n\n`;
}
}
return toml.trim();
}
/**
* Parse existing fly.toml content
*/
export function parseFlyToml(content) {
// Simple TOML parser for basic fly.toml structure
const config = {
app: null,
region: 'ord',
memory: '512mb',
cpu_kind: 'shared',
cpus: 1,
env: {},
volumes: [],
};
const lines = content.split('\n');
let currentSection = null;
let currentMount = null;
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith('#')) {
continue;
}
// Section headers
if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
currentSection = trimmed.slice(1, -1);
if (currentSection === '[[mounts]]') {
currentMount = {};
}
continue;
}
// Key-value pairs
const match = trimmed.match(/^(\w+)\s*=\s*(.+)$/);
if (!match) continue;
const [, key, value] = match;
const cleanValue = value.replace(/^["']|["']$/g, '');
// Parse based on current section
if (!currentSection || currentSection === 'root') {
if (key === 'app') {
config.app = cleanValue;
} else if (key === 'primary_region') {
config.region = cleanValue;
}
} else if (currentSection === 'vm') {
if (key === 'memory') {
config.memory = cleanValue;
}
} else if (currentSection === 'scaling') {
if (key === 'min_machines_running') {
config.instances.min = parseInt(cleanValue, 10);
}
} else if (currentSection === 'env') {
config.env[key] = cleanValue;
} else if (currentSection === '[[mounts]]' && currentMount) {
if (key === 'source') {
currentMount.name = cleanValue;
} else if (key === 'destination') {
currentMount.mount = cleanValue;
config.volumes.push({ ...currentMount });
currentMount = null;
}
}
}
return config;
}
/**
* Merge fly.toml configurations
*/
export function mergeFlyTomlConfig(existing, updates) {
return {
...existing,
...updates,
env: {
...existing?.env,
...updates?.env,
},
instances: {
...existing?.instances,
...updates?.instances,
},
volumes: [
...(existing?.volumes || []),
...(updates?.volumes || []),
],
};
}
/**
* Validate fly.toml configuration
*/
export function validateFlyTomlConfig(config) {
const errors = [];
if (!config.app) {
errors.push('App name is required');
}
if (config.instances) {
if (config.instances.min < 0) {
errors.push('Minimum instances must be >= 0');
}
if (config.instances.max < 1) {
errors.push('Maximum instances must be >= 1');
}
if (config.instances.min > config.instances.max) {
errors.push('Minimum instances cannot exceed maximum instances');
}
}
if (config.memory && !config.memory.match(/^\d+(mb|gb)$/i)) {
errors.push('Memory must be in format like "512mb" or "1gb"');
}
if (config.volumes) {
for (const volume of config.volumes) {
if (!volume.name) {
errors.push('Volume name is required');
}
if (!volume.mount) {
errors.push('Volume mount path is required');
}
if (volume.mount && !volume.mount.startsWith('/')) {
errors.push('Volume mount path must be absolute');
}
}
}
return errors;
}