@nuxfly/cli
Version:
CLI tool for deploying Nuxt applications to Fly.io
222 lines (192 loc) • 7.88 kB
JavaScript
import consola from 'consola';
import { executeFlyctlWithOutputInDir, executeFlyctlWithOutput, executeFlyctl } from './flyctl.mjs';
/**
* Get organization name from flyctl status
*/
export async function getOrgName(config) {
try {
const result = await executeFlyctlWithOutput('status', [], config);
// Parse the output to extract organization name
const lines = result.stdout.split('\n');
for (const line of lines) {
if (line.includes('Owner')) {
const match = line.match(/Owner\s*=\s*(.+)/);
if (match) {
return match[1].trim();
}
}
}
consola.debug('Could not find organization name in status output');
return null;
} catch (error) {
consola.debug('Failed to get organization name:', error.message);
return null;
}
}
/**
* Parse flyctl storage create output to extract credentials
*/
export function parseStorageCreateOutput(output) {
try {
const lines = output.split('\n');
const credentials = {};
for (const line of lines) {
if (line.includes('AWS_ACCESS_KEY_ID:')) {
credentials.accessKeyId = line.split(':').slice(1).join(':').trim();
} else if (line.includes('AWS_SECRET_ACCESS_KEY:')) {
credentials.secretAccessKey = line.split(':').slice(1).join(':').trim();
} else if (line.includes('AWS_ENDPOINT_URL_S3:')) {
credentials.endpointUrl = line.split(':').slice(1).join(':').trim();
} else if (line.includes('AWS_REGION:')) {
credentials.region = line.split(':').slice(1).join(':').trim();
} else if (line.includes('BUCKET_NAME:')) {
credentials.bucketName = line.split(':').slice(1).join(':').trim();
}
}
// Validate that we have all required credentials
if (credentials.accessKeyId && credentials.secretAccessKey &&
credentials.endpointUrl && credentials.region && credentials.bucketName) {
return credentials;
}
return null;
} catch (error) {
consola.debug('Failed to parse storage create output:', error.message);
return null;
}
}
/**
* Set multiple Fly secrets
*/
export async function setFlySecrets(appName, config, secrets) {
const secretArgs = [];
for (const [key, value] of Object.entries(secrets)) {
secretArgs.push(`${key}=${value}`);
}
try {
await executeFlyctl('secrets', ['set', ...secretArgs, '--app', appName, '--stage'], config);
consola.debug(`Set ${Object.keys(secrets).length} secrets`);
} catch (error) {
consola.error(`Failed to set secrets: ${error.message}`);
throw error;
}
}
/**
* Create litestream bucket for database backups
*/
export async function createLitestreamBucket(orgName, config) {
const bucketName = `${config.app}-litestream`;
consola.info(`Creating litestream bucket: ${bucketName}`);
try {
// Create bucket from /tmp directory to avoid taking app's default slot
const result = await executeFlyctlWithOutputInDir('storage', ['create', '--name', bucketName, '--org', orgName], config, '/tmp');
// Parse the output to extract credentials
const credentials = parseStorageCreateOutput(result.stdout);
if (credentials) {
// Set secrets with LITESTREAM_ prefix to match litestream.yml
await setFlySecrets(config.app, config, {
'LITESTREAM_S3_ACCESS_KEY_ID': credentials.accessKeyId,
'LITESTREAM_S3_SECRET_ACCESS_KEY': credentials.secretAccessKey,
'LITESTREAM_S3_ENDPOINT_URL': credentials.endpointUrl,
'LITESTREAM_S3_REGION': credentials.region,
'LITESTREAM_S3_BUCKET_NAME': credentials.bucketName,
});
consola.success(`✅ Litestream bucket created: ${bucketName}`);
} else {
consola.warn('Failed to parse litestream bucket credentials');
}
} catch (error) {
consola.error(`Failed to create litestream bucket: ${error.message}`);
throw error;
}
}
/**
* Create public bucket for public assets
*/
export async function createPublicBucket(orgName, config) {
const bucketName = `${config.app}-public`;
consola.info(`Creating public bucket: ${bucketName}`);
try {
// Create public bucket from /tmp directory
const result = await executeFlyctlWithOutputInDir('storage', ['create', '--name', bucketName, '--org', orgName, '--public'], config, '/tmp');
// Parse the output to extract credentials
const credentials = parseStorageCreateOutput(result.stdout);
if (credentials) {
// Set secrets with NUXT_NUXFLY_PUBLIC_BUCKET_S3_ prefix to override runtime config
await setFlySecrets(config.app, config, {
'NUXT_NUXFLY_PUBLIC_BUCKET_S3_ACCESS_KEY_ID': credentials.accessKeyId,
'NUXT_NUXFLY_PUBLIC_BUCKET_S3_SECRET_ACCESS_KEY': credentials.secretAccessKey,
'NUXT_NUXFLY_PUBLIC_BUCKET_S3_ENDPOINT': credentials.endpointUrl,
'NUXT_NUXFLY_PUBLIC_BUCKET_S3_BUCKET': credentials.bucketName,
'NUXT_NUXFLY_PUBLIC_BUCKET_S3_REGION': credentials.region,
});
consola.success(`✅ Public bucket created: ${bucketName}`);
} else {
consola.warn('Failed to parse public bucket credentials');
}
} catch (error) {
consola.error(`Failed to create public bucket: ${error.message}`);
throw error;
}
}
/**
* Create private bucket for private storage
*/
export async function createPrivateBucket(orgName, config) {
const bucketName = `${config.app}-private`;
consola.info(`Creating private bucket: ${bucketName}`);
try {
// Create private bucket from /tmp directory
const result = await executeFlyctlWithOutputInDir('storage', ['create', '--name', bucketName, '--org', orgName], config, '/tmp');
// Parse the output to extract credentials
const credentials = parseStorageCreateOutput(result.stdout);
if (credentials) {
// Set secrets with NUXT_NUXFLY_PRIVATE_BUCKET_S3_ prefix to override runtime config
await setFlySecrets(config.app, config, {
'NUXT_NUXFLY_PRIVATE_BUCKET_S3_ACCESS_KEY_ID': credentials.accessKeyId,
'NUXT_NUXFLY_PRIVATE_BUCKET_S3_SECRET_ACCESS_KEY': credentials.secretAccessKey,
'NUXT_NUXFLY_PRIVATE_BUCKET_S3_ENDPOINT': credentials.endpointUrl,
'NUXT_NUXFLY_PRIVATE_BUCKET_S3_BUCKET': credentials.bucketName,
'NUXT_NUXFLY_PRIVATE_BUCKET_S3_REGION': credentials.region,
});
consola.success(`✅ Private bucket created: ${bucketName}`);
} else {
consola.warn('Failed to parse private bucket credentials');
}
} catch (error) {
consola.error(`Failed to create private bucket: ${error.message}`);
throw error;
}
}
/**
* Create S3 buckets based on configuration
*/
export async function createS3Buckets(config) {
}
/**
* Get list of existing storage buckets for the app
*/
export async function getExistingBuckets(config) {
try {
consola.debug('Checking existing storage buckets...');
const result = await executeFlyctlWithOutput('storage', ['list'], config);
// Parse the output to extract bucket names
const buckets = [];
const lines = result.stdout.split('\n');
for (const line of lines) {
// Look for lines that contain bucket names (typically in a table format)
// This is a simple parser - may need adjustment based on actual flyctl output format
if (line.includes(config.app) && (line.includes('-public') || line.includes('-private') || line.includes('-litestream'))) {
const match = line.match(/(\S+(?:-public|-private|-litestream))/);
if (match) {
buckets.push(match[1]);
}
}
}
consola.debug('Found existing buckets:', buckets);
return buckets;
} catch (error) {
consola.debug('Failed to list existing buckets:', error.message);
// If we can't list buckets, assume none exist to be safe
return [];
}
}