longurl-js
Version:
LongURL - Programmable URL management framework with entity-driven design and production-ready infrastructure
294 lines (293 loc) โข 13.5 kB
JavaScript
;
/**
* CLI tool for batch generating URLs for existing entities.
*
* This utility helps in generating and updating URL IDs
* for entities that already exist in the database.
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const supabase_js_1 = require("@supabase/supabase-js");
const generator_1 = require("./generator");
const types_1 = require("../types");
/**
* Process entities of a specific type and generate URL IDs for them.
*/
async function processEntityType(entityType, tableName, primaryKey, dbConfig, limit = 100, dryRun = false) {
console.log(`\nProcessing ${entityType} entities...`);
const urlIdColumn = dbConfig.urlIdColumn || 'url_id';
const supabase = (0, supabase_js_1.createClient)(dbConfig.connection.url, dbConfig.connection.key);
console.log(`Fetching ${entityType} entities without URL IDs (limit: ${limit})...`);
const { data: entities, error } = await supabase
.from(tableName)
.select(`${primaryKey}, ${urlIdColumn}`)
.is(urlIdColumn, null)
.limit(limit);
if (error) {
console.error(`Error fetching ${entityType} entities:`, error);
return { success: 0, failed: 0, total: 0 };
}
if (!entities || entities.length === 0) {
console.log(`No ${entityType} entities found without URL IDs`);
return { success: 0, failed: 0, total: 0 };
}
console.log(`Found ${entities.length} ${entityType} entities without URL IDs`);
let success = 0;
let failed = 0;
for (const entity of entities) {
const entityId = entity[primaryKey];
if (!entityId) {
console.error(`Entity is missing ${primaryKey}, skipping`);
failed++;
continue;
}
console.log(`Processing ${entityType} with ID: ${entityId}`);
try {
const result = await (0, generator_1.generateUrlId)(entityType, entityId.toString(), {}, dbConfig);
if (!result.success || !result.urlId) {
console.error(`Failed to generate URL ID for ${entityType} ${entityId}: ${result.error}`);
failed++;
continue;
}
const urlId = result.urlId;
console.log(`Generated URL ID ${urlId} for ${entityType} ${entityId}`);
if (!dryRun) {
if (dbConfig.strategy === types_1.StorageStrategy.LOOKUP_TABLE) {
const lookupTable = dbConfig.lookupTable || 'short_urls';
const { error: insertError } = await supabase
.from(lookupTable)
.insert({
url_id: urlId,
entity_id: entityId,
entity_type: entityType,
original_url: `/${entityType}/${entityId}`,
click_count: 0,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
});
if (insertError) {
console.error(`Error updating lookup table for ${entityType} ${entityId}:`, insertError);
failed++;
continue;
}
}
else {
const { error: updateError } = await supabase
.from(tableName)
.update({ [urlIdColumn]: urlId })
.eq(primaryKey, entityId);
if (updateError) {
console.error(`Error updating ${entityType} ${entityId}:`, updateError);
failed++;
continue;
}
}
console.log(`Updated ${entityType} ${entityId} with URL ID ${urlId}`);
}
else {
console.log(`[DRY RUN] Would update ${entityType} ${entityId} with URL ID ${urlId}`);
}
success++;
}
catch (error) {
console.error(`Error processing ${entityType} ${entityId}:`, error);
failed++;
}
}
return { success, failed, total: entities.length };
}
/**
* Main CLI function with conditional environment loading
*/
async function main() {
const args = process.argv.slice(2);
// โ
Help works instantly - no environment loading needed
if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
console.log('Usage: longurl-cli <command> [options]');
console.log('');
console.log('Commands:');
console.log(' generate <entity-type> <table-name> <primary-key> [limit] [--dry-run]');
console.log(' Generate URL IDs for entities in a specific table');
console.log(' test <entity-type> <entity-id> [domain] [--framework]');
console.log(' Test URL generation for a single entity (no database required)');
console.log('');
console.log('Examples:');
console.log(' longurl-cli generate product products id 50');
console.log(' longurl-cli generate customer customers customer_id 100 --dry-run');
console.log(' longurl-cli test product laptop-123');
console.log(' longurl-cli test user user-456 mysite.com');
console.log(' longurl-cli test product laptop-dell-xps-13 mystore.co --framework');
console.log('');
console.log('Test Mode Options:');
console.log(' (no flag) Compare both shortening and framework modes');
console.log(' --framework Test only framework mode (readable URLs)');
console.log('');
console.log('Environment Variables:');
console.log(' SUPABASE_URL Supabase project URL');
console.log(' SUPABASE_SERVICE_ROLE_KEY Service role key for database access');
return;
}
const command = args[0];
const dryRun = args.includes('--dry-run');
if (command === 'generate') {
// โ
Only load environment when database is actually needed
const { initializeDatabaseEnvironment } = await Promise.resolve().then(() => __importStar(require('./env')));
const entityType = args[1];
const tableName = args[2];
const primaryKey = args[3];
const limit = parseInt(args[4]) || 100;
if (!entityType || !tableName || !primaryKey) {
console.error('Error: entity-type, table-name, and primary-key are required');
console.error('Usage: longurl-cli generate <entity-type> <table-name> <primary-key> [limit] [--dry-run]');
process.exit(1);
}
try {
const dbConfig = initializeDatabaseEnvironment();
console.log(`Starting URL ID generation for ${entityType} entities...`);
console.log(`Table: ${tableName}`);
console.log(`Primary Key: ${primaryKey}`);
console.log(`Limit: ${limit}`);
console.log(`Dry run: ${dryRun ? 'Yes' : 'No'}`);
console.log('');
const result = await processEntityType(entityType, tableName, primaryKey, dbConfig, limit, dryRun);
console.log('\n=== SUMMARY ===');
console.log(`Total entities processed: ${result.total}`);
console.log(`Successfully generated: ${result.success}`);
console.log(`Failed: ${result.failed}`);
if (result.failed > 0) {
process.exit(1);
}
}
catch (error) {
console.error('Error:', error.message);
process.exit(1);
}
}
else if (command === 'test') {
// โ
Test command works with zero environment - no database needed
const entityType = args[1];
const entityId = args[2];
const domain = args[3] || 'yourdomain.co';
const modeFlag = args[4];
if (!entityType || !entityId) {
console.error('Error: entity-type and entity-id are required');
console.error('Usage: longurl-cli test <entity-type> <entity-id> [domain] [--framework]');
console.error('');
console.error('Options:');
console.error(' --framework Test framework mode (readable URLs)');
console.error(' (default) Test shortening mode (random IDs)');
process.exit(1);
}
const isFrameworkMode = modeFlag === '--framework' || process.env.LONGURL_SHORTEN === 'false';
console.log(`๐งช Testing URL generation (no database required)`);
console.log(`Entity Type: ${entityType}`);
console.log(`Entity ID: ${entityId}`);
console.log(`Domain: ${domain}`);
console.log(`Mode: ${isFrameworkMode ? 'Framework (readable URLs)' : 'Shortening (random IDs)'}`);
console.log('');
// Create minimal config for testing (no database operations)
const testConfig = {
strategy: types_1.StorageStrategy.LOOKUP_TABLE,
connection: { url: '', key: '' }, // Empty - won't be used for test
lookupTable: 'short_urls',
urlIdColumn: 'url_id'
};
// Test both modes if not specified
if (!modeFlag) {
console.log('=== SHORTENING MODE (Traditional) ===');
const shortenResult = await (0, generator_1.generateUrlId)(entityType, entityId, {
domain,
enableShortening: true
}, testConfig);
if (shortenResult.success) {
console.log('โ
Shortened URL generated:');
console.log(`๐ Short URL: ${shortenResult.shortUrl}`);
console.log(`๐ URL ID: ${shortenResult.urlId} (random Base62)`);
}
else {
console.log(`โ Shortening failed: ${shortenResult.error}`);
}
console.log('\n=== FRAMEWORK MODE (URL Management) ===');
const frameworkResult = await (0, generator_1.generateUrlId)(entityType, entityId, {
domain,
enableShortening: false
}, testConfig);
if (frameworkResult.success) {
console.log('โ
Framework URL generated:');
console.log(`๐ Managed URL: ${frameworkResult.shortUrl}`);
console.log(`๐ URL ID: ${frameworkResult.urlId} (readable entity ID)`);
}
else {
console.log(`โ Framework mode failed: ${frameworkResult.error}`);
}
console.log('\n=== COMPARISON ===');
if (shortenResult.success && frameworkResult.success) {
console.log(`Shortening: ${shortenResult.shortUrl}`);
console.log(`Framework: ${frameworkResult.shortUrl}`);
}
console.log('\n=== ENVIRONMENT CONTROL ===');
console.log('Set LONGURL_SHORTEN=false to enable framework mode globally');
console.log('export LONGURL_SHORTEN=false');
}
else {
// Test specific mode
const result = await (0, generator_1.generateUrlId)(entityType, entityId, {
domain,
enableShortening: !isFrameworkMode
}, testConfig);
if (result.success) {
console.log('โ
URL generated successfully!');
console.log(`๐ ${isFrameworkMode ? 'Managed' : 'Short'} URL: ${result.shortUrl}`);
console.log(`๐ URL ID: ${result.urlId}`);
}
else {
console.log(`โ Generation failed: ${result.error}`);
}
}
console.log('\n๐ก This demonstrates the URL structure your app would generate.');
console.log(' Add database tables to enable collision detection and persistence.');
}
else {
console.error(`Error: Unknown command "${command}"`);
console.error('Run without arguments to see usage information');
process.exit(1);
}
}
// Run the CLI
main().catch(error => {
console.error('Error running CLI:', error);
process.exit(1);
});