symfluence 0.5.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,205 @@
1
+ # SYMFLUENCE - npm Package
2
+
3
+ Pre-compiled hydrological modeling tools for SYMFLUENCE framework.
4
+
5
+ ## What's Included
6
+
7
+ This package provides pre-built binaries for:
8
+
9
+ - **SUMMA** - Structure for Unifying Multiple Modeling Alternatives
10
+ - **mizuRoute** - Multi-scale routing model
11
+ - **FUSE** - Framework for Understanding Structural Errors
12
+ - **NGEN** - NOAA Next Generation Water Resources Modeling Framework
13
+ - **TauDEM** - Terrain Analysis Using Digital Elevation Models
14
+
15
+ ## Installation
16
+
17
+ ### Global Installation (Recommended)
18
+
19
+ ```bash
20
+ npm install -g symfluence
21
+ ```
22
+
23
+ This will:
24
+ 1. Download platform-specific pre-compiled binaries (~50-100 MB)
25
+ 2. Extract them to your global npm directory
26
+ 3. Make the `symfluence` command available
27
+
28
+ ### Local Installation
29
+
30
+ ```bash
31
+ npm install symfluence
32
+ ```
33
+
34
+ ## Supported Platforms
35
+
36
+ - **Linux**: x86_64 (Ubuntu 22.04+, RHEL 9+, Debian 12+)
37
+ - **macOS**: ARM64 (Apple Silicon M1/M2/M3, macOS 12+)
38
+
39
+ ## System Requirements
40
+
41
+ ### Linux
42
+
43
+ - **OS**: Ubuntu 22.04+, RHEL 9+, or Debian 12+
44
+ - **glibc**: ≥ 2.35
45
+ - **Libraries** (must be installed):
46
+ ```bash
47
+ sudo apt-get install libnetcdf19 libnetcdff7 libhdf5-103 libgdal32
48
+ ```
49
+
50
+ ### macOS
51
+
52
+ - **OS**: macOS 12 (Monterey) or later
53
+ - **Architecture**: Apple Silicon (ARM64)
54
+ - **Libraries** (install via Homebrew):
55
+ ```bash
56
+ brew install netcdf netcdf-fortran hdf5 gdal
57
+ ```
58
+
59
+ For detailed requirements, see [SYSTEM_REQUIREMENTS.md](https://github.com/DarriEy/SYMFLUENCE/blob/main/docs/SYSTEM_REQUIREMENTS.md).
60
+
61
+ ## Usage
62
+
63
+ ### Check Installation
64
+
65
+ ```bash
66
+ symfluence info
67
+ ```
68
+
69
+ This shows:
70
+ - Installed version
71
+ - Platform information
72
+ - Available tools
73
+ - Build metadata
74
+ - Binary directory path
75
+
76
+ ### Use Tools Directly
77
+
78
+ #### Option 1: Add to PATH
79
+
80
+ ```bash
81
+ # Bash/Zsh
82
+ export PATH="$(npm root -g)/symfluence/dist/bin:$PATH"
83
+
84
+ # Fish
85
+ set -x PATH (npm root -g)/symfluence/dist/bin $PATH
86
+ ```
87
+
88
+ Then run tools directly:
89
+ ```bash
90
+ summa --version
91
+ mizuroute --help
92
+ ngen --version
93
+ ```
94
+
95
+ #### Option 2: Use Full Path
96
+
97
+ ```bash
98
+ $(npm root -g)/symfluence/dist/bin/summa --version
99
+ ```
100
+
101
+ #### Option 3: Use with SYMFLUENCE Python Package
102
+
103
+ ```bash
104
+ # Install Python package
105
+ pip install symfluence
106
+
107
+ # Configure to use npm-installed binaries
108
+ export SYMFLUENCE_DATA="$(npm root -g)/symfluence/dist"
109
+ ```
110
+
111
+ ### Get Binary Directory
112
+
113
+ ```bash
114
+ symfluence path
115
+ ```
116
+
117
+ ## Commands
118
+
119
+ ```bash
120
+ symfluence info # Show installation info and available tools
121
+ symfluence version # Show version
122
+ symfluence path # Show binary directory path
123
+ symfluence help # Show help
124
+ ```
125
+
126
+ ## Troubleshooting
127
+
128
+ ### Installation Fails
129
+
130
+ 1. **Check platform support**:
131
+ ```bash
132
+ node -e "console.log(process.platform, process.arch)"
133
+ ```
134
+ Must be `linux x64` or `darwin arm64`
135
+
136
+ 2. **Check internet connection**: Downloads from GitHub Releases
137
+
138
+ 3. **Verify release exists**:
139
+ https://github.com/DarriEy/SYMFLUENCE/releases
140
+
141
+ 4. **Try manual installation**: See repository README
142
+
143
+ ### "libnetcdf.so.19: not found" (Linux)
144
+
145
+ Install required libraries:
146
+ ```bash
147
+ sudo apt-get install libnetcdf19 libnetcdff7 libhdf5-103
148
+ ```
149
+
150
+ ### "dyld: Library not loaded" (macOS)
151
+
152
+ Install required libraries:
153
+ ```bash
154
+ brew install netcdf netcdf-fortran hdf5
155
+ ```
156
+
157
+ ### "version `GLIBC_2.35' not found" (Linux)
158
+
159
+ Your system has an older glibc. Options:
160
+ - Upgrade to Ubuntu 22.04+ / RHEL 9+ / Debian 12+
161
+ - Build from source (see repository docs)
162
+ - Use Docker (see repository docs)
163
+
164
+ ## Development
165
+
166
+ ### Local Testing
167
+
168
+ ```bash
169
+ # In the npm/ directory
170
+ npm install . # Test installation
171
+ node install.js # Test download manually
172
+ ./bin/symfluence info # Test CLI
173
+ ```
174
+
175
+ ### Publishing
176
+
177
+ ```bash
178
+ # Update version in package.json to match release tag
179
+ npm publish
180
+ ```
181
+
182
+ ## Documentation
183
+
184
+ - **Repository**: https://github.com/DarriEy/SYMFLUENCE
185
+ - **System Requirements**: [docs/SYSTEM_REQUIREMENTS.md](https://github.com/DarriEy/SYMFLUENCE/blob/main/docs/SYSTEM_REQUIREMENTS.md)
186
+ - **Dynamic Linking Strategy**: [docs/DYNAMIC_LINKING_STRATEGY.md](https://github.com/DarriEy/SYMFLUENCE/blob/main/docs/DYNAMIC_LINKING_STRATEGY.md)
187
+ - **Issues**: https://github.com/DarriEy/SYMFLUENCE/issues
188
+
189
+ ## License
190
+
191
+ GPL-3.0 - See repository for details.
192
+
193
+ ## Contributing
194
+
195
+ This package provides pre-built binaries only. For contributing to the tools themselves or the Python framework, see the main repository.
196
+
197
+ ## Credits
198
+
199
+ - **SUMMA**: Martyn Clark and NCAR
200
+ - **mizuRoute**: Naoki Mizukami and NCAR
201
+ - **FUSE**: Martyn Clark
202
+ - **NGEN**: NOAA-OWP
203
+ - **TauDEM**: David Tarboton, Utah State University
204
+
205
+ SYMFLUENCE framework developed by Darri Eythorsson.
package/bin/symfluence ADDED
@@ -0,0 +1,214 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * SYMFLUENCE CLI wrapper
4
+ * Provides information about installed binaries and tools
5
+ */
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+ const { getPlatformName } = require('../lib/platform');
10
+
11
+ const distDir = path.join(__dirname, '..', 'dist');
12
+ const binDir = path.join(distDir, 'bin');
13
+ const toolchainFile = path.join(distDir, 'toolchain.json');
14
+
15
+ /**
16
+ * Check if SYMFLUENCE binaries are installed
17
+ * @returns {boolean}
18
+ */
19
+ function isInstalled() {
20
+ return fs.existsSync(distDir) && fs.existsSync(binDir);
21
+ }
22
+
23
+ /**
24
+ * Get list of installed tools
25
+ * @returns {Array<string>}
26
+ */
27
+ function getInstalledTools() {
28
+ if (!fs.existsSync(binDir)) {
29
+ return [];
30
+ }
31
+
32
+ return fs.readdirSync(binDir)
33
+ .filter(f => {
34
+ const fullPath = path.join(binDir, f);
35
+ const stats = fs.statSync(fullPath);
36
+ return stats.isFile() && (stats.mode & 0o111); // Executable
37
+ })
38
+ .sort();
39
+ }
40
+
41
+ /**
42
+ * Read toolchain metadata
43
+ * @returns {Object|null}
44
+ */
45
+ function getToolchain() {
46
+ if (!fs.existsSync(toolchainFile)) {
47
+ return null;
48
+ }
49
+
50
+ try {
51
+ return JSON.parse(fs.readFileSync(toolchainFile, 'utf8'));
52
+ } catch (err) {
53
+ return null;
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Display help information
59
+ */
60
+ function showHelp() {
61
+ console.log('SYMFLUENCE - Hydrological Modeling Framework\n');
62
+ console.log('Usage: symfluence [command]\n');
63
+ console.log('Commands:');
64
+ console.log(' info Show installed tools and build information');
65
+ console.log(' version Show version information');
66
+ console.log(' path Show binary directory path');
67
+ console.log(' help Show this help message\n');
68
+ console.log('Environment:');
69
+ console.log(' To use tools directly, add to your PATH:');
70
+ console.log(` export PATH="$(npm root -g)/symfluence/dist/bin:$PATH"\n`);
71
+ console.log('Documentation: https://github.com/DarriEy/SYMFLUENCE');
72
+ }
73
+
74
+ /**
75
+ * Display version information
76
+ */
77
+ function showVersion() {
78
+ const packageJson = require('../package.json');
79
+ const toolchain = getToolchain();
80
+
81
+ console.log(`symfluence v${packageJson.version}`);
82
+
83
+ if (toolchain) {
84
+ console.log(`Platform: ${toolchain.platform || 'unknown'}`);
85
+ console.log(`Build date: ${toolchain.build_date || 'unknown'}`);
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Display installed tools and build information
91
+ */
92
+ function showInfo() {
93
+ if (!isInstalled()) {
94
+ console.error('❌ SYMFLUENCE binaries not found.');
95
+ console.error(' Run: npm install -g symfluence');
96
+ process.exit(1);
97
+ }
98
+
99
+ const packageJson = require('../package.json');
100
+ const toolchain = getToolchain();
101
+ const tools = getInstalledTools();
102
+
103
+ console.log('╔════════════════════════════════════════════╗');
104
+ console.log('║ SYMFLUENCE Installation Info ║');
105
+ console.log('╚════════════════════════════════════════════╝\n');
106
+
107
+ console.log(`📦 Version: ${packageJson.version}`);
108
+ console.log(`📍 Platform: ${getPlatformName()}`);
109
+
110
+ if (toolchain) {
111
+ console.log(`🔧 Build Platform: ${toolchain.platform || 'unknown'}`);
112
+ console.log(`📅 Build Date: ${toolchain.build_date || 'unknown'}`);
113
+
114
+ if (toolchain.compilers) {
115
+ console.log('\n🛠️ Compilers:');
116
+ if (toolchain.compilers.fortran) {
117
+ console.log(` Fortran: ${toolchain.compilers.fortran}`);
118
+ }
119
+ if (toolchain.compilers.c) {
120
+ console.log(` C: ${toolchain.compilers.c}`);
121
+ }
122
+ }
123
+
124
+ if (toolchain.libraries) {
125
+ console.log('\n📚 Libraries:');
126
+ if (toolchain.libraries.netcdf) {
127
+ console.log(` NetCDF: ${toolchain.libraries.netcdf}`);
128
+ }
129
+ if (toolchain.libraries.hdf5) {
130
+ console.log(` HDF5: ${toolchain.libraries.hdf5}`);
131
+ }
132
+ }
133
+ }
134
+
135
+ console.log('\n🔨 Installed Tools:');
136
+ if (tools.length === 0) {
137
+ console.log(' (none found)');
138
+ } else {
139
+ tools.forEach(tool => {
140
+ const toolPath = path.join(binDir, tool);
141
+ console.log(` ✓ ${tool}`);
142
+
143
+ // Show additional info from toolchain if available
144
+ if (toolchain && toolchain.tools && toolchain.tools[tool]) {
145
+ const toolInfo = toolchain.tools[tool];
146
+ if (toolInfo.commit) {
147
+ console.log(` Commit: ${toolInfo.commit.substring(0, 8)}`);
148
+ }
149
+ if (toolInfo.branch) {
150
+ console.log(` Branch: ${toolInfo.branch}`);
151
+ }
152
+ }
153
+ });
154
+ }
155
+
156
+ console.log('\n📂 Binary Directory:');
157
+ console.log(` ${binDir}`);
158
+
159
+ console.log('\n💡 Usage:');
160
+ console.log(' Add to PATH:');
161
+ console.log(` export PATH="${binDir}:$PATH"`);
162
+ console.log(' Or use full path:');
163
+ if (tools.length > 0) {
164
+ console.log(` ${path.join(binDir, tools[0])} --help`);
165
+ }
166
+
167
+ console.log('\n📖 Documentation:');
168
+ console.log(' https://github.com/DarriEy/SYMFLUENCE');
169
+ console.log(' https://github.com/DarriEy/SYMFLUENCE/blob/main/docs/SYSTEM_REQUIREMENTS.md\n');
170
+ }
171
+
172
+ /**
173
+ * Show binary directory path
174
+ */
175
+ function showPath() {
176
+ if (!isInstalled()) {
177
+ console.error('❌ SYMFLUENCE binaries not found.');
178
+ process.exit(1);
179
+ }
180
+ console.log(binDir);
181
+ }
182
+
183
+ /**
184
+ * Main CLI entry point
185
+ */
186
+ function main() {
187
+ const args = process.argv.slice(2);
188
+ const command = args[0] || 'help';
189
+
190
+ switch (command) {
191
+ case 'info':
192
+ showInfo();
193
+ break;
194
+
195
+ case 'version':
196
+ case '-v':
197
+ case '--version':
198
+ showVersion();
199
+ break;
200
+
201
+ case 'path':
202
+ showPath();
203
+ break;
204
+
205
+ case 'help':
206
+ case '-h':
207
+ case '--help':
208
+ default:
209
+ showHelp();
210
+ break;
211
+ }
212
+ }
213
+
214
+ main();
package/index.js ADDED
@@ -0,0 +1,124 @@
1
+ /**
2
+ * SYMFLUENCE npm package - Programmatic API
3
+ * Provides access to binary paths and metadata
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const { getPlatform, getPlatformName, isPlatformSupported } = require('./lib/platform');
9
+
10
+ const DIST_DIR = path.join(__dirname, 'dist');
11
+ const BIN_DIR = path.join(DIST_DIR, 'bin');
12
+ const TOOLCHAIN_FILE = path.join(DIST_DIR, 'toolchain.json');
13
+
14
+ /**
15
+ * Check if binaries are installed
16
+ * @returns {boolean}
17
+ */
18
+ function isInstalled() {
19
+ return fs.existsSync(DIST_DIR) && fs.existsSync(BIN_DIR);
20
+ }
21
+
22
+ /**
23
+ * Get the binary directory path
24
+ * @returns {string|null} Path to bin directory, or null if not installed
25
+ */
26
+ function getBinDir() {
27
+ return isInstalled() ? BIN_DIR : null;
28
+ }
29
+
30
+ /**
31
+ * Get path to a specific tool
32
+ * @param {string} toolName - Name of the tool (e.g., 'summa', 'mizuroute')
33
+ * @returns {string|null} Full path to tool, or null if not found
34
+ */
35
+ function getToolPath(toolName) {
36
+ if (!isInstalled()) {
37
+ return null;
38
+ }
39
+
40
+ const toolPath = path.join(BIN_DIR, toolName);
41
+ return fs.existsSync(toolPath) ? toolPath : null;
42
+ }
43
+
44
+ /**
45
+ * Get list of installed tools
46
+ * @returns {Array<string>} Array of tool names
47
+ */
48
+ function getInstalledTools() {
49
+ if (!isInstalled()) {
50
+ return [];
51
+ }
52
+
53
+ try {
54
+ return fs.readdirSync(BIN_DIR)
55
+ .filter(f => {
56
+ const fullPath = path.join(BIN_DIR, f);
57
+ const stats = fs.statSync(fullPath);
58
+ return stats.isFile() && (stats.mode & 0o111); // Executable
59
+ })
60
+ .sort();
61
+ } catch (err) {
62
+ return [];
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Get toolchain metadata
68
+ * @returns {Object|null} Toolchain metadata, or null if not available
69
+ */
70
+ function getToolchain() {
71
+ if (!fs.existsSync(TOOLCHAIN_FILE)) {
72
+ return null;
73
+ }
74
+
75
+ try {
76
+ return JSON.parse(fs.readFileSync(TOOLCHAIN_FILE, 'utf8'));
77
+ } catch (err) {
78
+ return null;
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Get package version
84
+ * @returns {string}
85
+ */
86
+ function getVersion() {
87
+ return require('./package.json').version;
88
+ }
89
+
90
+ /**
91
+ * Get all available paths for environment setup
92
+ * @returns {Object} Object with paths
93
+ */
94
+ function getPaths() {
95
+ return {
96
+ dist: DIST_DIR,
97
+ bin: BIN_DIR,
98
+ toolchain: TOOLCHAIN_FILE,
99
+ };
100
+ }
101
+
102
+ module.exports = {
103
+ // Installation checks
104
+ isInstalled,
105
+ isPlatformSupported,
106
+
107
+ // Paths
108
+ getBinDir,
109
+ getToolPath,
110
+ getPaths,
111
+
112
+ // Tool information
113
+ getInstalledTools,
114
+ getToolchain,
115
+ getVersion,
116
+
117
+ // Platform information
118
+ getPlatform,
119
+ getPlatformName,
120
+
121
+ // Constants
122
+ DIST_DIR,
123
+ BIN_DIR,
124
+ };
package/install.js ADDED
@@ -0,0 +1,270 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * SYMFLUENCE npm installer
4
+ * Downloads and extracts pre-built binaries from GitHub Releases
5
+ */
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+ const https = require('https');
10
+ const crypto = require('crypto');
11
+ const { execSync } = require('child_process');
12
+ const { getPlatform, getPlatformName } = require('./lib/platform');
13
+
14
+ const PACKAGE_VERSION = require('./package.json').version;
15
+ const GITHUB_REPO = 'DarriEy/SYMFLUENCE';
16
+
17
+ /**
18
+ * Construct the download URL for the current platform
19
+ * @param {string} platform - Platform identifier (e.g., 'macos-arm64')
20
+ * @returns {string} Full download URL
21
+ */
22
+ function getDownloadUrl(platform) {
23
+ const tag = `v${PACKAGE_VERSION}`;
24
+ const filename = `symfluence-tools-${tag}-${platform}.tar.gz`;
25
+ return `https://github.com/${GITHUB_REPO}/releases/download/${tag}/${filename}`;
26
+ }
27
+
28
+ /**
29
+ * Download a file from URL with progress tracking
30
+ * @param {string} url - URL to download from
31
+ * @param {string} dest - Destination file path
32
+ * @returns {Promise<void>}
33
+ */
34
+ async function downloadFile(url, dest) {
35
+ return new Promise((resolve, reject) => {
36
+ const file = fs.createWriteStream(dest);
37
+
38
+ const request = https.get(url, {
39
+ headers: { 'User-Agent': 'symfluence-npm-installer' }
40
+ }, (response) => {
41
+ // Handle redirects
42
+ if (response.statusCode === 302 || response.statusCode === 301) {
43
+ file.close();
44
+ fs.unlinkSync(dest);
45
+ downloadFile(response.headers.location, dest).then(resolve).catch(reject);
46
+ return;
47
+ }
48
+
49
+ if (response.statusCode !== 200) {
50
+ file.close();
51
+ fs.unlinkSync(dest);
52
+ reject(new Error(
53
+ `Download failed: ${response.statusCode} ${response.statusMessage}\n` +
54
+ `URL: ${url}`
55
+ ));
56
+ return;
57
+ }
58
+
59
+ const totalBytes = parseInt(response.headers['content-length'], 10);
60
+ let downloadedBytes = 0;
61
+ let lastPercent = -1;
62
+
63
+ response.on('data', (chunk) => {
64
+ downloadedBytes += chunk.length;
65
+ const percent = Math.floor((downloadedBytes / totalBytes) * 100);
66
+
67
+ // Only update display every 5% to reduce output noise
68
+ if (percent !== lastPercent && percent % 5 === 0) {
69
+ const mb = (downloadedBytes / 1024 / 1024).toFixed(1);
70
+ const totalMb = (totalBytes / 1024 / 1024).toFixed(1);
71
+ process.stdout.write(`\r📥 Downloading... ${percent}% (${mb}/${totalMb} MB)`);
72
+ lastPercent = percent;
73
+ }
74
+ });
75
+
76
+ response.pipe(file);
77
+
78
+ file.on('finish', () => {
79
+ file.close();
80
+ console.log('\n✅ Download complete');
81
+ resolve();
82
+ });
83
+
84
+ file.on('error', (err) => {
85
+ fs.unlinkSync(dest);
86
+ reject(err);
87
+ });
88
+ });
89
+
90
+ request.on('error', (err) => {
91
+ if (fs.existsSync(dest)) {
92
+ fs.unlinkSync(dest);
93
+ }
94
+ reject(err);
95
+ });
96
+ });
97
+ }
98
+
99
+ /**
100
+ * Verify file checksum against published SHA256
101
+ * @param {string} file - Path to file to verify
102
+ * @param {string} checksumUrl - URL of .sha256 file
103
+ * @returns {Promise<void>}
104
+ */
105
+ async function verifyChecksum(file, checksumUrl) {
106
+ console.log('🔐 Verifying checksum...');
107
+
108
+ try {
109
+ // Download checksum file
110
+ const checksumData = await new Promise((resolve, reject) => {
111
+ let data = '';
112
+ https.get(checksumUrl, {
113
+ headers: { 'User-Agent': 'symfluence-npm-installer' }
114
+ }, (res) => {
115
+ if (res.statusCode === 302 || res.statusCode === 301) {
116
+ // Follow redirect
117
+ https.get(res.headers.location, (redirectRes) => {
118
+ redirectRes.on('data', chunk => data += chunk);
119
+ redirectRes.on('end', () => resolve(data));
120
+ }).on('error', reject);
121
+ return;
122
+ }
123
+ res.on('data', chunk => data += chunk);
124
+ res.on('end', () => resolve(data));
125
+ }).on('error', reject);
126
+ });
127
+
128
+ // Extract expected hash (format: "hash filename")
129
+ const expectedHash = checksumData.trim().split(/\s+/)[0];
130
+
131
+ // Calculate actual hash
132
+ const fileBuffer = fs.readFileSync(file);
133
+ const hash = crypto.createHash('sha256');
134
+ hash.update(fileBuffer);
135
+ const actualHash = hash.digest('hex');
136
+
137
+ if (expectedHash.toLowerCase() !== actualHash.toLowerCase()) {
138
+ throw new Error(
139
+ 'Checksum mismatch! File may be corrupted.\n' +
140
+ ` Expected: ${expectedHash}\n` +
141
+ ` Actual: ${actualHash}`
142
+ );
143
+ }
144
+
145
+ console.log('✅ Checksum verified');
146
+ } catch (err) {
147
+ console.warn('⚠️ Could not verify checksum:', err.message);
148
+ console.warn(' Proceeding anyway, but installation may be corrupted...');
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Extract tarball to destination directory
154
+ * @param {string} tarball - Path to tarball
155
+ * @param {string} destDir - Destination directory
156
+ */
157
+ function extractTarball(tarball, destDir) {
158
+ console.log('📦 Extracting binaries...');
159
+
160
+ // Use --strip-components=1 to remove the top-level directory
161
+ const extractCmd = `tar -xzf "${tarball}" -C "${destDir}" --strip-components=1`;
162
+
163
+ try {
164
+ execSync(extractCmd, { stdio: 'inherit' });
165
+ console.log('✅ Extraction complete');
166
+ } catch (err) {
167
+ throw new Error(`Extraction failed: ${err.message}`);
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Main installation function
173
+ */
174
+ async function install() {
175
+ console.log('╔════════════════════════════════════════════╗');
176
+ console.log('║ SYMFLUENCE Binary Installer ║');
177
+ console.log('╚════════════════════════════════════════════╝\n');
178
+
179
+ // Detect platform
180
+ let platform;
181
+ try {
182
+ platform = getPlatform();
183
+ } catch (err) {
184
+ console.error('❌', err.message);
185
+ console.error('\n📖 For manual installation, see:');
186
+ console.error(' https://github.com/DarriEy/SYMFLUENCE#installation\n');
187
+ process.exit(1);
188
+ }
189
+
190
+ console.log(`📍 Platform: ${getPlatformName()} (${platform})`);
191
+ console.log(`📦 Version: ${PACKAGE_VERSION}\n`);
192
+
193
+ const url = getDownloadUrl(platform);
194
+ const checksumUrl = `${url}.sha256`;
195
+
196
+ console.log(`🔗 Downloading from GitHub Releases...`);
197
+ console.log(` ${url}\n`);
198
+
199
+ const distDir = path.join(__dirname, 'dist');
200
+ const tarballPath = path.join(__dirname, 'symfluence-tools.tar.gz');
201
+
202
+ // Clean and create dist directory
203
+ if (fs.existsSync(distDir)) {
204
+ console.log('🧹 Cleaning previous installation...');
205
+ fs.rmSync(distDir, { recursive: true, force: true });
206
+ }
207
+ fs.mkdirSync(distDir, { recursive: true });
208
+
209
+ try {
210
+ // Download tarball
211
+ await downloadFile(url, tarballPath);
212
+
213
+ // Verify checksum
214
+ await verifyChecksum(tarballPath, checksumUrl);
215
+
216
+ // Extract
217
+ extractTarball(tarballPath, distDir);
218
+
219
+ // Cleanup tarball
220
+ fs.unlinkSync(tarballPath);
221
+
222
+ // Display installation info
223
+ console.log('\n╔════════════════════════════════════════════╗');
224
+ console.log('║ 🎉 Installation Complete! ║');
225
+ console.log('╚════════════════════════════════════════════╝\n');
226
+
227
+ console.log('📦 Installed Tools:');
228
+ const binDir = path.join(distDir, 'bin');
229
+ if (fs.existsSync(binDir)) {
230
+ const tools = fs.readdirSync(binDir).filter(f => {
231
+ const fullPath = path.join(binDir, f);
232
+ return fs.statSync(fullPath).isFile();
233
+ });
234
+ tools.forEach(tool => console.log(` ✓ ${tool}`));
235
+ }
236
+
237
+ console.log('\n📖 Next Steps:');
238
+ console.log(' 1. Check installation: symfluence --help');
239
+ console.log(' 2. View available tools: ls $(npm root -g)/symfluence/dist/bin');
240
+ console.log(' 3. Install Python package: pip install symfluence\n');
241
+
242
+ console.log('📚 Documentation: https://github.com/DarriEy/SYMFLUENCE\n');
243
+
244
+ } catch (err) {
245
+ console.error('\n❌ Installation failed:', err.message);
246
+ console.error('\n📖 Troubleshooting:');
247
+ console.error(' 1. Check your internet connection');
248
+ console.error(' 2. Verify the release exists:');
249
+ console.error(` https://github.com/${GITHUB_REPO}/releases/tag/v${PACKAGE_VERSION}`);
250
+ console.error(' 3. Check system requirements:');
251
+ console.error(' https://github.com/DarriEy/SYMFLUENCE/blob/main/docs/SYSTEM_REQUIREMENTS.md');
252
+ console.error(' 4. Try manual installation:');
253
+ console.error(' https://github.com/DarriEy/SYMFLUENCE#installation\n');
254
+
255
+ // Clean up on failure
256
+ if (fs.existsSync(tarballPath)) {
257
+ fs.unlinkSync(tarballPath);
258
+ }
259
+ if (fs.existsSync(distDir)) {
260
+ fs.rmSync(distDir, { recursive: true, force: true });
261
+ }
262
+
263
+ process.exit(1);
264
+ }
265
+ }
266
+
267
+ // Run installer if executed directly (not required)
268
+ if (require.main === module) {
269
+ install();
270
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Platform detection and mapping for SYMFLUENCE binaries
3
+ */
4
+
5
+ // Mapping from Node.js platform/arch to SYMFLUENCE release naming
6
+ const PLATFORM_MAP = {
7
+ 'darwin-arm64': 'macos-arm64',
8
+ 'darwin-x64': 'macos-x64', // Currently not supported, but reserved
9
+ 'linux-x64': 'linux-x86_64',
10
+ 'linux-arm64': 'linux-aarch64', // Future support
11
+ };
12
+
13
+ /**
14
+ * Get the current platform identifier for SYMFLUENCE releases
15
+ * @returns {string} Platform identifier (e.g., 'macos-arm64', 'linux-x86_64')
16
+ * @throws {Error} If platform is not supported
17
+ */
18
+ function getPlatform() {
19
+ const platform = process.platform;
20
+ const arch = process.arch;
21
+ const key = `${platform}-${arch}`;
22
+
23
+ if (!PLATFORM_MAP[key]) {
24
+ throw new Error(
25
+ `Unsupported platform: ${platform} ${arch}\n` +
26
+ `Supported platforms: ${Object.keys(PLATFORM_MAP).join(', ')}\n` +
27
+ `Please file an issue at: https://github.com/DarriEy/SYMFLUENCE/issues`
28
+ );
29
+ }
30
+
31
+ return PLATFORM_MAP[key];
32
+ }
33
+
34
+ /**
35
+ * Check if current platform is supported
36
+ * @returns {boolean} True if platform is supported
37
+ */
38
+ function isPlatformSupported() {
39
+ const platform = process.platform;
40
+ const arch = process.arch;
41
+ const key = `${platform}-${arch}`;
42
+ return key in PLATFORM_MAP;
43
+ }
44
+
45
+ /**
46
+ * Get user-friendly platform name
47
+ * @returns {string} Platform name for display
48
+ */
49
+ function getPlatformName() {
50
+ const platform = process.platform;
51
+ const arch = process.arch;
52
+
53
+ const names = {
54
+ 'darwin-arm64': 'macOS (Apple Silicon)',
55
+ 'darwin-x64': 'macOS (Intel)',
56
+ 'linux-x64': 'Linux (x86_64)',
57
+ 'linux-arm64': 'Linux (ARM64)',
58
+ };
59
+
60
+ return names[`${platform}-${arch}`] || `${platform} ${arch}`;
61
+ }
62
+
63
+ /**
64
+ * Get all supported platforms
65
+ * @returns {Array<string>} List of supported platform identifiers
66
+ */
67
+ function getSupportedPlatforms() {
68
+ return Object.values(PLATFORM_MAP);
69
+ }
70
+
71
+ module.exports = {
72
+ getPlatform,
73
+ isPlatformSupported,
74
+ getPlatformName,
75
+ getSupportedPlatforms,
76
+ PLATFORM_MAP,
77
+ };
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "symfluence",
3
+ "version": "0.5.8",
4
+ "description": "Structure for Unifying Multiple Modeling Alternatives - Pre-compiled hydrological modeling tools",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "symfluence": "bin/symfluence"
8
+ },
9
+ "scripts": {
10
+ "postinstall": "node install.js"
11
+ },
12
+ "os": [
13
+ "darwin",
14
+ "linux"
15
+ ],
16
+ "cpu": [
17
+ "x64",
18
+ "arm64"
19
+ ],
20
+ "engines": {
21
+ "node": ">=14"
22
+ },
23
+ "keywords": [
24
+ "hydrology",
25
+ "modeling",
26
+ "summa",
27
+ "mizuroute",
28
+ "ngen",
29
+ "fuse",
30
+ "taudem",
31
+ "scientific",
32
+ "water-resources"
33
+ ],
34
+ "author": "Darri Eythorsson",
35
+ "license": "GPL-3.0",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/DarriEy/SYMFLUENCE.git"
39
+ },
40
+ "homepage": "https://github.com/DarriEy/SYMFLUENCE",
41
+ "bugs": {
42
+ "url": "https://github.com/DarriEy/SYMFLUENCE/issues"
43
+ },
44
+ "preferGlobal": true,
45
+ "files": [
46
+ "bin/",
47
+ "lib/",
48
+ "dist/",
49
+ "install.js",
50
+ "README.md"
51
+ ]
52
+ }