-
Notifications
You must be signed in to change notification settings - Fork 0
/
runtime.js
128 lines (122 loc) · 5.02 KB
/
runtime.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/** @typedef {import('pear-interface')} */ /* global Pear */
'use strict'
const fs = require('bare-fs')
const os = require('bare-os')
const tty = require('bare-tty')
const path = require('bare-path')
const Pipe = require('bare-pipe')
const { spawn } = require('bare-subprocess')
const env = require('bare-env')
const { command } = require('paparam')
const { isLinux, isWindows } = require('which-runtime')
const { pathToFileURL } = require('url-file-url')
const constants = require('pear-api/constants')
const parseLink = require('pear-api/parse-link')
const { ERR_INVALID_INPUT } = require('pear-api/errors')
const EXEC = isWindows
? 'pear-runtime-app\\Pear Runtime.exe'
: isLinux
? 'pear-runtime-app/pear-runtime'
: 'Pear Runtime.app/Contents/MacOS/Pear Runtime'
const BOOT = require.resolve('./boot.js')
const shell = require('pear-api/shell')
const run = require('pear-api/cmd-def/run')
class PearElectron {
constructor () {
this.stderr = null
this.ipc = Pear[Pear.constructor.IPC]
this.bin = ''
Pear.teardown(() => this.ipc.close())
const ondisk = Pear.config.key === null
if (ondisk === false) {
this.bin = path.join(constants.PLATFORM_DIR, 'experimental', require.addon.host, 'bin', EXEC)
}
}
start (info) {
let argv = Pear.argv
const parsed = shell(Pear.argv)
const cmdIx = parsed?.indices.args.cmd ?? -1
if (cmdIx > -1) argv = argv.slice(cmdIx)
const cmd = command('run', ...run)
let { args, indices, flags } = cmd.parse(argv)
let link = Pear.config.link
const { drive, pathname } = parseLink(link)
const entry = isWindows ? path.normalize(pathname.slice(1)) : pathname
const { key } = drive
const isPear = link.startsWith('pear://')
const isFile = link.startsWith('file://')
const isPath = isPear === false && isFile === false
let cwd = os.cwd()
const originalCwd = cwd
let dir = cwd
let base = null
if (key === null) {
try {
dir = fs.statSync(entry).isDirectory() ? entry : path.dirname(entry)
} catch { /* ignore */ }
base = project(dir, pathname, cwd)
dir = base.dir
if (dir !== cwd) {
global.Bare.on('exit', () => os.chdir(originalCwd)) // TODO: remove this once Pear.shutdown is used to close
Pear.teardown(() => os.chdir(originalCwd))
os.chdir(dir)
cwd = dir
}
if (isPath) {
link = pathToFileURL(path.join(dir, base.entrypoint || '/')).pathname
}
}
if (isPath) args[indices.args.link] = 'file://' + (base.entrypoint || '/')
args[indices.args.link] = args[indices.args.link].replace('://', '_||') // for Windows
if ((isLinux || isWindows) && !flags.sandbox) args.splice(indices.args.link, 0, '--no-sandbox')
const detach = args.includes('--detach')
const mountpoint = constants.MOUNT
args = [BOOT, '--runtime-info', info, '--mountpoint', mountpoint, '--start-id=' + Pear.config.startId, ...args]
const stdio = detach ? 'ignore' : ['ignore', 'inherit', 'pipe', 'overlapped']
const sp = spawn(this.bin, args, {
stdio,
cwd,
windowsHide: true,
...{ env: { ...env, NODE_PRESERVE_SYMLINKS: 1 } }
})
if (detach) return null
const pipe = sp.stdio[3]
sp.on('exit', (code) => {
this.close().finally(() => Pear.exit(code))
})
this.stderr = tty.isTTY(2) ? new tty.WriteStream(2) : new Pipe(2)
const onerr = (data) => {
const str = data.toString()
const ignore = str.indexOf('DevTools listening on ws://') > -1 ||
str.indexOf('NSApplicationDelegate.applicationSupportsSecureRestorableState') > -1 ||
str.indexOf('", source: devtools://devtools/') > -1 ||
str.indexOf('sysctlbyname for kern.hv_vmm_present failed with status -1') > -1 ||
str.indexOf('dev.i915.perf_stream_paranoid=0') > -1 ||
str.indexOf('libva error: vaGetDriverNameByIndex() failed') > -1 ||
str.indexOf('GetVSyncParametersIfAvailable() failed') > -1 ||
(str.indexOf(':ERROR:') > -1 && /:ERROR:.+cache/.test(str))
if (ignore) return
this.stderr.write(data)
}
sp.stderr.on('data', onerr)
return pipe
}
}
function project (dir, origin, cwd) {
try {
if (JSON.parse(fs.readFileSync(path.join(dir, 'package.json'))).pear) {
return { dir, origin, entrypoint: isWindows ? path.normalize(origin.slice(1)).slice(dir.length) : origin.slice(dir.length) }
}
} catch (err) {
if (err.code !== 'ENOENT' && err.code !== 'EISDIR' && err.code !== 'ENOTDIR') throw err
}
const parent = path.dirname(dir)
if (parent === dir || parent.startsWith(cwd) === false) {
const normalizedOrigin = !isWindows ? origin : path.normalize(origin.slice(1))
const cwdIsOrigin = path.relative(cwd, normalizedOrigin).length === 0
const condition = cwdIsOrigin ? `at "${cwd}"` : normalizedOrigin.includes(cwd) ? `from "${normalizedOrigin}" up to "${cwd}"` : `at "${normalizedOrigin}"`
throw ERR_INVALID_INPUT(`A valid package.json file with pear field must exist ${condition}`)
}
return project(parent, origin, cwd)
}
module.exports = PearElectron