electerm 3.3.0 → 3.3.2

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/npm/electerm CHANGED
@@ -1,4 +1,73 @@
1
- #!/bin/bash
2
- cd `dirname $0`
3
- cd ..
4
- ./lib/node_modules/electerm/electerm/electerm
1
+ #!/usr/bin/env node
2
+ /**
3
+ * electerm CLI launcher (cross-platform)
4
+ * After npm i -g electerm, the postinstall script downloads and installs the binary.
5
+ * This script simply finds and launches the installed binary.
6
+ *
7
+ * Binary locations:
8
+ * macOS: /Applications/electerm.app/Contents/MacOS/electerm
9
+ * Windows: <package>/electerm/electerm.exe
10
+ * Linux: <package>/electerm/electerm
11
+ */
12
+
13
+ const path = require('path')
14
+ const fs = require('fs')
15
+ const { spawn } = require('child_process')
16
+ const os = require('os')
17
+
18
+ const plat = os.platform()
19
+ const packageRoot = path.resolve(__dirname, '..')
20
+
21
+ function getElectermExePath () {
22
+ if (plat === 'darwin') {
23
+ const appBinary = '/Applications/electerm.app/Contents/MacOS/electerm'
24
+ if (fs.existsSync(appBinary)) {
25
+ return appBinary
26
+ }
27
+ return path.join(packageRoot, 'electerm', 'electerm')
28
+ }
29
+
30
+ if (plat === 'win32') {
31
+ return path.join(packageRoot, 'electerm', 'electerm.exe')
32
+ }
33
+
34
+ return path.join(packageRoot, 'electerm', 'electerm')
35
+ }
36
+
37
+ function launchElecterm () {
38
+ const exePath = getElectermExePath()
39
+
40
+ if (!fs.existsSync(exePath)) {
41
+ console.error('electerm binary not found at:', exePath)
42
+ console.error('')
43
+ console.error('The binary may not have been installed properly.')
44
+ console.error('Try running manually:')
45
+ console.error(' node', path.join(packageRoot, 'npm', 'install.js'))
46
+ process.exit(1)
47
+ }
48
+
49
+ const child = spawn(exePath, process.argv.slice(2), {
50
+ stdio: 'inherit',
51
+ detached: plat !== 'win32',
52
+ windowsHide: false
53
+ })
54
+
55
+ if (plat !== 'win32') {
56
+ child.unref()
57
+ }
58
+
59
+ child.on('error', (err) => {
60
+ console.error('Failed to start electerm:', err.message)
61
+ process.exit(1)
62
+ })
63
+
64
+ child.on('exit', (code) => {
65
+ process.exit(code || 0)
66
+ })
67
+ }
68
+
69
+ if (require.main === module) {
70
+ launchElecterm()
71
+ }
72
+
73
+ module.exports = { launchElecterm }
package/npm/install.js CHANGED
@@ -1,12 +1,19 @@
1
1
  /**
2
2
  * install electerm from binary
3
+ * After npm i -g electerm, running `electerm` command will:
4
+ * 1. Download the appropriate binary for the platform
5
+ * 2. Extract it to the package directory (electerm/)
6
+ * 3. The bash script (npm/electerm) then launches the extracted binary
7
+ *
8
+ * This script only downloads and extracts. Launching is handled by the bash script.
3
9
  */
4
10
 
5
11
  const os = require('os')
6
- const { resolve } = require('path')
7
- const { exec, rm, mv } = require('shelljs')
12
+ const { resolve, join } = require('path')
13
+ const { execSync, rm, mv } = require('shelljs')
8
14
  const { execFile } = require('child_process')
9
- const { phin, download } = require('./utils')
15
+ const fs = require('fs')
16
+ const { phin, download, extractTarGz, GITHUB_PROXY, applyProxy } = require('./utils')
10
17
 
11
18
  const plat = os.platform()
12
19
  const arch = os.arch()
@@ -15,13 +22,15 @@ const { homepage } = require('../package.json')
15
22
  const releaseInfoUrl = `${homepage}/data/electerm-github-release.json?_=${+new Date()}`
16
23
  const versionUrl = `${homepage}/version.html?_=${+new Date()}`
17
24
 
25
+ // Directory where electerm package is installed
26
+ const packageRoot = resolve(__dirname, '..')
27
+ // Directory where the extracted binary will live
28
+ const extractDir = join(packageRoot, 'electerm')
29
+
18
30
  // ---------------------------------------------------------------------------
19
31
  // Security helpers
20
32
  // ---------------------------------------------------------------------------
21
33
 
