pear-install 1.0.7 → 1.0.9
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 +3 -1
- package/cmd.js +5 -1
- package/index.js +52 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ npx pear-install [link]
|
|
|
20
20
|
|
|
21
21
|
- **macOS** — apps to `/Applications`, bins to `/usr/local/bin`
|
|
22
22
|
- **Linux** — apps to `~/Applications`, `~/AppImages`, or `~/.local/bin`; bins to `~/.local/bin`
|
|
23
|
-
- **Windows** — apps
|
|
23
|
+
- **Windows** — apps installed as MSIX packages; bins (`.exe`) to `%LOCALAPPDATA%\Programs\<app>\` with the install directory added to the User `PATH`
|
|
24
24
|
|
|
25
25
|
## API
|
|
26
26
|
|
|
@@ -70,6 +70,7 @@ await corestore.close()
|
|
|
70
70
|
- `installing` → `{ link, host }`
|
|
71
71
|
- `app` → `{ app, name, version, upgrade, verlink, key, dest }`
|
|
72
72
|
- `stats` → `{ download, upload, peers }`
|
|
73
|
+
- `path` → `{ dir }` — Windows only; emitted when a bin directory is appended to the User `PATH`
|
|
73
74
|
- `final` → `{ success, installed, exists }`
|
|
74
75
|
|
|
75
76
|
## Command Integration: `/cmd`
|
|
@@ -88,6 +89,7 @@ await InstallCmd.output(json, stream)
|
|
|
88
89
|
- `installing` → `{ link, host }`
|
|
89
90
|
- `app` → `{ app, name, version, upgrade, verlink, key, dest }`
|
|
90
91
|
- `stats` → `{ download, upload, peers }`
|
|
92
|
+
- `path` → `{ dir }` — Windows only
|
|
91
93
|
- `error` → `{ code, message, stack, info, success: false }`
|
|
92
94
|
- `final` → `{ success, installed, exists }`
|
|
93
95
|
|
package/cmd.js
CHANGED
|
@@ -12,7 +12,7 @@ class InstallCmd extends Opstream {
|
|
|
12
12
|
static outputs = {
|
|
13
13
|
installing: ({ link }) => `Installing... ${link}`,
|
|
14
14
|
app: ({ app, version, upgrade, dest, key, verlink }) =>
|
|
15
|
-
`App: ${app}\nVersion: ${version}\nLink: ${upgrade}\nVerlink: ${verlink}\nPathname: ${key}\nTarget: ${dest}`,
|
|
15
|
+
`App: ${app}\nVersion: ${version}\nLink: ${typeof upgrade === 'string' ? upgrade : JSON.stringify(upgrade, null, 2)}\nVerlink: ${verlink}\nPathname: ${key}\nTarget: ${dest}`,
|
|
16
16
|
stats({ download, peers }) {
|
|
17
17
|
const dl =
|
|
18
18
|
download.bytes + download.speed === 0
|
|
@@ -20,6 +20,7 @@ class InstallCmd extends Opstream {
|
|
|
20
20
|
: ` [ ${down} ${byteSize(download.bytes)} - ${byteSize(download.speed)}/s ] `
|
|
21
21
|
return `[ Peers: ${peers} ]${dl}`
|
|
22
22
|
},
|
|
23
|
+
path: ({ dir }) => `Added to User PATH: ${dir}\n Restart shell for change to take effect.`,
|
|
23
24
|
error: ({ message }) => message,
|
|
24
25
|
final({ success, message }) {
|
|
25
26
|
if (success) return 'Installed'
|
|
@@ -78,6 +79,9 @@ class InstallCmd extends Opstream {
|
|
|
78
79
|
install.on('stats', (data) => {
|
|
79
80
|
this.push({ tag: 'stats', data })
|
|
80
81
|
})
|
|
82
|
+
install.on('path', (data) => {
|
|
83
|
+
this.push({ tag: 'path', data })
|
|
84
|
+
})
|
|
81
85
|
install.on('final', (data) => {
|
|
82
86
|
this.final = data
|
|
83
87
|
})
|
package/index.js
CHANGED
|
@@ -99,16 +99,18 @@ class Install extends ReadyResource {
|
|
|
99
99
|
const appName = productName ?? name
|
|
100
100
|
const home = os.homedir()
|
|
101
101
|
|
|
102
|
+
const localAppData = process.env.LOCALAPPDATA || path.join(home, 'AppData', 'Local')
|
|
103
|
+
|
|
102
104
|
if (bin) {
|
|
103
105
|
const bins = typeof bin === 'string' ? { [name]: bin } : bin
|
|
104
106
|
for (const binName of Object.keys(bins)) {
|
|
105
|
-
const ext = isWindows ? '.
|
|
106
|
-
const dest =
|
|
107
|
-
?
|
|
108
|
-
:
|
|
109
|
-
? path.join(
|
|
110
|
-
:
|
|
111
|
-
? path.join(
|
|
107
|
+
const ext = isWindows ? '.exe' : ''
|
|
108
|
+
const dest = to
|
|
109
|
+
? path.join(to, binName + ext)
|
|
110
|
+
: isMac
|
|
111
|
+
? path.join('/', 'usr', 'local', 'bin', binName)
|
|
112
|
+
: isWindows
|
|
113
|
+
? path.join(localAppData, 'Programs', appName, binName + ext)
|
|
112
114
|
: path.join(home, '.local', 'bin', binName)
|
|
113
115
|
this.targets.push({ filename: binName, ext, dest, isBin: true })
|
|
114
116
|
}
|
|
@@ -151,27 +153,28 @@ class Install extends ReadyResource {
|
|
|
151
153
|
const exists = []
|
|
152
154
|
const installs = []
|
|
153
155
|
for (const target of this.targets) {
|
|
154
|
-
if (
|
|
156
|
+
if (target.ext === '.msix') {
|
|
157
|
+
const escName = name.replace(/'/g, "''")
|
|
155
158
|
const ps = spawnSync('powershell', [
|
|
156
159
|
'-NoProfile',
|
|
157
160
|
'-Command',
|
|
158
|
-
|
|
161
|
+
`$null -ne (Get-AppxPackage -Name '${escName}' -ErrorAction SilentlyContinue)`
|
|
159
162
|
])
|
|
160
163
|
if (ps.stdout.toString().trim() === 'True') {
|
|
161
|
-
exists.push({ filename: target.filename, dest: target.dest })
|
|
164
|
+
exists.push({ filename: target.filename, dest: target.dest, ext: target.ext })
|
|
162
165
|
continue
|
|
163
166
|
}
|
|
164
167
|
} else if (fs.existsSync(target.dest)) {
|
|
165
|
-
exists.push({ filename: target.filename, dest: target.dest })
|
|
168
|
+
exists.push({ filename: target.filename, dest: target.dest, ext: target.ext })
|
|
166
169
|
continue
|
|
167
170
|
}
|
|
168
171
|
installs.push(target)
|
|
169
172
|
}
|
|
170
173
|
|
|
171
174
|
if (installs.length === 0) {
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
+
const lines = exists.map(({ filename, dest }) => ' ' + (dest ?? filename))
|
|
176
|
+
const header = isWindows ? 'Already installed:' : 'Refusing to overwrite existing:'
|
|
177
|
+
const message = `${header}\n${lines.join('\n')}\nTo reinstall, manually remove then rerun command`
|
|
175
178
|
throw ERR_EXISTS(message)
|
|
176
179
|
}
|
|
177
180
|
|
|
@@ -190,6 +193,7 @@ class Install extends ReadyResource {
|
|
|
190
193
|
monitor.destroy()
|
|
191
194
|
|
|
192
195
|
let installed = 0
|
|
196
|
+
const exes = new Set()
|
|
193
197
|
for (const { filename, ext, dest, isBin } of installs) {
|
|
194
198
|
const key = appPath + filename + ext
|
|
195
199
|
const verlink = plink.serialize({ drive: this.drive.core })
|
|
@@ -210,11 +214,13 @@ class Install extends ReadyResource {
|
|
|
210
214
|
throw ERR_NOT_FOUND(plink.serialize({ ...parsed, pathname: key }))
|
|
211
215
|
}
|
|
212
216
|
|
|
213
|
-
if (
|
|
217
|
+
if (ext === '.msix') {
|
|
214
218
|
const MSIXManager = require('msix-manager')
|
|
215
219
|
await new MSIXManager().addPackage(from)
|
|
216
220
|
installed++
|
|
217
221
|
continue
|
|
222
|
+
} else if (ext === '.exe') {
|
|
223
|
+
exes.add(dest)
|
|
218
224
|
}
|
|
219
225
|
|
|
220
226
|
if (isBin) {
|
|
@@ -252,6 +258,9 @@ class Install extends ReadyResource {
|
|
|
252
258
|
if (installed === 0) {
|
|
253
259
|
throw ERR_UNKNOWN('Failed to install')
|
|
254
260
|
}
|
|
261
|
+
|
|
262
|
+
for (const dest of exes) this._exe(path.dirname(dest))
|
|
263
|
+
|
|
255
264
|
this.emit('final', { success: true, installed, exists })
|
|
256
265
|
}
|
|
257
266
|
|
|
@@ -276,6 +285,34 @@ class Install extends ReadyResource {
|
|
|
276
285
|
)
|
|
277
286
|
}
|
|
278
287
|
|
|
288
|
+
_exe(dir) {
|
|
289
|
+
const read = spawnSync('powershell', [
|
|
290
|
+
'-NoProfile',
|
|
291
|
+
'-Command',
|
|
292
|
+
"[Environment]::GetEnvironmentVariable('Path', 'User')"
|
|
293
|
+
])
|
|
294
|
+
if (read.status !== 0) {
|
|
295
|
+
const err = (read.stderr || '').toString().trim()
|
|
296
|
+
throw new Error('Failed to read User PATH: ' + (err || 'powershell exit ' + read.status))
|
|
297
|
+
}
|
|
298
|
+
const current = read.stdout.toString().replace(/\r?\n$/, '')
|
|
299
|
+
const entries = current ? current.split(';').filter(Boolean) : []
|
|
300
|
+
if (entries.includes(dir)) return false
|
|
301
|
+
const next = (current ? current + ';' : '') + dir
|
|
302
|
+
const escNext = next.replace(/'/g, "''")
|
|
303
|
+
const write = spawnSync('powershell', [
|
|
304
|
+
'-NoProfile',
|
|
305
|
+
'-Command',
|
|
306
|
+
`[Environment]::SetEnvironmentVariable('Path', '${escNext}', 'User')`
|
|
307
|
+
])
|
|
308
|
+
if (write.status !== 0) {
|
|
309
|
+
const err = (write.stderr || '').toString().trim()
|
|
310
|
+
throw new Error('Failed to update User PATH: ' + (err || 'powershell exit ' + write.status))
|
|
311
|
+
}
|
|
312
|
+
this.emit('path', { dir })
|
|
313
|
+
return true
|
|
314
|
+
}
|
|
315
|
+
|
|
279
316
|
_extract(appImage, extracted, cwd, file) {
|
|
280
317
|
const { status } = spawnSync(appImage, ['--appimage-extract', file], { cwd })
|
|
281
318
|
if (status !== 0) throw new Error('appimage-extract failed')
|