#!/usr/bin/env node /** * Generates VaultVersions and VaultSql for Swift and Kotlin from TypeScript source. * This allows native iOS and Android code to create vaults with the correct SQL schema. */ const fs = require('fs'); const path = require('path'); // Paths const REPO_ROOT = path.join(__dirname, '../../..'); const TS_VERSIONS_SOURCE = path.join(REPO_ROOT, 'core/vault/src/sql/VaultVersions.ts'); const TS_SQL_SOURCE = path.join(REPO_ROOT, 'core/vault/src/sql/SqlConstants.ts'); // Output paths // Note: VaultModels is a framework that can be imported by other targets (including AliasVaultUITests) const SWIFT_OUTPUT_DIR = path.join(REPO_ROOT, 'apps/mobile-app/ios/VaultModels'); const KOTLIN_OUTPUT_DIR = path.join(REPO_ROOT, 'apps/mobile-app/android/app/src/main/java/net/aliasvault/app/vaultstore/models'); /** * Parse VaultVersions from TypeScript source */ function parseVaultVersions(tsContent) { const versions = []; // Find the VAULT_VERSIONS array const match = tsContent.match(/export const VAULT_VERSIONS[^=]*=\s*\[([\s\S]*?)\];/); if (!match) { throw new Error('Could not find VAULT_VERSIONS in source'); } const body = match[1]; // Match each version object const versionRegex = /\{\s*revision:\s*(\d+),\s*version:\s*'([^']+)',\s*description:\s*'([^']+)',\s*releaseVersion:\s*'([^']+)',\s*compatibleUpToVersion:\s*'([^']+)',?\s*\}/g; let versionMatch; while ((versionMatch = versionRegex.exec(body)) !== null) { versions.push({ revision: parseInt(versionMatch[1], 10), version: versionMatch[2], description: versionMatch[3], releaseVersion: versionMatch[4], compatibleUpToVersion: versionMatch[5] }); } return versions; } /** * Parse COMPLETE_SCHEMA_SQL from TypeScript source */ function parseCompleteSchemaSql(tsContent) { // Find the COMPLETE_SCHEMA_SQL constant const match = tsContent.match(/export const COMPLETE_SCHEMA_SQL\s*=\s*`([\s\S]*?)`;/); if (!match) { throw new Error('Could not find COMPLETE_SCHEMA_SQL in source'); } return match[1].trim(); } /** * Parse MIGRATION_SCRIPTS from TypeScript source */ function parseMigrationScripts(tsContent) { const migrations = {}; // Find the MIGRATION_SCRIPTS object const match = tsContent.match(/export const MIGRATION_SCRIPTS:\s*Record\s*=\s*\{([\s\S]*?)\n\};/); if (!match) { // MIGRATION_SCRIPTS might be optional or have different format console.log('Note: MIGRATION_SCRIPTS not found or has different format'); return migrations; } const body = match[1]; // Match each migration: number: `sql` const migrationRegex = /(\d+):\s*`([\s\S]*?)`/g; let migrationMatch; while ((migrationMatch = migrationRegex.exec(body)) !== null) { migrations[parseInt(migrationMatch[1], 10)] = migrationMatch[2].trim(); } return migrations; } /** * Escape string for Swift multiline string literal */ function escapeSwiftString(str) { // Swift multiline strings handle most escaping, but we need to escape backslashes and quotes return str .replace(/\\/g, '\\\\') .replace(/"/g, '\\"'); } /** * Escape string for Kotlin multiline string literal (trimIndent) */ function escapeKotlinString(str) { // Kotlin raw strings don't need much escaping, but $ needs escaping return str.replace(/\$/g, '\\$'); } /** * Generate Swift VaultVersions struct */ function generateSwiftVaultVersions(versions) { const latestVersion = versions[versions.length - 1]; const versionsArray = versions.map(v => { return ` VaultVersion( revision: ${v.revision}, version: "${v.version}", description: "${v.description}", releaseVersion: "${v.releaseVersion}", compatibleUpToVersion: "${v.compatibleUpToVersion}" )`; }).join(',\n'); return `// // This file is auto-generated from core/vault/src/sql/VaultVersions.ts // Do not edit this file directly. Run './build.sh' in core/vault to regenerate. // swiftlint:disable all import Foundation /// Vault database version information. public struct VaultVersion { /// The migration revision number. public let revision: Int /// The internal migration version number that equals the AliasClientDb database version (e.g., "1.5.0"). public let version: String /// Description of changes in this version. public let description: String /// The AliasVault release that this vault version was introduced in (e.g., "0.14.0"). public let releaseVersion: String /// The last AliasVault release version that the vault was compatible with before requiring this migration. public let compatibleUpToVersion: String } /// Registry of all vault versions. public struct VaultVersions { /// All vault migrations/versions in chronological order. public static let all: [VaultVersion] = [ ${versionsArray} ] /// Get the latest vault version. public static var latest: VaultVersion { return all.last! } /// Get the latest vault version string (e.g., "1.7.0"). public static var latestVersion: String { return latest.version } /// Get the latest vault revision number. public static var latestRevision: Int { return latest.revision } /// Find a vault version by revision number. public static func findByRevision(_ revision: Int) -> VaultVersion? { return all.first { $0.revision == revision } } /// Find a vault version by version string. public static func findByVersion(_ version: String) -> VaultVersion? { return all.first { $0.version == version } } } // swiftlint:enable all `; } /** * Generate Kotlin VaultVersions object */ function generateKotlinVaultVersions(versions) { const versionsArray = versions.map(v => { return ` VaultVersion( revision = ${v.revision}, version = "${v.version}", description = "${v.description}", releaseVersion = "${v.releaseVersion}", compatibleUpToVersion = "${v.compatibleUpToVersion}", )`; }).join(',\n'); return `// // This file is auto-generated from core/vault/src/sql/VaultVersions.ts // Do not edit this file directly. Run './build.sh' in core/vault to regenerate. @file:Suppress("all") package net.aliasvault.app.vaultstore.models /** * Vault database version information. */ data class VaultVersion( /** The migration revision number. */ val revision: Int, /** The internal migration version number that equals the AliasClientDb database version (e.g., "1.5.0"). */ val version: String, /** Description of changes in this version. */ val description: String, /** The AliasVault release that this vault version was introduced in (e.g., "0.14.0"). */ val releaseVersion: String, /** The last AliasVault release version that the vault was compatible with before requiring this migration. */ val compatibleUpToVersion: String, ) /** * Registry of all vault versions. */ object VaultVersions { /** * All vault migrations/versions in chronological order. */ val all: List = listOf( ${versionsArray}, ) /** * Get the latest vault version. */ val latest: VaultVersion get() = all.last() /** * Get the latest vault version string (e.g., "1.7.0"). */ val latestVersion: String get() = latest.version /** * Get the latest vault revision number. */ val latestRevision: Int get() = latest.revision /** * Find a vault version by revision number. */ fun findByRevision(revision: Int): VaultVersion? { return all.find { it.revision == revision } } /** * Find a vault version by version string. */ fun findByVersion(version: String): VaultVersion? { return all.find { it.version == version } } } `; } /** * Indent each line of a multi-line string for Swift multi-line string literals */ function indentSwiftMultilineString(str, spaces = 8) { const indent = ' '.repeat(spaces); return str.split('\n').map(line => indent + line).join('\n'); } /** * Generate Swift VaultSql struct */ function generateSwiftVaultSql(completeSchemaSql, migrations) { const escapedSql = escapeSwiftString(completeSchemaSql); const indentedSql = indentSwiftMultilineString(escapedSql); // Generate migration entries const migrationEntries = Object.entries(migrations).map(([key, sql]) => { const escapedMigrationSql = escapeSwiftString(sql); const indentedMigrationSql = indentSwiftMultilineString(escapedMigrationSql, 12); return ` ${key}: """ ${indentedMigrationSql} """`; }).join(',\n'); const migrationsDict = Object.keys(migrations).length > 0 ? ` /// Migration SQL scripts indexed by migration number. /// Key is the source migration number (migration FROM this version). public static let migrations: [Int: String] = [ ${migrationEntries} ]` : ` /// Migration SQL scripts indexed by migration number. public static let migrations: [Int: String] = [:]`; return `// // This file is auto-generated from core/vault/src/sql/SqlConstants.ts // Do not edit this file directly. Run './build.sh' in core/vault to regenerate. // swiftlint:disable all import Foundation /// SQL statements for vault creation and migration. public struct VaultSql { /// Complete database schema SQL for creating a new vault with the latest schema. public static let completeSchema = """ ${indentedSql} """ ${migrationsDict} /// Get migration SQL for upgrading from a specific revision. public static func getMigrationSql(fromRevision: Int) -> String? { return migrations[fromRevision] } } // swiftlint:enable all `; } /** * Generate Kotlin VaultSql object */ function generateKotlinVaultSql(completeSchemaSql, migrations) { const escapedSql = escapeKotlinString(completeSchemaSql); // Generate migration entries const migrationEntries = Object.entries(migrations).map(([key, sql]) => { const escapedMigrationSql = escapeKotlinString(sql); return ` ${key} to """ ${escapedMigrationSql} """.trimIndent()`; }).join(',\n'); const migrationsMap = Object.keys(migrations).length > 0 ? ` /** * Migration SQL scripts indexed by migration number. * Key is the source migration number (migration FROM this version). */ val migrations: Map = mapOf( ${migrationEntries}, )` : ` /** * Migration SQL scripts indexed by migration number. */ val migrations: Map = emptyMap()`; return `// // This file is auto-generated from core/vault/src/sql/SqlConstants.ts // Do not edit this file directly. Run './build.sh' in core/vault to regenerate. @file:Suppress("all") package net.aliasvault.app.vaultstore.models /** * SQL statements for vault creation and migration. */ object VaultSql { /** * Complete database schema SQL for creating a new vault with the latest schema. */ val completeSchema = """ ${escapedSql} """.trimIndent() ${migrationsMap} /** * Get migration SQL for upgrading from a specific revision. */ fun getMigrationSql(fromRevision: Int): String? { return migrations[fromRevision] } } `; } /** * Ensure directory exists */ function ensureDir(filePath) { const dir = path.dirname(filePath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } } /** * Main execution */ function main() { console.log('Generating vault SQL for Swift and Kotlin...\n'); // Read TypeScript VaultVersions source if (!fs.existsSync(TS_VERSIONS_SOURCE)) { throw new Error(`Source file not found: ${TS_VERSIONS_SOURCE}`); } const tsVersionsContent = fs.readFileSync(TS_VERSIONS_SOURCE, 'utf8'); const versions = parseVaultVersions(tsVersionsContent); if (versions.length === 0) { throw new Error('No vault versions found in source file'); } console.log(`Parsed ${versions.length} vault versions`); console.log(`Latest version: ${versions[versions.length - 1].version} (revision ${versions[versions.length - 1].revision})`); // Read TypeScript SqlConstants source if (!fs.existsSync(TS_SQL_SOURCE)) { throw new Error(`Source file not found: ${TS_SQL_SOURCE}`); } const tsSqlContent = fs.readFileSync(TS_SQL_SOURCE, 'utf8'); const completeSchemaSql = parseCompleteSchemaSql(tsSqlContent); const migrations = parseMigrationScripts(tsSqlContent); console.log(`Parsed complete schema SQL (${completeSchemaSql.length} chars)`); console.log(`Parsed ${Object.keys(migrations).length} migration scripts`); // Generate Swift VaultVersions const swiftVersionsPath = path.join(SWIFT_OUTPUT_DIR, 'VaultVersions.swift'); ensureDir(swiftVersionsPath); const swiftVersionsContent = generateSwiftVaultVersions(versions); fs.writeFileSync(swiftVersionsPath, swiftVersionsContent, 'utf8'); console.log(`Generated: ${swiftVersionsPath}`); // Generate Swift VaultSql const swiftSqlPath = path.join(SWIFT_OUTPUT_DIR, 'VaultSql.swift'); ensureDir(swiftSqlPath); const swiftSqlContent = generateSwiftVaultSql(completeSchemaSql, migrations); fs.writeFileSync(swiftSqlPath, swiftSqlContent, 'utf8'); console.log(`Generated: ${swiftSqlPath}`); // Generate Kotlin VaultVersions const kotlinVersionsPath = path.join(KOTLIN_OUTPUT_DIR, 'VaultVersions.kt'); ensureDir(kotlinVersionsPath); const kotlinVersionsContent = generateKotlinVaultVersions(versions); fs.writeFileSync(kotlinVersionsPath, kotlinVersionsContent, 'utf8'); console.log(`Generated: ${kotlinVersionsPath}`); // Generate Kotlin VaultSql const kotlinSqlPath = path.join(KOTLIN_OUTPUT_DIR, 'VaultSql.kt'); ensureDir(kotlinSqlPath); const kotlinSqlContent = generateKotlinVaultSql(completeSchemaSql, migrations); fs.writeFileSync(kotlinSqlPath, kotlinSqlContent, 'utf8'); console.log(`Generated: ${kotlinSqlPath}`); console.log('\nVault SQL generation complete!'); } main();