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 +205 -0
- package/bin/symfluence +214 -0
- package/index.js +124 -0
- package/install.js +270 -0
- package/lib/platform.js +77 -0
- package/package.json +52 -0
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
|
+
}
|
package/lib/platform.js
ADDED
|
@@ -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
|
+
}
|