-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathruntime.js
More file actions
215 lines (194 loc) · 6.67 KB
/
runtime.js
File metadata and controls
215 lines (194 loc) · 6.67 KB
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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
/** @typedef {import('pear-interface')} */ /* global Pear */
'use strict'
const fs = require('bare-fs')
const os = require('bare-os')
const path = require('bare-path')
const { spawn } = require('bare-subprocess')
const env = require('bare-env')
const { command } = require('paparam')
const { isLinux, isWindows, isMac } = require('which-runtime')
const { pathToFileURL, fileURLToPath } = require('url-file-url')
const constants = require('pear-constants')
const plink = require('pear-link')
const Logger = require('pear-logger')
const { stdio } = require('pear-terminal')
const { ERR_INVALID_APPLING, ERR_INVALID_PROJECT_DIR, ERR_INVALID_CONFIG } = require('pear-errors')
// cutover stops replaying & relaying subscriber streams between clients
// set to false to stop run flow from auto cutover, so we can cutover at end of ui init
Pear.constructor.CUTOVER = false
const pear = require('pear-cmd')
const run = require('pear-cmd/run')
const pkg = require('./package.json')
const bin = (name) => {
const kebab = name.toLowerCase().split(' ').join('-')
const cased = kebab
.split('-')
.map((w) => w[0].toUpperCase() + w.slice(1))
.join(' ')
const app = isMac ? cased + '.app' : kebab + '-app'
const exe = isWindows ? cased + '.exe' : isMac ? 'Contents/MacOS/' + cased : kebab
return isWindows
? 'bin\\' + app + '\\' + exe
: isMac
? 'bin/' + app + '/' + exe
: 'bin/' + app + '/' + exe
}
class PearElectron {
constructor() {
if (!Pear.app.assets.ui?.path) {
const info = Pear.app.options.pre
? { assets: Pear.app.assets }
: { assets: Pear.app.assets, hint: 'set pre: pear-electron/pre to autoset assets.ui' }
throw new ERR_INVALID_CONFIG('pear.assets.ui must be defined for project', info)
}
if (!Pear.app.assets.ui?.name) {
throw new ERR_INVALID_CONFIG('pear.assets.ui.name must be defined for project', {
assets: Pear.app.assets
})
}
this.ipc = Pear[Pear.constructor.IPC]
this.applink = new URL(Pear.app.applink)
this.LOG = new Logger({ labels: [pkg.name] })
Pear.teardown(() => this.ipc.close())
}
async start(opts = {}) {
this.bin = path.join(
Pear.app.assets.ui.path,
'by-arch',
require.addon.host,
bin(Pear.app.assets.ui.name)
)
const parsed = pear(Pear.argv.slice(1))
const cmd = command('run', ...run)
let argv = parsed.rest
const { args, indices } = cmd.parse(argv)
let link = Pear.app.link
const { drive, pathname, hash, search } = plink.parse(link)
const { key } = drive
const isPear = link.startsWith('pear://')
const isFile = link.startsWith('file://')
const isPath = isPear === false && isFile === false
const cwd = os.cwd()
let dir = cwd
if (key === null) {
const initial = normalize(pathname)
const base = project(initial, initial)
dir = base.dir
if (dir.length > 1 && dir.endsWith('/')) dir = dir.slice(0, -1)
if (isPath) {
link = pathToFileURL(path.join(dir, base.entrypoint || '/')) + search + hash
argv[indices.args.link] = link
}
}
argv[indices.args.link] = argv[indices.args.link].replace('://', '_||') // for Windows
if ((isLinux || isWindows) && indices.flags.sandbox === undefined) {
argv.splice(indices.args.link, 0, '--no-sandbox')
}
const builtins = [
'electron',
'net',
'assert',
'console',
'events',
'fs',
'fs/promises',
'http',
'https',
'os',
'path',
'child_process',
'repl',
'url',
'tty',
'module',
'process',
'timers',
'inspector'
]
const extensions = ['.node']
const conditions = ['electron', 'node']
const prebuilds = path.join(Pear.app.assets.ui.path, 'prebuilds')
const boot = await this.ipc.bundle({
cache: true,
entry: '/node_modules/pear-electron/boot.js',
prebuilds,
builtins,
extensions,
conditions
})
const info = JSON.stringify({
checkout: constants.CHECKOUT,
mount: constants.MOUNT,
bridge: opts.bridge?.addr ?? undefined,
startId: Pear.app.startId,
dir
})
argv = [fileURLToPath(boot.file), '--rti', info, ...argv]
const options = {
stdio: args.detach
? ['ignore', 'ignore', 'ignore', 'overlapped']
: ['ignore', 'pipe', 'pipe', 'overlapped'],
cwd,
windowsHide: true,
...{ env: { ...env, NODE_PRESERVE_SYMLINKS: 1 } }
}
let sp = null
if (args.appling) {
this.LOG.info('Spawning UI (appling)')
const { appling } = args
const applingApp = isMac ? appling.split('.app')[0] + '.app' : appling
if (fs.existsSync(applingApp) === false) throw ERR_INVALID_APPLING('Appling does not exist')
if (isMac) sp = spawn('open', [applingApp, '--args', ...argv], options)
else sp = spawn(applingApp, argv, options)
} else {
this.LOG.info('Spawning UI (asset)')
sp = spawn(this.bin, argv, options)
}
sp.on('exit', (code) => {
this.LOG.info('UI exited with code', code)
Pear.exitCode = code
if (!pipe.destroyed) pipe.destroy()
})
const pipe = sp.stdio[3]
if (args.detach) return pipe
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('Unsupported pixel format: -1') > -1 ||
(str.indexOf(':ERROR:') > -1 && /:ERROR:.+cache/.test(str))
if (ignore) return
fs.writeSync(2, data)
}
sp.stderr.on('data', onerr)
sp.stdout.pipe(stdio.out)
return pipe
}
}
function project(dir, initial) {
try {
if (JSON.parse(fs.readFileSync(path.join(dir, 'package.json'))).pear) {
return { dir, entrypoint: initial.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) {
throw ERR_INVALID_PROJECT_DIR(
`A valid package.json file with pear field must exist (checked from "${initial}" to "${dir}")`
)
}
return project(parent, initial)
}
function normalize(pathname) {
if (isWindows) return path.normalize(pathname.slice(1))
return pathname
}
module.exports = PearElectron