#!/usr/bin/env node /** * Post-install script to download required validation resources * This script is automatically run after npm/pnpm install * All users need validation capabilities, so this is mandatory */ import { SchematronDownloader } from '../dist_ts/formats/validation/schematron.downloader.js'; import * as path from 'path'; import * as fs from 'fs'; import * as crypto from 'crypto'; // Version for cache invalidation const RESOURCES_VERSION = '1.0.0'; /** * Check if we're in a proper npm install context */ function isValidInstallContext(): boolean { // Skip if we're in a git install or similar if (process.env.npm_lifecycle_event !== 'postinstall') { return false; } // Skip in CI if explicitly disabled if (process.env.CI && process.env.EINVOICE_SKIP_RESOURCES) { console.log('⏭️ Skipping resource download (EINVOICE_SKIP_RESOURCES set)'); return false; } return true; } /** * Create a checksum for a file */ function getFileChecksum(filePath: string): string | null { try { if (!fs.existsSync(filePath)) return null; const content = fs.readFileSync(filePath); return crypto.createHash('sha256').update(content).digest('hex'); } catch { return null; } } /** * Check if resources are already downloaded and valid */ function checkExistingResources(): boolean { const versionFile = path.join('assets_downloaded', 'schematron', '.version'); try { if (!fs.existsSync(versionFile)) return false; const version = fs.readFileSync(versionFile, 'utf-8').trim(); if (version !== RESOURCES_VERSION) { console.log('📦 Resource version mismatch, re-downloading...'); return false; } // Check if key files exist const keyFiles = [ 'assets_downloaded/schematron/EN16931-UBL-v1.3.14.sch', 'assets_downloaded/schematron/EN16931-CII-v1.3.14.sch', 'assets_downloaded/schematron/PEPPOL-EN16931-UBL-v3.0.17.sch' ]; for (const file of keyFiles) { if (!fs.existsSync(file)) { console.log(`📦 Missing ${file}, re-downloading resources...`); return false; } } return true; } catch { return false; } } /** * Save version file after successful download */ function saveVersionFile(): void { const versionFile = path.join('assets_downloaded', 'schematron', '.version'); try { fs.mkdirSync(path.dirname(versionFile), { recursive: true }); fs.writeFileSync(versionFile, RESOURCES_VERSION); } catch (error) { console.warn('⚠️ Could not save version file:', error.message); } } async function downloadSchematron() { console.log('📥 Downloading Schematron validation files...\n'); const downloader = new SchematronDownloader('assets_downloaded/schematron'); await downloader.initialize(); let successCount = 0; let failCount = 0; // Download EN16931 Schematron files console.log('🔵 Downloading EN16931 Schematron files...'); try { const en16931Files = await downloader.downloadStandard('EN16931'); console.log(`✅ Downloaded ${en16931Files.length} EN16931 files`); successCount += en16931Files.length; } catch (error) { console.error(`⚠️ Failed to download EN16931: ${error.message}`); failCount++; } // Download PEPPOL Schematron files console.log('\n🔵 Downloading PEPPOL Schematron files...'); try { const peppolFiles = await downloader.downloadStandard('PEPPOL'); console.log(`✅ Downloaded ${peppolFiles.length} PEPPOL files`); successCount += peppolFiles.length; } catch (error) { console.error(`⚠️ Failed to download PEPPOL: ${error.message}`); failCount++; } // Download XRechnung Schematron files console.log('\n🔵 Downloading XRechnung Schematron files...'); try { const xrechnungFiles = await downloader.downloadStandard('XRECHNUNG'); console.log(`✅ Downloaded ${xrechnungFiles.length} XRechnung files`); successCount += xrechnungFiles.length; } catch (error) { console.error(`⚠️ Failed to download XRechnung: ${error.message}`); failCount++; } // Report results if (successCount > 0) { saveVersionFile(); console.log(`\n✅ Successfully downloaded ${successCount} validation files`); } if (failCount > 0) { console.log(`⚠️ Failed to download ${failCount} resource sets`); console.log(' Some validation features may be limited'); } return { successCount, failCount }; } async function main() { // Check if we should run if (!isValidInstallContext()) { return; } console.log('='.repeat(60)); console.log('🚀 @fin.cx/einvoice - Validation Resources Setup'); console.log('='.repeat(60)); console.log(); try { // Check if resources already exist and are current if (checkExistingResources()) { console.log('✅ Validation resources already installed and up-to-date'); console.log(); return; } // Check if we're in the right directory const packageJsonPath = path.join(process.cwd(), 'package.json'); if (!fs.existsSync(packageJsonPath)) { console.error('❌ Error: package.json not found'); console.error(' Installation context issue - skipping resource download'); return; } // Check if dist_ts exists (module should be built) const distPath = path.join(process.cwd(), 'dist_ts'); if (!fs.existsSync(distPath)) { console.log('⚠️ Module not yet built - skipping resource download'); console.log(' Resources will be downloaded on first use'); return; } // Check network connectivity (simple DNS check) try { await import('dns').then(dns => new Promise((resolve, reject) => { dns.lookup('github.com', (err) => { if (err) reject(err); else resolve(true); }); }) ); } catch { console.log('⚠️ No network connectivity detected'); console.log(' Validation resources will be downloaded on first use'); console.log(' when network is available'); return; } // Download resources with retry logic let attempts = 0; const maxAttempts = 3; let lastError; while (attempts < maxAttempts) { attempts++; if (attempts > 1) { console.log(`\n🔄 Retry attempt ${attempts}/${maxAttempts}...`); } try { const { successCount, failCount } = await downloadSchematron(); if (successCount > 0) { console.log(); console.log('='.repeat(60)); console.log('✅ Validation resources installed successfully!'); console.log('='.repeat(60)); console.log(); return; } if (failCount > 0 && attempts < maxAttempts) { console.log(`\n⚠️ Some downloads failed, retrying...`); await new Promise(resolve => setTimeout(resolve, 2000)); // Wait 2s before retry continue; } break; } catch (error) { lastError = error; if (attempts < maxAttempts) { console.log(`\n⚠️ Download failed: ${error.message}`); console.log(' Retrying...'); await new Promise(resolve => setTimeout(resolve, 3000)); // Wait 3s before retry } } } // If we get here, downloads failed after retries console.error(); console.error('⚠️ Could not download all validation resources'); console.error(' The library will work but validation features may be limited'); console.error(' Resources will be attempted again on first use'); console.error(); if (lastError) { console.error(' Last error:', lastError.message); } } catch (error) { // Catch-all for unexpected errors console.error(); console.error('⚠️ Unexpected error during resource setup:', error.message); console.error(' This won\'t affect library installation'); console.error(' Resources will be downloaded on first use'); console.error(); } } // Only run if this is the main module if (import.meta.url === `file://${process.argv[1]}`) { main().catch(error => { console.error('⚠️ Resource setup error:', error.message); // Never fail the install process.exit(0); }); } export default main;