prices-as-code
Version:
Prices as Code (PaC) - Define your product pricing schemas with type-safe definitions
184 lines (177 loc) • 7.94 kB
JavaScript
#!/usr/bin/env node
import path from 'path';
import fs from 'fs';
import pac from './index.js';
// Parse command line arguments
const args = process.argv.slice(2);
const helpArg = args.includes('--help') || args.includes('-h');
// Detect commands
const isPull = args[0] === 'pull';
const isGenerate = args[0] === 'generate';
const command = isPull ? 'pull' : isGenerate ? 'generate' : 'sync'; // Default is sync
// Config path is the first non-flag, non-command argument
const configPathArg = args.find(arg => !arg.startsWith('--') &&
arg !== 'pull' && arg !== 'generate' && arg !== 'sync');
// Environment options
const envFileArg = args.find(arg => arg.startsWith('--env='))?.split('=')[1];
const stripeKeyArg = args.find(arg => arg.startsWith('--stripe-key='))?.split('=')[1];
const writeBackArg = args.includes('--write-back');
// Parse the format argument
const formatArg = args.find(arg => arg.startsWith('--format='))?.split('=')[1] || 'yaml';
// Validate and convert to the correct type
const outputFormat = (formatArg === 'yaml' || formatArg === 'json' || formatArg === 'ts')
? formatArg
: 'yaml';
// Generate command specific arguments
const tierArg = args.find(arg => arg.startsWith('--tiers='))?.split('=')[1];
const currencyArg = args.find(arg => arg.startsWith('--currency='))?.split('=')[1];
const intervalArg = args.find(arg => arg.startsWith('--intervals='))?.split('=')[1];
const noMetadataArg = args.includes('--no-metadata');
const noFeaturesArg = args.includes('--no-features');
// Display help information
function showHelp() {
console.log(`
🏷️ Prices as Code (PaC) - Define your product pricing schemas with type-safe definitions
Usage: prices-as-code [command] [options] [config-file]
Commands:
sync Synchronize your pricing schema with provider (default)
pull Pull pricing from provider into a local config file
generate Generate a template pricing structure
validate Validate your pricing schema without making changes
diff Show differences between local config and provider
Options:
-h, --help Show this help message
--env=<path> Path to .env file with environment variables
--stripe-key=<key> Stripe API key (overrides env var)
--write-back Write provider IDs back to your config file (default: off)
--format=<format> Output format for 'pull' and 'generate' commands (yaml, json, ts) (default: yaml)
--dry-run Show changes without executing them
--verbose Show detailed logging information
Generate Options:
--tiers=<tiers> Comma-separated list of product tiers (default: basic,pro,enterprise)
--currency=<cur> ISO currency code (default: usd)
--intervals=<int> Comma-separated list of intervals (default: month,year)
--no-metadata Don't include metadata in generated file
--no-features Don't include feature lists in generated products
Examples:
prices-as-code prices.yml # Sync pricing to provider
prices-as-code pull prices.yml # Pull pricing from provider
prices-as-code generate prices.yml # Generate template pricing
prices-as-code generate --tiers=free,basic,pro prices.yml # Custom tiers
prices-as-code pricing.ts --env=.env.production # With custom env file
prices-as-code pull --format=ts pricing.ts # Pull as TypeScript
prices-as-code --help # Show help
Documentation: https://wickdninja.github.io/prices-as-code/
`);
process.exit(0);
}
async function main() {
try {
// Show help if requested
if (helpArg) {
showHelp();
return;
}
// Validate that a config file is provided and exists
if (!configPathArg) {
console.error('❌ Error: No configuration file specified');
console.log('Run "prices-as-code --help" for usage information');
process.exit(1);
}
const resolvedConfigPath = path.resolve(process.cwd(), configPathArg);
// For sync command, config file must exist
// For pull and generate commands, we'll create or overwrite the file
if (command === 'sync' && !fs.existsSync(resolvedConfigPath)) {
console.error(`❌ Error: Configuration file not found: ${resolvedConfigPath}`);
console.log('Run "prices-as-code --help" for usage information');
process.exit(1);
}
// If env file specified, set it in Node env
if (envFileArg) {
const envPath = path.resolve(process.cwd(), envFileArg);
// Check if env file exists
if (!fs.existsSync(envPath)) {
console.error(`❌ Error: Environment file not found: ${envPath}`);
process.exit(1);
}
process.env.DOTENV_CONFIG_PATH = envPath;
console.log(`🔧 Using environment file: ${envFileArg}`);
}
// Handle generate command
if (command === 'generate') {
// Parse tier argument if provided
const productTiers = tierArg ?
tierArg.split(',').map(tier => tier.trim().toLowerCase()) :
undefined;
// Parse interval argument if provided
const intervals = intervalArg ?
intervalArg.split(',').map(interval => interval.trim().toLowerCase()) :
undefined;
// Create generate options
const generateOptions = {
configPath: resolvedConfigPath,
format: outputFormat,
provider: 'stripe', // Only stripe supported currently
productTiers,
intervals,
currency: currencyArg ? currencyArg.toLowerCase() : undefined,
includeMetadata: !noMetadataArg,
includeFeatures: !noFeaturesArg
};
// Generate template
await pac.generate(generateOptions);
}
else if (command === 'pull') {
// Set up provider options
const providers = [];
// Add providers from command line args
if (stripeKeyArg) {
providers.push({
provider: 'stripe',
options: {
secretKey: stripeKeyArg
}
});
}
// Create properly typed pull options
const pullOptions = {
configPath: resolvedConfigPath,
providers: providers.length > 0 ? providers : undefined,
format: outputFormat,
writeBack: false // Not applicable for pull
};
await pac.pull(pullOptions);
}
else {
// For sync command, use pricesAsCode
const providers = [];
// Add providers from command line args
if (stripeKeyArg) {
providers.push({
provider: 'stripe',
options: {
secretKey: stripeKeyArg
}
});
}
const options = {
configPath: resolvedConfigPath,
providers: providers.length > 0 ? providers : undefined,
writeBack: writeBackArg
};
await pac(options);
}
process.exit(0);
}
catch (error) {
console.error('❌ Error:', error instanceof Error ? error.message : String(error));
// Show help hint for common errors
if (error instanceof Error &&
(error.message.includes('Cannot find module') ||
error.message.includes('not found'))) {
console.log('Run "prices-as-code --help" for usage information');
}
process.exit(1);
}
}
main();