22
- /**
23
- * Validate that a version string is a plain semver (e.g. "1.2.3" or "v1.2.3").
24
- */
25
34
  function sanitizeVersion (ver) {
26
35
  const clean = String(ver).trim().replace(/^v/, '')
27
36
  if (!/^\d+\.\d+\.\d+$/.test(clean)) {
@@ -32,9 +41,6 @@ function sanitizeVersion (ver) {
32
41
  return clean
33
42
  }
34
43
 
35
- /**
36
- * Validate that a release asset filename contains only safe characters.
37
- */
38
44
  function sanitizeFilename (name) {
39
45
  const clean = String(name).trim()
40
46
  if (!/^[\w.-]+$/.test(clean)) {
@@ -49,14 +55,6 @@ function sanitizeFilename (name) {
49
55
  // Core helpers
50
56
  // ---------------------------------------------------------------------------
51
57
 
52
- function down (url, extract = true) {
53
- const local = resolve(__dirname, '../')
54
- console.log('downloading ' + url)
55
- return download(url, local, { extract }).then(() => {
56
- console.log('done!')
57
- })
58
- }
59
-
60
58
  function getVer () {
61
59
  return phin({
62
60
  url: versionUrl,
@@ -79,50 +77,42 @@ function getReleaseInfo (filter) {
79
77
  }
80
78
 
81
79
  function showFinalMessage () {
82
- console.log('\n========================================')
80
+ console.log('')
81
+ console.log('========================================')
83
82
  console.log('electerm installation complete!')
84
83
  console.log('========================================')
85
- console.log('\nFor more information, documentation, and updates, please visit:')
86
- console.log('\x1b[36m%s\x1b[0m', 'https://electerm.html5beta.com')
87
- console.log('\nThank you for using electerm!')
88
- console.log('========================================\n')
84
+ console.log('')
85
+ console.log('For more information, documentation, and updates, please visit:')
86
+ console.log('https://electerm.html5beta.com')
87
+ console.log('')
88
+ console.log('Thank you for using electerm!')
89
+ console.log('========================================')
90
+ console.log('')
89
91
  }
90
92
 
91
93
  // ---------------------------------------------------------------------------
92
94
  // Platform detection helpers
93
95
  // ---------------------------------------------------------------------------
94
96
 
95
- // Check if running on Windows 7 or earlier
96
97
  function isWindows7OrEarlier (platform, release) {
97
98
  if (platform !== 'win32') return false
98
- // Windows 7 is NT 6.1, Windows 8 is NT 6.2, Windows 10 is NT 10.0
99
99
  const [major, minor] = release.split('.').map(Number)
100
100
  return major < 10 && (major < 6 || (major === 6 && minor <= 1))
101
101
  }
102
102
 
103
- // Check if running on macOS 10.x (older than Big Sur 11.0)
104
103
  function isMacOS10 (platform, release) {
105
104
  if (platform !== 'darwin') return false
106
- // Darwin kernel version: macOS 11 (Big Sur) = Darwin 20.x, macOS 10.15 = Darwin 19.x
107
105
  const majorVersion = parseInt(release.split('.')[0], 10)
108
106
  return majorVersion < 20
109
107
  }
110
108
 
111
- // Check if running on Linux with old glibc (< 2.34)
112
- function isLinuxLegacy (platform, glibcVersion) {
109
+ function isLinuxLegacy (platform) {
113
110
  if (platform !== 'linux') return false
114
- if (typeof glibcVersion === 'number') {
115
- return glibcVersion < 2.34
116
- }
117
111
  try {
118
- const result = exec('ldd --version 2>&1 | head -n1', { silent: true })
119
- if (result.code !== 0) return false
120
- const output = result.stdout || ''
121
- // Extract version number like "ldd (GNU libc) 2.31" or "ldd (Ubuntu GLIBC 2.35-0ubuntu3) 2.35"
122
- const match = output.match(/(\d+\.\d+)\s*$/)
112
+ const result = execSync('ldd --version 2>&1 | head -n1', { encoding: 'utf8' })
113
+ const match = result.match(/(\d+\.\d+)\s*$/)
123
114
  if (match) {
124
- const version = parseFloat(match[1])
125
- return version < 2.34
115
+ return parseFloat(match[1]) < 2.34
126
116
  }
127
117
  return false
128
118
  } catch (e) {
@@ -130,137 +120,450 @@ function isLinuxLegacy (platform, glibcVersion) {
130
120
  }
131
121
  }
132
122
 
133
- // Get the file pattern for download based on platform/arch/legacy status
134
- function getDownloadPattern (platform, architecture, options = {}) {
135
- const { win7, mac10, linuxLegacy } = options
123
+ // ---------------------------------------------------------------------------
124
+ // Launch the extracted binary
125
+ // ---------------------------------------------------------------------------
136
126
 
137
- if (platform === 'win32') {
138
- if (win7) {
139
- return { pattern: /electerm-\d+\.\d+\.\d+-win7\.tar\.gz$/, type: 'win7' }
140
- } else if (architecture === 'arm64') {
141
- return { pattern: /electerm-\d+\.\d+\.\d+-win-arm64\.tar\.gz$/, type: 'win-arm64' }
142
- } else {
143
- return { pattern: /electerm-\d+\.\d+\.\d+-win-x64\.tar\.gz$/, type: 'win-x64' }
144
- }
145
- } else if (platform === 'darwin') {
146
- if (mac10) {
147
- return { pattern: /mac10-x64\.dmg$/, type: 'mac10-x64' }
148
- } else if (architecture === 'arm64') {
149
- return { pattern: /mac-arm64\.dmg$/, type: 'mac-arm64' }
150
- } else {
151
- return { pattern: /mac-x64\.dmg$/, type: 'mac-x64' }
152
- }
153
- } else if (platform === 'linux') {
154
- const suffix = linuxLegacy ? '-legacy' : ''
155
- if (architecture === 'arm64') {
156
- return { pattern: new RegExp(`linux-arm64${suffix}\\.tar\\.gz$`), type: `linux-arm64${suffix}` }
157
- } else if (architecture === 'arm') {
158
- return { pattern: new RegExp(`linux-armv7l${suffix}\\.tar\\.gz$`), type: `linux-armv7l${suffix}` }
159
- } else {
160
- return { pattern: new RegExp(`linux-x64${suffix}\\.tar\\.gz$`), type: `linux-x64${suffix}` }
161
- }
127
+ /**
128
+ * Get the path to the extracted electerm executable
129
+ */
130
+ function getElectermExePath () {
131
+ if (plat === 'win32') {
132
+ return join(extractDir, 'electerm.exe')
162
133
  }
134
+ // Linux and macOS (if extracted)
135
+ return join(extractDir, 'electerm')
136
+ }
163
137
 
164
- return { pattern: null, type: 'unsupported' }
138
+ /**
139
+ * Check if the electerm binary has been extracted already
140
+ */
141
+ function isElectermExtracted () {
142
+ const exePath = getElectermExePath()
143
+ return fs.existsSync(exePath)
165
144
  }
166
145
 
167
146
  // ---------------------------------------------------------------------------
168
147
  // Platform installers
148
+ // ---------------------------------------------------------------------------
149
+
169
150
  async function runLinux (folderName, filePattern) {
170
151
  const rawVer = await getVer()
171
- const ver = sanitizeVersion(rawVer) // throws if tampered
152
+ const ver = sanitizeVersion(rawVer)
172
153
 
173
- const target = resolve(__dirname, `../electerm-${ver}-${folderName}`)
174
- const targetNew = resolve(__dirname, '../electerm')
154
+ console.log(` Version: ${ver}`)
155
+ console.log(` Target: ${folderName}`)
175
156
 
176
- // Use shelljs array API — no shell string interpolation
177
- rm('-rf', [target, targetNew])
157
+ const target = join(packageRoot, `electerm-${ver}-${folderName}`)
178
158
 
159
+ // Clean up old installations
160
+ rm('-rf', [target, extractDir])
161
+
162
+ console.log(' Fetching release info...')
179
163
  const releaseInfo = await getReleaseInfo(r => r.name.includes(filePattern))
180
164
  if (!releaseInfo) {
181
165
  throw new Error(`No release found for pattern: ${filePattern}`)
182
166
  }
183
167
 
184
- await down(releaseInfo.browser_download_url)
168
+ // Download without extracting to packageRoot directly
169
+ // We'll extract to a temp location first
170
+ const tmpDir = join(packageRoot, '.electerm-tmp')
171
+ rm('-rf', tmpDir)
172
+ fs.mkdirSync(tmpDir, { recursive: true })
185
173
 
186
- mv(target, targetNew)
187
- showFinalMessage()
188
- exec('electerm')
189
- }
174
+ const proxyUrl = applyProxy(releaseInfo.browser_download_url)
175
+ console.log(` URL: ${proxyUrl}`)
190
176
 
191
- async function runMac (archName) {
192
- const pattern = new RegExp(`mac-${archName}\\.dmg$`)
193
- const releaseInfo = await getReleaseInfo(r => pattern.test(r.name))
194
- if (!releaseInfo) {
195
- throw new Error(`No release found for Mac ${archName}`)
196
- }
177
+ const { filepath } = await download(releaseInfo.browser_download_url, tmpDir, { extract: false, displayName: releaseInfo.name })
197
178
 
198
- const safeName = sanitizeFilename(releaseInfo.name) // throws if tampered
199
- await down(releaseInfo.browser_download_url, false)
179
+ // Extract to tmpDir (keeps top-level folder name)
180
+ await extractTarGz(filepath, tmpDir)
200
181
 
201
- const target = resolve(__dirname, '../', safeName)
202
- showFinalMessage()
182
+ // Find the extracted folder (should be the only directory)
183
+ const entries = fs.readdirSync(tmpDir)
184
+ const extractedFolder = entries.find(e => fs.statSync(join(tmpDir, e)).isDirectory())
203
185
 
204
- // execFile does not spawn a shell — no injection possible
205
- execFile('open', [target])
206
- }
186
+ if (!extractedFolder) {
187
+ throw new Error('No folder found in extracted archive')
188
+ }
207
189
 
208
- async function runMac10 () {
209
- const releaseInfo = await getReleaseInfo(r => /mac10-x64\.dmg$/.test(r.name))
210
- if (!releaseInfo) {
211
- throw new Error('No release found for macOS 10.x')
190
+ // Move to extractDir
191
+ console.log(` Installing to: ${extractDir}`)
192
+ mv(join(tmpDir, extractedFolder), extractDir)
193
+
194
+ // Fix chrome-sandbox permissions on Linux (Electron requires specific permissions)
195
+ if (plat === 'linux') {
196
+ const chromeSandboxPath = join(extractDir, 'chrome-sandbox')
197
+ if (fs.existsSync(chromeSandboxPath)) {
198
+ console.log(' Fixing chrome-sandbox permissions...')
199
+ fs.chmodSync(chromeSandboxPath, 0o4755)
200
+ }
212
201
  }
213
202
 
214
- const safeName = sanitizeFilename(releaseInfo.name) // throws if tampered
215
- await down(releaseInfo.browser_download_url, false)
203
+ // Clean up temp files
204
+ rm('-rf', tmpDir)
216
205
 
217
- const target = resolve(__dirname, '../', safeName)
218
206
  showFinalMessage()
219
-
220
- // execFile does not spawn a shell — no injection possible
221
- execFile('open', [target])
222
207
  }
223
208
 
224
209
  async function runWin (archName) {
210
+ console.log(' [DEBUG] runWin started')
211
+ console.log(` [DEBUG] packageRoot: ${packageRoot}`)
212
+ console.log(` [DEBUG] extractDir: ${extractDir}`)
213
+
225
214
  const rawVer = await getVer()
226
- const ver = sanitizeVersion(rawVer) // consistent hardening
215
+ const ver = sanitizeVersion(rawVer)
216
+
217
+ console.log(` [DEBUG] Raw version from server: ${rawVer}`)
218
+ console.log(` Sanitized version: ${ver}`)
219
+ console.log(` Target: win-${archName}`)
227
220
 
228
- const target = resolve(__dirname, `../electerm-${ver}-win-${archName}`)
229
- const targetNew = resolve(__dirname, '../electerm')
221
+ const target = join(packageRoot, `electerm-${ver}-win-${archName}`)
222
+ console.log(` [DEBUG] Target folder: ${target}`)
230
223
 
231
- rm('-rf', [target, targetNew])
224
+ console.log(' Cleaning old installations...')
225
+ rm('-rf', [target, extractDir])
226
+ console.log(' [DEBUG] Old installations cleaned')
232
227
 
233
228
  const pattern = new RegExp(`electerm-\\d+\\.\\d+\\.\\d+-win-${archName}\\.tar\\.gz$`)
229
+ console.log(' Fetching release info...')
234
230
  const releaseInfo = await getReleaseInfo(r => pattern.test(r.name))
235
231
  if (!releaseInfo) {
236
232
  throw new Error(`No release found for Windows ${archName}`)
237
233
  }
234
+ console.log(` [DEBUG] Release info found: ${JSON.stringify(releaseInfo, null, 2)}`)
235
+
236
+ const tmpDir = join(packageRoot, '.electerm-tmp')
237
+ console.log(` [DEBUG] Creating temp directory: ${tmpDir}`)
238
+ rm('-rf', tmpDir)
239
+ fs.mkdirSync(tmpDir, { recursive: true })
240
+
241
+ const proxyUrl = applyProxy(releaseInfo.browser_download_url)
242
+ console.log(` [DEBUG] Proxy URL: ${proxyUrl}`)
243
+ console.log(` [DEBUG] Download URL: ${releaseInfo.browser_download_url}`)
244
+
245
+ console.log(' Downloading...')
246
+ const { filepath } = await download(releaseInfo.browser_download_url, tmpDir, { extract: false, displayName: releaseInfo.name })
247
+ console.log(` [DEBUG] Downloaded to: ${filepath}`)
248
+ console.log(` [DEBUG] File exists: ${fs.existsSync(filepath)}`)
249
+ console.log(` [DEBUG] File size: ${fs.statSync(filepath).size}`)
250
+
251
+ console.log(' Extracting...')
252
+ await extractTarGz(filepath, tmpDir)
253
+ console.log(' [DEBUG] Extraction complete')
254
+
255
+ console.log(' [DEBUG] Listing temp directory contents:')
256
+ const entries = fs.readdirSync(tmpDir)
257
+ entries.forEach(e => {
258
+ const fullPath = join(tmpDir, e)
259
+ const stat = fs.statSync(fullPath)
260
+ console.log(` [DEBUG] ${e} - ${stat.isDirectory() ? 'DIR' : 'FILE'} (${stat.size} bytes)`)
261
+ })
262
+
263
+ const extractedFolder = entries.find(e => fs.statSync(join(tmpDir, e)).isDirectory())
264
+
265
+ if (!extractedFolder) {
266
+ console.error(' [DEBUG] No directory found in extracted archive')
267
+ console.error(' [DEBUG] All entries:', entries)
268
+ throw new Error('No folder found in extracted archive')
269
+ }
270
+
271
+ console.log(` [DEBUG] Extracted folder: ${extractedFolder}`)
272
+ console.log(' [DEBUG] Contents of extracted folder:')
273
+ const extractedContents = fs.readdirSync(join(tmpDir, extractedFolder))
274
+ extractedContents.forEach(e => {
275
+ const fullPath = join(tmpDir, extractedFolder, e)
276
+ const stat = fs.statSync(fullPath)
277
+ console.log(` [DEBUG] ${e} - ${stat.isDirectory() ? 'DIR' : 'FILE'} (${stat.size} bytes)`)
278
+ })
279
+
280
+ console.log(` Installing to: ${extractDir}`)
281
+ fs.renameSync(join(tmpDir, extractedFolder), extractDir)
282
+ console.log(' [DEBUG] Renamed folder to extractDir')
283
+
284
+ console.log(' [DEBUG] Verifying extractDir contents:')
285
+ const installContents = fs.readdirSync(extractDir)
286
+ installContents.forEach(e => {
287
+ const fullPath = join(extractDir, e)
288
+ const stat = fs.statSync(fullPath)
289
+ console.log(` [DEBUG] ${e} - ${stat.isDirectory() ? 'DIR' : 'FILE'} (${stat.size} bytes)`)
290
+ })
291
+
292
+ rm('-rf', tmpDir)
293
+ console.log(' [DEBUG] Temp directory cleaned')
294
+
295
+ const exePath = getElectermExePath()
296
+ console.log(` [DEBUG] Expected exe path: ${exePath}`)
297
+ console.log(` [DEBUG] Exe exists: ${fs.existsSync(exePath)}`)
298
+ if (!fs.existsSync(exePath)) {
299
+ throw new Error(`electerm.exe not found at ${exePath} after extraction. Archive may have an unexpected structure.`)
300
+ }
238
301
 
239
- await down(releaseInfo.browser_download_url)
240
- await mv(target, targetNew)
241
302
  showFinalMessage()
242
- require('child_process').execFile(`${targetNew}\\electerm.exe`)
243
303
  }
244
304
 
245
- // Windows 7 specific version
246
305
  async function runWin7 () {
306
+ console.log(' [DEBUG] runWin7 started')
307
+ console.log(` [DEBUG] packageRoot: ${packageRoot}`)
308
+ console.log(` [DEBUG] extractDir: ${extractDir}`)
309
+
247
310
  const rawVer = await getVer()
248
- const ver = sanitizeVersion(rawVer) // consistent hardening
311
+ const ver = sanitizeVersion(rawVer)
312
+
313
+ console.log(` [DEBUG] Raw version from server: ${rawVer}`)
314
+ console.log(` Sanitized version: ${ver}`)
315
+ console.log(' Target: win7')
249
316
 
250
- const target = resolve(__dirname, `../electerm-${ver}-win7`)
251
- const targetNew = resolve(__dirname, '../electerm')
317
+ const target = join(packageRoot, `electerm-${ver}-win7`)
318
+ console.log(` [DEBUG] Target folder: ${target}`)
252
319
 
253
- rm('-rf', [target, targetNew])
320
+ console.log(' Cleaning old installations...')
321
+ rm('-rf', [target, extractDir])
322
+ console.log(' [DEBUG] Old installations cleaned')
254
323
 
324
+ console.log(' Fetching release info...')
255
325
  const releaseInfo = await getReleaseInfo(r => /electerm-\d+\.\d+\.\d+-win7\.tar\.gz$/.test(r.name))
256
326
  if (!releaseInfo) {
257
327
  throw new Error('No release found for Windows 7')
258
328
  }
329
+ console.log(` [DEBUG] Release info found: ${JSON.stringify(releaseInfo, null, 2)}`)
330
+
331
+ const tmpDir = join(packageRoot, '.electerm-tmp')
332
+ console.log(` [DEBUG] Creating temp directory: ${tmpDir}`)
333
+ rm('-rf', tmpDir)
334
+ fs.mkdirSync(tmpDir, { recursive: true })
335
+
336
+ const proxyUrl = applyProxy(releaseInfo.browser_download_url)
337
+ console.log(` [DEBUG] Proxy URL: ${proxyUrl}`)
338
+ console.log(` [DEBUG] Download URL: ${releaseInfo.browser_download_url}`)
339
+
340
+ console.log(' Downloading...')
341
+ const { filepath } = await download(releaseInfo.browser_download_url, tmpDir, { extract: false, displayName: releaseInfo.name })
342
+ console.log(` [DEBUG] Downloaded to: ${filepath}`)
343
+ console.log(` [DEBUG] File exists: ${fs.existsSync(filepath)}`)
344
+ console.log(` [DEBUG] File size: ${fs.statSync(filepath).size}`)
345
+
346
+ console.log(' Extracting...')
347
+ await extractTarGz(filepath, tmpDir)
348
+ console.log(' [DEBUG] Extraction complete')
349
+
350
+ console.log(' [DEBUG] Listing temp directory contents:')
351
+ const entries = fs.readdirSync(tmpDir)
352
+ entries.forEach(e => {
353
+ const fullPath = join(tmpDir, e)
354
+ const stat = fs.statSync(fullPath)
355
+ console.log(` [DEBUG] ${e} - ${stat.isDirectory() ? 'DIR' : 'FILE'} (${stat.size} bytes)`)
356
+ })
357
+
358
+ const extractedFolder = entries.find(e => fs.statSync(join(tmpDir, e)).isDirectory())
359
+
360
+ if (!extractedFolder) {
361
+ console.error(' [DEBUG] No directory found in extracted archive')
362
+ console.error(' [DEBUG] All entries:', entries)
363
+ throw new Error('No folder found in extracted archive')
364
+ }
365
+
366
+ console.log(` [DEBUG] Extracted folder: ${extractedFolder}`)
367
+ console.log(' [DEBUG] Contents of extracted folder:')
368
+ const extractedContents = fs.readdirSync(join(tmpDir, extractedFolder))
369
+ extractedContents.forEach(e => {
370
+ const fullPath = join(tmpDir, extractedFolder, e)
371
+ const stat = fs.statSync(fullPath)
372
+ console.log(` [DEBUG] ${e} - ${stat.isDirectory() ? 'DIR' : 'FILE'} (${stat.size} bytes)`)
373
+ })
259
374
 
260
- await down(releaseInfo.browser_download_url)
261
- await mv(target, targetNew)
375
+ console.log(` Installing to: ${extractDir}`)
376
+ fs.renameSync(join(tmpDir, extractedFolder), extractDir)
377
+ console.log(' [DEBUG] Renamed folder to extractDir')
378
+
379
+ console.log(' [DEBUG] Verifying extractDir contents:')
380
+ const installContents = fs.readdirSync(extractDir)
381
+ installContents.forEach(e => {
382
+ const fullPath = join(extractDir, e)
383
+ const stat = fs.statSync(fullPath)
384
+ console.log(` [DEBUG] ${e} - ${stat.isDirectory() ? 'DIR' : 'FILE'} (${stat.size} bytes)`)
385
+ })
386
+
387
+ rm('-rf', tmpDir)
388
+ console.log(' [DEBUG] Temp directory cleaned')
389
+
390
+ const exePath = getElectermExePath()
391
+ console.log(` [DEBUG] Expected exe path: ${exePath}`)
392
+ console.log(` [DEBUG] Exe exists: ${fs.existsSync(exePath)}`)
393
+ if (!fs.existsSync(exePath)) {
394
+ throw new Error(`electerm.exe not found at ${exePath} after extraction. Archive may have an unexpected structure.`)
395
+ }
396
+
397
+ showFinalMessage()
398
+ }
399
+
400
+ /**
401
+ * Mount a DMG, copy the .app to /Applications, then detach
402
+ * @param {string} dmgPath - Path to the DMG file
403
+ * @returns {Promise<string>} - Path to the installed app
404
+ */
405
+ function installFromDmg (dmgPath) {
406
+ return new Promise((resolve, reject) => {
407
+ // Step 1: Mount the DMG
408
+ console.log(' Mounting DMG...')
409
+ execFile('hdiutil', ['attach', dmgPath, '-nobrowse', '-readonly'], (err, stdout) => {
410
+ if (err) {
411
+ reject(new Error(`Failed to mount DMG: ${err.message}`))
412
+ return
413
+ }
414
+
415
+ // Parse mount point
416
+ const mountMatch = stdout.match(/(\/Volumes\/[^\n]+)/)
417
+ if (!mountMatch) {
418
+ reject(new Error('Could not find mount point'))
419
+ return
420
+ }
421
+
422
+ const mountPoint = mountMatch[1].trim()
423
+ console.log(` Mounted at: ${mountPoint}`)
424
+
425
+ // Step 2: Find the .app bundle
426
+ try {
427
+ const entries = fs.readdirSync(mountPoint)
428
+ const appFile = entries.find(e => e.endsWith('.app'))
429
+
430
+ if (!appFile) {
431
+ // Try to detach before rejecting
432
+ execFileSyncIgnore('hdiutil', ['detach', mountPoint])
433
+ reject(new Error('No .app bundle found in DMG'))
434
+ return
435
+ }
436
+
437
+ const appSource = join(mountPoint, appFile)
438
+ const appDest = `/Applications/${appFile}`
439
+
440
+ // Check if app already exists
441
+ if (fs.existsSync(appDest)) {
442
+ console.log(` Existing app found at ${appDest}, replacing...`)
443
+ // Remove existing app
444
+ rm('-rf', appDest)
445
+ }
446
+
447
+ // Step 3: Copy the app to /Applications
448
+ console.log(` Installing ${appFile} to /Applications...`)
449
+ execFile('cp', ['-R', appSource, appDest], (cpErr) => {
450
+ // Step 4: Detach the DMG (always, regardless of copy result)
451
+ console.log(' Detaching DMG...')
452
+ execFile('hdiutil', ['detach', mountPoint], (detachErr) => {
453
+ if (detachErr) {
454
+ console.log(' Warning: Failed to detach DMG:', detachErr.message)
455
+ } else {
456
+ console.log(' DMG detached')
457
+ }
458
+
459
+ if (cpErr) {
460
+ reject(new Error(`Failed to copy app: ${cpErr.message}`))
461
+ return
462
+ }
463
+
464
+ console.log(` App installed to: ${appDest}`)
465
+ resolve(appDest)
466
+ })
467
+ })
468
+ } catch (e) {
469
+ // Try to detach before rejecting
470
+ execFileSyncIgnore('hdiutil', ['detach', mountPoint])
471
+ reject(e)
472
+ }
473
+ })
474
+ })
475
+ }
476
+
477
+ /**
478
+ * Execute a file synchronously, ignoring errors
479
+ */
480
+ function execFileSyncIgnore (cmd, args) {
481
+ try {
482
+ execSync(cmd, args, { stdio: 'ignore' })
483
+ } catch (e) {
484
+ // Ignore
485
+ }
486
+ }
487
+
488
+ async function runMac (archName) {
489
+ const pattern = new RegExp(`mac-${archName}\\.dmg$`)
490
+ console.log(' Fetching release info...')
491
+ const releaseInfo = await getReleaseInfo(r => pattern.test(r.name))
492
+ if (!releaseInfo) {
493
+ throw new Error(`No release found for Mac ${archName}`)
494
+ }
495
+
496
+ const safeName = sanitizeFilename(releaseInfo.name)
497
+ const proxyUrl = applyProxy(releaseInfo.browser_download_url)
498
+ console.log(` URL: ${proxyUrl}`)
499
+
500
+ await download(releaseInfo.browser_download_url, packageRoot, { extract: false, displayName: releaseInfo.name })
501
+
502
+ const dmgPath = join(packageRoot, safeName)
262
503
  showFinalMessage()
263
- require('child_process').execFile(`${targetNew}\\electerm.exe`)
504
+
505
+ // Install from DMG automatically
506
+ try {
507
+ await installFromDmg(dmgPath)
508
+
509
+ // Clean up DMG
510
+ try {
511
+ fs.unlinkSync(dmgPath)
512
+ console.log(' Cleaned up DMG file')
513
+ } catch (e) {
514
+ // Ignore cleanup errors
515
+ }
516
+
517
+ console.log('')
518
+ console.log(' Installation complete! You can now launch electerm from /Applications')
519
+ } catch (err) {
520
+ console.error('')
521
+ console.error(' Warning: Automatic installation failed:', err.message)
522
+ console.error(' Please manually copy the app from the DMG to /Applications')
523
+ console.error('')
524
+ console.log(' Opening DMG for manual installation...')
525
+ execFile('open', [dmgPath])
526
+ }
527
+ }
528
+
529
+ async function runMac10 () {
530
+ console.log(' Fetching release info...')
531
+ const releaseInfo = await getReleaseInfo(r => /mac10-x64\.dmg$/.test(r.name))
532
+ if (!releaseInfo) {
533
+ throw new Error('No release found for macOS 10.x')
534
+ }
535
+
536
+ const safeName = sanitizeFilename(releaseInfo.name)
537
+ const proxyUrl = applyProxy(releaseInfo.browser_download_url)
538
+ console.log(` URL: ${proxyUrl}`)
539
+
540
+ await download(releaseInfo.browser_download_url, packageRoot, { extract: false, displayName: releaseInfo.name })
541
+
542
+ const dmgPath = join(packageRoot, safeName)
543
+ showFinalMessage()
544
+
545
+ // Install from DMG automatically
546
+ try {
547
+ await installFromDmg(dmgPath)
548
+
549
+ // Clean up DMG
550
+ try {
551
+ fs.unlinkSync(dmgPath)
552
+ console.log(' Cleaned up DMG file')
553
+ } catch (e) {
554
+ // Ignore cleanup errors
555
+ }
556
+
557
+ console.log('')
558
+ console.log(' Installation complete! You can now launch electerm from /Applications')
559
+ } catch (err) {
560
+ console.error('')
561
+ console.error(' Warning: Automatic installation failed:', err.message)
562
+ console.error(' Please manually copy the app from the DMG to /Applications')
563
+ console.error('')
564
+ console.log(' Opening DMG for manual installation...')
565
+ execFile('open', [dmgPath])
566
+ }
264
567
  }
265
568
 
266
569
  // ---------------------------------------------------------------------------
@@ -268,18 +571,28 @@ async function runWin7 () {
268
571
  // ---------------------------------------------------------------------------
269
572
 
270
573
  async function main () {
271
- console.log(`Detected platform: ${plat}, architecture: ${arch}`)
574
+ console.log('')
575
+ console.log('========================================')
576
+ console.log('electerm binary installer')
577
+ console.log('========================================')
578
+ console.log(`Platform: ${plat}, Architecture: ${arch}`)
579
+
580
+ if (GITHUB_PROXY) {
581
+ console.log(`GitHub Proxy: ${GITHUB_PROXY}`)
582
+ }
583
+
584
+ console.log('')
272
585
 
273
586
  // Check for legacy systems
274
587
  const win7 = isWindows7OrEarlier(plat, os.release())
275
588
  const mac10 = isMacOS10(plat, os.release())
276
589
  const linuxLegacy = isLinuxLegacy(plat)
277
590
 
278
- if (win7) console.log('Detected: Windows 7 or earlier')
279
- if (mac10) console.log('Detected: macOS 10.x')
280
- if (linuxLegacy) console.log('Detected: Linux with glibc < 2.34 (legacy)')
591
+ if (win7) console.log(' Detected: Windows 7 or earlier')
592
+ if (mac10) console.log(' Detected: macOS 10.x')
593
+ if (linuxLegacy) console.log(' Detected: Linux with glibc < 2.34 (legacy)')
281
594
 
282
- console.log('Fetching release information...\n')
595
+ console.log(' Fetching release information...')
283
596
 
284
597
  try {
285
598
  if (plat === 'win32') {
@@ -300,7 +613,7 @@ async function main () {
300
613
  }
301
614
  } else if (plat === 'linux') {
302
615
  const suffix = linuxLegacy ? '-legacy' : ''
303
- if (arch === 'arm64') {
616
+ if (arch === 'arm64' || arch === 'aarch64') {
304
617
  await runLinux(`linux-arm64${suffix}`, `linux-arm64${suffix}.tar.gz`)
305
618
  } else if (arch === 'arm') {
306
619
  await runLinux(`linux-armv7l${suffix}`, `linux-armv7l${suffix}.tar.gz`)
@@ -311,32 +624,37 @@ async function main () {
311
624
  throw new Error(`Platform "${plat}" is not supported.`)
312
625
  }
313
626
  } catch (err) {
314
- console.error('\n========================================')
627
+ console.error('')
628
+ console.error('========================================')
315
629
  console.error('Installation failed!')
316
630
  console.error('========================================')
317
631
  console.error(`Error: ${err.message}`)
318
- console.error(`\nPlatform: ${plat}, Architecture: ${arch}`)
319
- console.error('\nPlease visit https://electerm.html5beta.com for manual download options.')
320
- console.error('========================================\n')
632
+ console.error(`Platform: ${plat}, Architecture: ${arch}`)
633
+ console.error('')
634
+ console.error('Please visit https://electerm.html5beta.com for manual download options.')
635
+ console.error('========================================')
636
+ console.error('')
321
637
  process.exit(1)
322
638
  }
323
639
  }
324
640
 
325
641
  // ---------------------------------------------------------------------------
326
- // Exports
642
+ // Exports for testing
327
643
  // ---------------------------------------------------------------------------
328
644
 
329
- // Export functions for testing
330
645
  module.exports = {
331
646
  isWindows7OrEarlier,
332
647
  isMacOS10,
333
648
  isLinuxLegacy,
334
- getDownloadPattern,
335
649
  sanitizeVersion,
336
- sanitizeFilename
650
+ sanitizeFilename,
651
+ getElectermExePath,
652
+ isElectermExtracted,
653
+ // Expose for test injection
654
+ _packageRoot: packageRoot,
655
+ _extractDir: extractDir
337
656
  }
338
657
 
339
- // Run main only if this file is executed directly
340
658
  if (require.main === module) {
341
659
  main()
342
660
  }
package/npm/utils.js CHANGED
@@ -2,6 +2,7 @@
2
2
  * Utility functions for npm installer
3
3
  * Replaces download and phin packages with native Node.js http/https and tar
4
4
  * Supports Node.js 16+
5
+ * Supports GITHUB_PROXY environment variable for proxying GitHub URLs
5
6
  */
6
7
 
7
8
  const https = require('https')
@@ -10,13 +11,46 @@ const fs = require('fs')
10
11
  const path = require('path')
11
12
  const tar = require('tar')
12
13
 
14
+ // GitHub proxy support
15
+ const GITHUB_PROXY = process.env.GITHUB_PROXY || ''
16
+
17
+ /**
18
+ * Apply GitHub proxy to URLs if configured
19
+ * @param {string} url - Original URL
20
+ * @returns {string} - Proxy URL or original URL
21
+ */
22
+ function applyProxy (url) {
23
+ if (!GITHUB_PROXY) return url
24
+ if (!url.includes('github.com')) return url
25
+
26
+ // Remove trailing slash from proxy if present
27
+ const proxy = GITHUB_PROXY.replace(/\/+$/, '')
28
+ // Ensure url has protocol
29
+ const urlWithProto = url.startsWith('http') ? url : `https://${url}`
30
+
31
+ return `${proxy}/${urlWithProto}`
32
+ }
33
+
34
+ /**
35
+ * Format bytes to human readable
36
+ */
37
+ function formatBytes (bytes) {
38
+ if (bytes === 0) return '0 B'
39
+ const k = 1024
40
+ const sizes = ['B', 'KB', 'MB', 'GB']
41
+ const i = Math.floor(Math.log(bytes) / Math.log(k))
42
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]
43
+ }
44
+
13
45
  /**
14
- * Make an HTTP GET request
46
+ * Make an HTTP GET request and download to a file with progress
15
47
  * @param {string} url - URL to fetch
16
- * @param {number} timeout - Request timeout in milliseconds (default: 15000)
17
- * @returns {Promise<string>} Response body as string
48
+ * @param {string} filepath - Destination file path
49
+ * @param {number} timeout - Request timeout in milliseconds (default: 300000 = 5min)
50
+ * @param {function} onProgress - Progress callback (received, total, percent)
51
+ * @returns {Promise<string>} Path to downloaded file
18
52
  */
19
- function httpGet (url, timeout = 15000) {
53
+ function httpDownload (url, filepath, timeout = 300000, onProgress) {
20
54
  return new Promise((resolve, reject) => {
21
55
  const client = url.startsWith('https') ? https : http
22
56
 
@@ -24,7 +58,13 @@ function httpGet (url, timeout = 15000) {
24
58
  // Handle redirects
25
59
  if (res.statusCode === 301 || res.statusCode === 302 || res.statusCode === 307) {
26
60
  if (res.headers.location) {
27
- resolve(httpGet(res.headers.location, timeout))
61
+ // Handle relative URLs
62
+ let redirectUrl = res.headers.location
63
+ if (!redirectUrl.startsWith('http://') && !redirectUrl.startsWith('https://')) {
64
+ const parsedUrl = new URL(url)
65
+ redirectUrl = `${parsedUrl.protocol}//${parsedUrl.host}${redirectUrl}`
66
+ }
67
+ resolve(httpDownload(redirectUrl, filepath, timeout, onProgress))
28
68
  return
29
69
  }
30
70
  }
@@ -34,13 +74,32 @@ function httpGet (url, timeout = 15000) {
34
74
  return
35
75
  }
36
76
 
37
- const chunks = []
38
- res.on('data', (chunk) => chunks.push(chunk))
39
- res.on('end', () => {
40
- const buffer = Buffer.concat(chunks)
41
- resolve(buffer.toString())
77
+ const total = parseInt(res.headers['content-length'] || '0', 10)
78
+ let received = 0
79
+ let lastPercent = -1
80
+
81
+ const fileStream = fs.createWriteStream(filepath)
82
+
83
+ res.on('data', (chunk) => {
84
+ received += chunk.length
85
+ if (onProgress && total > 0) {
86
+ const percent = Math.round((received / total) * 100)
87
+ if (percent !== lastPercent) {
88
+ lastPercent = percent
89
+ onProgress(received, total, percent)
90
+ }
91
+ }
92
+ })
93
+
94
+ res.pipe(fileStream)
95
+ fileStream.on('finish', () => {
96
+ fileStream.close()
97
+ resolve(filepath)
98
+ })
99
+ fileStream.on('error', (err) => {
100
+ fs.unlink(filepath, () => {}) // Clean up partial download
101
+ reject(err)
42
102
  })
43
- res.on('error', reject)
44
103
  })
45
104
 
46
105
  req.on('error', reject)
@@ -52,20 +111,26 @@ function httpGet (url, timeout = 15000) {
52
111
  }
53
112
 
54
113
  /**
55
- * Download a file from URL to local path
56
- * @param {string} url - URL to download from
57
- * @param {string} dest - Destination directory path
58
- * @returns {Promise<string>} Path to downloaded file
114
+ * Make an HTTP GET request and return response body as string
115
+ * @param {string} url - URL to fetch
116
+ * @param {number} timeout - Request timeout in milliseconds (default: 15000)
117
+ * @returns {Promise<string>} Response body as string
59
118
  */
60
- function downloadFile (url, dest) {
119
+ function httpGet (url, timeout = 15000) {
61
120
  return new Promise((resolve, reject) => {
62
121
  const client = url.startsWith('https') ? https : http
63
122
 
64
- const req = client.get(url, { timeout: 300000 }, (res) => {
123
+ const req = client.get(url, { timeout }, (res) => {
65
124
  // Handle redirects
66
125
  if (res.statusCode === 301 || res.statusCode === 302 || res.statusCode === 307) {
67
126
  if (res.headers.location) {
68
- resolve(downloadFile(res.headers.location, dest))
127
+ // Handle relative URLs
128
+ let redirectUrl = res.headers.location
129
+ if (!redirectUrl.startsWith('http://') && !redirectUrl.startsWith('https://')) {
130
+ const parsedUrl = new URL(url)
131
+ redirectUrl = `${parsedUrl.protocol}//${parsedUrl.host}${redirectUrl}`
132
+ }
133
+ resolve(httpGet(redirectUrl, timeout))
69
134
  return
70
135
  }
71
136
  }
@@ -75,37 +140,19 @@ function downloadFile (url, dest) {
75
140
  return
76
141
  }
77
142
 
78
- // Extract filename from URL or Content-Disposition header
79
- let filename = 'download'
80
- const contentDisposition = res.headers['content-disposition']
81
- if (contentDisposition) {
82
- const match = contentDisposition.match(/filename[^;=\n]*=(['"]?)([^'";\n]*)\1/)
83
- if (match) {
84
- filename = match[2]
85
- }
86
- } else {
87
- const urlParts = url.split('/')
88
- filename = urlParts[urlParts.length - 1].split('?')[0] || 'download'
89
- }
90
-
91
- const filepath = path.join(dest, filename)
92
- const fileStream = fs.createWriteStream(filepath)
93
-
94
- res.pipe(fileStream)
95
- fileStream.on('finish', () => {
96
- fileStream.close()
97
- resolve(filepath)
98
- })
99
- fileStream.on('error', (err) => {
100
- fs.unlink(filepath, () => {}) // Clean up partial download
101
- reject(err)
143
+ const chunks = []
144
+ res.on('data', (chunk) => chunks.push(chunk))
145
+ res.on('end', () => {
146
+ const buffer = Buffer.concat(chunks)
147
+ resolve(buffer.toString())
102
148
  })
149
+ res.on('error', reject)
103
150
  })
104
151
 
105
152
  req.on('error', reject)
106
153
  req.on('timeout', () => {
107
154
  req.destroy()
108
- reject(new Error('Download timeout after 300s'))
155
+ reject(new Error(`Request timeout after ${timeout}ms`))
109
156
  })
110
157
  })
111
158
  }
@@ -114,40 +161,74 @@ function downloadFile (url, dest) {
114
161
  * Extract a tar.gz file to destination directory
115
162
  * @param {string} filepath - Path to tar.gz file
116
163
  * @param {string} dest - Destination directory
164
+ * @param {number} strip - Number of leading path components to strip (default: 0)
117
165
  * @returns {Promise<void>}
118
166
  */
119
- function extractTarGz (filepath, dest) {
167
+ function extractTarGz (filepath, dest, strip = 0) {
120
168
  return tar.extract({
121
169
  file: filepath,
122
170
  cwd: dest,
123
- strip: 1 // Strip top-level directory
171
+ strip
124
172
  })
125
173
  }
126
174
 
127
175
  /**
128
- * Download and optionally extract a file
129
- * Replaces the download package functionality
176
+ * Download and optionally extract a file with progress
130
177
  * @param {string} url - URL to download from
131
178
  * @param {string} dest - Destination directory
132
- * @param {boolean} extract - Whether to extract the file (default: true)
133
- * @returns {Promise<void>}
179
+ * @param {object} options - Options
180
+ * @param {boolean} options.extract - Whether to extract the file (default: true)
181
+ * @param {string} options.displayName - Display name for progress output
182
+ * @returns {Promise<{filepath: string, extracted: boolean}>}
134
183
  */
135
- async function download (url, dest, { extract: doExtract = true } = {}) {
136
- console.log('downloading ' + url)
184
+ async function download (url, dest, { extract: doExtract = true, displayName } = {}) {
185
+ // Ensure dest directory exists
186
+ if (!fs.existsSync(dest)) {
187
+ fs.mkdirSync(dest, { recursive: true })
188
+ }
189
+
190
+ // Extract filename from URL
191
+ const urlParts = url.split('/')
192
+ const filename = urlParts[urlParts.length - 1].split('?')[0] || 'download'
193
+ const filepath = path.join(dest, filename)
194
+
195
+ // Apply proxy if configured
196
+ const downloadUrl = applyProxy(url)
197
+
198
+ const label = displayName || filename
199
+ const proxyInfo = GITHUB_PROXY ? ' [via proxy]' : ''
200
+
201
+ console.log('')
202
+ console.log(` Downloading: ${label}${proxyInfo}`)
203
+
204
+ let lastPercent = -1
205
+ await httpDownload(downloadUrl, filepath, 300000, (received, total, percent) => {
206
+ if (percent !== lastPercent && percent % 10 === 0) {
207
+ lastPercent = percent
208
+ const receivedStr = formatBytes(received)
209
+ const totalStr = formatBytes(total)
210
+ process.stdout.write(` Progress: ${percent}% (${receivedStr} / ${totalStr})\n`)
211
+ }
212
+ })
137
213
 
138
- const filepath = await downloadFile(url, dest)
214
+ console.log(` Progress: 100% (${formatBytes(fs.statSync(filepath).size)})`)
215
+ console.log(' Download complete!')
139
216
 
217
+ let extracted = false
140
218
  if (doExtract && (filepath.endsWith('.tar.gz') || filepath.endsWith('.tgz'))) {
219
+ console.log(' Extracting archive...')
141
220
  await extractTarGz(filepath, dest)
142
221
  // Clean up the downloaded archive
143
222
  try {
144
223
  fs.unlinkSync(filepath)
145
224
  } catch (err) {
146
- console.warn('Warning: Failed to clean up downloaded archive:', err.message)
225
+ // Ignore cleanup errors
147
226
  }
227
+ extracted = true
228
+ console.log(' Extraction complete!')
148
229
  }
149
230
 
150
- console.log('done!')
231
+ return { filepath, extracted }
151
232
  }
152
233
 
153
234
  /**
@@ -155,7 +236,7 @@ async function download (url, dest, { extract: doExtract = true } = {}) {
155
236
  * @param {object} options - Request options
156
237
  * @param {string} options.url - URL to fetch
157
238
  * @param {number} options.timeout - Request timeout (default: 15000)
158
- * @returns {Promise<{body: string, statusCode: number, headers: object}>}
239
+ * @returns {Promise<{body: Buffer, statusCode: number, headers: object}>}
159
240
  */
160
241
  async function phin (options) {
161
242
  const { url, timeout = 15000 } = options
@@ -168,13 +249,15 @@ async function phin (options) {
168
249
  }
169
250
  }
170
251
 
171
- // Export promisified version
172
252
  phin.promisified = phin
173
253
 
174
254
  module.exports = {
175
255
  httpGet,
176
- downloadFile,
256
+ httpDownload,
177
257
  extractTarGz,
178
258
  download,
179
- phin
259
+ phin,
260
+ applyProxy,
261
+ formatBytes,
262
+ GITHUB_PROXY
180
263
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "electerm",
3
- "version": "3.3.0",
3
+ "version": "3.3.2",
4
4
  "description": "Terminal/ssh/telnet/serialport/sftp client(linux, mac, win)",
5
5
  "main": "app.js",
6
6
  "bin": "npm/electerm",
@@ -1,21 +0,0 @@
1
- Copyright (c) Electron contributors
2
- Copyright (c) 2013-2020 GitHub Inc.
3
-
4
- Permission is hereby granted, free of charge, to any person obtaining
5
- a copy of this software and associated documentation files (the
6
- "Software"), to deal in the Software without restriction, including
7
- without limitation the rights to use, copy, modify, merge, publish,
8
- distribute, sublicense, and/or sell copies of the Software, and to
9
- permit persons to whom the Software is furnished to do so, subject to
10
- the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be
13
- included in all copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.