Files
einvoice/ts_install/index.ts

275 lines
8.1 KiB
TypeScript
Raw Normal View History

#!/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', '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/schematron/EN16931-UBL-v1.3.14.sch',
'assets/schematron/EN16931-CII-v1.3.14.sch',
'assets/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', '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/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;