bare-stow 0.0.0 → 0.1.0
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/LICENSE +201 -0
- package/README.md +237 -0
- package/bin.js +109 -0
- package/index.d.ts +83 -0
- package/index.js +207 -0
- package/lib/fs.js +78 -0
- package/lib/host.d.ts +19 -0
- package/lib/host.js +62 -0
- package/lib/protocol.d.ts +16 -0
- package/lib/protocol.js +129 -0
- package/lib/rpc/bare-rpc.js +24 -0
- package/lib/rpc.js +23 -0
- package/lib/shim.js +86 -0
- package/lib/target/bare-sidecar.js +63 -0
- package/lib/target.js +27 -0
- package/package.json +91 -1
package/index.js
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
const { fileURLToPath } = require('url')
|
|
3
|
+
const pack = require('bare-pack')
|
|
4
|
+
const { resolve } = require('bare-module-traverse')
|
|
5
|
+
const id = require('bare-bundle-id')
|
|
6
|
+
const fs = require('./lib/fs')
|
|
7
|
+
const shim = require('./lib/shim')
|
|
8
|
+
const harness = require('./lib/target')
|
|
9
|
+
const rpc = require('./lib/rpc')
|
|
10
|
+
|
|
11
|
+
module.exports = async function* stow(entry, target, out, opts = {}) {
|
|
12
|
+
if (!target) throw new Error("'target' is required")
|
|
13
|
+
if (!out) throw new Error("'out' is required")
|
|
14
|
+
|
|
15
|
+
let { client, server, base, hosts, resolveTarget, resolveRPC, ...packOpts } = opts
|
|
16
|
+
|
|
17
|
+
const t = harness(target, resolveTarget)
|
|
18
|
+
|
|
19
|
+
const targetName = typeof target === 'string' ? target : t.name
|
|
20
|
+
|
|
21
|
+
entry = new URL(entry)
|
|
22
|
+
out = new URL(out)
|
|
23
|
+
base = base ? new URL(base) : new URL('./', entry)
|
|
24
|
+
|
|
25
|
+
if (hosts) {
|
|
26
|
+
for (const host of hosts) {
|
|
27
|
+
if (!t.hosts.includes(host)) {
|
|
28
|
+
throw new Error(`Host '${host}' is not supported by target '${targetName}'`)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
} else {
|
|
32
|
+
hosts = t.hosts
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const shimURL = shim.url(base)
|
|
36
|
+
|
|
37
|
+
// The server only contributes runtime wiring to the shim; the shim is not a
|
|
38
|
+
// typed, host-facing module so it has no declaration artifact.
|
|
39
|
+
const serverSetup = server
|
|
40
|
+
? fragment(
|
|
41
|
+
rpc(server, resolveRPC).generate({
|
|
42
|
+
ipc: 'ipc',
|
|
43
|
+
rpc: 'rpc',
|
|
44
|
+
module: 'esm',
|
|
45
|
+
role: 'server'
|
|
46
|
+
}),
|
|
47
|
+
null
|
|
48
|
+
)
|
|
49
|
+
: null
|
|
50
|
+
|
|
51
|
+
const shimSource = shim(entry, shimURL, { server: serverSetup })
|
|
52
|
+
|
|
53
|
+
const readModule = wrapReadModule(fs.readModule, shimURL, shimSource)
|
|
54
|
+
|
|
55
|
+
const bundleURL = siblingURL(out, t.extension)
|
|
56
|
+
const bundleSpecifier = relativeSpecifier(out, bundleURL)
|
|
57
|
+
|
|
58
|
+
const offloaded = []
|
|
59
|
+
|
|
60
|
+
let writeFile
|
|
61
|
+
|
|
62
|
+
if (isOffloadEnabled(t.offload)) writeFile = collectOffloaded(base, out, offloaded)
|
|
63
|
+
|
|
64
|
+
const bundle = await pack(
|
|
65
|
+
shimURL,
|
|
66
|
+
{
|
|
67
|
+
resolve: resolve.bare,
|
|
68
|
+
...packOpts,
|
|
69
|
+
hosts,
|
|
70
|
+
linked: t.linked,
|
|
71
|
+
offload: t.offload,
|
|
72
|
+
base: base
|
|
73
|
+
},
|
|
74
|
+
readModule,
|
|
75
|
+
fs.listPrefix,
|
|
76
|
+
writeFile
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
bundle.id = id(bundle).toString('hex')
|
|
80
|
+
|
|
81
|
+
// The client contributes both runtime wiring (spliced into the harness) and a
|
|
82
|
+
// type expression (spliced into the harness declaration), so it is resolved
|
|
83
|
+
// into both channels before handing it to the target.
|
|
84
|
+
let clientSetup = null
|
|
85
|
+
|
|
86
|
+
if (client) {
|
|
87
|
+
const artifacts = rpc(client, resolveRPC).generate({
|
|
88
|
+
ipc: 'ipc',
|
|
89
|
+
rpc: 'rpc',
|
|
90
|
+
module: t.module,
|
|
91
|
+
role: 'client'
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
clientSetup = {
|
|
95
|
+
source: fragment(artifacts, null),
|
|
96
|
+
type: fragment(artifacts, '.d.ts')
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const artifacts = t.generate({
|
|
101
|
+
bundleSpecifier,
|
|
102
|
+
ipc: 'ipc',
|
|
103
|
+
rpc: 'rpc',
|
|
104
|
+
client: clientSetup
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
// The first artifact is the harness written to `out`; any further artifacts
|
|
108
|
+
// are siblings named after `out` with the artifact's own extension.
|
|
109
|
+
for (const artifact of artifacts) {
|
|
110
|
+
const url = artifact.extension ? siblingURL(out, artifact.extension) : out
|
|
111
|
+
|
|
112
|
+
await fs.writeFile(url, artifact.source)
|
|
113
|
+
yield { url }
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
await fs.writeFile(bundleURL, encodeBundle(bundle, t.format, t.encoding ?? 'utf8'))
|
|
117
|
+
yield { url: bundleURL }
|
|
118
|
+
|
|
119
|
+
for (const artifact of offloaded) {
|
|
120
|
+
await fs.writeFile(artifact.url, artifact.source)
|
|
121
|
+
yield { url: artifact.url }
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function isOffloadEnabled(offload) {
|
|
126
|
+
if (offload === true) return true
|
|
127
|
+
if (offload && (offload.addons || offload.assets)) return true
|
|
128
|
+
|
|
129
|
+
return false
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function wrapReadModule(readModule, shimURL, shimSource) {
|
|
133
|
+
return function (url) {
|
|
134
|
+
if (url.href === shimURL.href) return shimSource
|
|
135
|
+
|
|
136
|
+
return readModule(url)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function collectOffloaded(base, out, sink) {
|
|
141
|
+
const dir = new URL('./', out)
|
|
142
|
+
|
|
143
|
+
return function writeFile(url, source) {
|
|
144
|
+
let relative
|
|
145
|
+
|
|
146
|
+
const nm = url.pathname.indexOf('/node_modules/')
|
|
147
|
+
|
|
148
|
+
if (nm >= 0) {
|
|
149
|
+
relative = url.pathname.slice(nm + 1)
|
|
150
|
+
} else if (url.pathname.startsWith(base.pathname)) {
|
|
151
|
+
relative = url.pathname.slice(base.pathname.length)
|
|
152
|
+
} else {
|
|
153
|
+
relative = url.pathname.replace(/^\//, '')
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
sink.push({ url: new URL(relative, dir), source })
|
|
157
|
+
|
|
158
|
+
return null
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function siblingURL(out, extension) {
|
|
163
|
+
const dir = new URL('./', out)
|
|
164
|
+
const base = path.basename(out.pathname, path.extname(out.pathname))
|
|
165
|
+
|
|
166
|
+
return new URL(base + extension, dir)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function fragment(artifacts, extension) {
|
|
170
|
+
for (const artifact of artifacts) {
|
|
171
|
+
if ((artifact.extension ?? null) === extension) return artifact.source
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return null
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function relativeSpecifier(from, to) {
|
|
178
|
+
if (from.protocol === 'file:' && to.protocol === 'file:') {
|
|
179
|
+
const fromDir = path.dirname(fileURLToPath(from))
|
|
180
|
+
const toPath = fileURLToPath(to)
|
|
181
|
+
|
|
182
|
+
let relative = path.relative(fromDir, toPath).split(path.sep).join('/')
|
|
183
|
+
|
|
184
|
+
if (!relative.startsWith('.')) relative = './' + relative
|
|
185
|
+
|
|
186
|
+
return relative
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return to.href
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function encodeBundle(bundle, format, encoding) {
|
|
193
|
+
const data = bundle.toBuffer()
|
|
194
|
+
|
|
195
|
+
switch (format) {
|
|
196
|
+
case 'bundle':
|
|
197
|
+
return data
|
|
198
|
+
case 'bundle.cjs':
|
|
199
|
+
return `module.exports = ${JSON.stringify(data.toString(encoding))}\n`
|
|
200
|
+
case 'bundle.mjs':
|
|
201
|
+
return `export default ${JSON.stringify(data.toString(encoding))}\n`
|
|
202
|
+
case 'bundle.json':
|
|
203
|
+
return JSON.stringify(data.toString(encoding)) + '\n'
|
|
204
|
+
default:
|
|
205
|
+
throw new Error(`Unknown format '${format}'`)
|
|
206
|
+
}
|
|
207
|
+
}
|
package/lib/fs.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
const { fileURLToPath } = require('url')
|
|
2
|
+
const fs = require('fs')
|
|
3
|
+
|
|
4
|
+
exports.writeFile = async function writeFile(url, data) {
|
|
5
|
+
await exports.makeDir(new URL('.', url))
|
|
6
|
+
|
|
7
|
+
return new Promise((resolve, reject) => {
|
|
8
|
+
fs.writeFile(fileURLToPath(url), data, (err) => {
|
|
9
|
+
err ? reject(err) : resolve()
|
|
10
|
+
})
|
|
11
|
+
})
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
exports.readFile = function readFile(url) {
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
fs.readFile(fileURLToPath(url), (err, data) => {
|
|
17
|
+
err ? reject(err) : resolve(data)
|
|
18
|
+
})
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
exports.makeDir = function makeDir(url) {
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
fs.mkdir(fileURLToPath(url), { recursive: true }, (err) => {
|
|
25
|
+
err ? reject(err) : resolve()
|
|
26
|
+
})
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
exports.openDir = function openDir(url) {
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
fs.opendir(fileURLToPath(url), (err, dir) => {
|
|
33
|
+
err ? reject(err) : resolve(dir)
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
exports.readModule = async function readModule(url) {
|
|
39
|
+
try {
|
|
40
|
+
return await exports.readFile(url)
|
|
41
|
+
} catch {
|
|
42
|
+
return null
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function isFile(url) {
|
|
47
|
+
return new Promise((resolve) => {
|
|
48
|
+
fs.stat(fileURLToPath(url), (err, stat) => {
|
|
49
|
+
resolve(err === null && stat.isFile())
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function isDir(url) {
|
|
55
|
+
return new Promise((resolve) => {
|
|
56
|
+
fs.stat(fileURLToPath(url), (err, stat) => {
|
|
57
|
+
resolve(err === null && stat.isDirectory())
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
exports.listPrefix = async function* listPrefix(url) {
|
|
63
|
+
if (await isFile(url)) return yield url
|
|
64
|
+
|
|
65
|
+
if (url.pathname[url.pathname.length - 1] !== '/') {
|
|
66
|
+
url.pathname += '/'
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (await isDir(url)) {
|
|
70
|
+
for await (const entry of await exports.openDir(url)) {
|
|
71
|
+
if (entry.isDirectory()) {
|
|
72
|
+
yield* listPrefix(new URL(entry.name, url))
|
|
73
|
+
} else {
|
|
74
|
+
yield new URL(entry.name, url)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
package/lib/host.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Duplex } from 'bare-stream'
|
|
2
|
+
import { Protocol } from './protocol'
|
|
3
|
+
|
|
4
|
+
interface IPC extends Protocol {
|
|
5
|
+
readonly ready: Promise<void>
|
|
6
|
+
terminate(): Promise<number | undefined>
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
declare class IPC {
|
|
10
|
+
constructor(stream: Duplex)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
declare function wrap(stream: Duplex): IPC
|
|
14
|
+
|
|
15
|
+
declare namespace wrap {
|
|
16
|
+
export { IPC }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export = wrap
|
package/lib/host.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const { Protocol } = require('./protocol')
|
|
2
|
+
|
|
3
|
+
class IPC extends Protocol {
|
|
4
|
+
constructor(stream) {
|
|
5
|
+
super(stream)
|
|
6
|
+
|
|
7
|
+
this._ready = new Promise((resolve, reject) => {
|
|
8
|
+
const onready = () => {
|
|
9
|
+
this.off('ready', onready).off('error', onerror)
|
|
10
|
+
resolve()
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const onerror = (err) => {
|
|
14
|
+
this.off('ready', onready).off('error', onerror)
|
|
15
|
+
reject(err)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
this.on('ready', onready).on('error', onerror)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
this._ready.catch(() => {})
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get ready() {
|
|
25
|
+
return this._ready
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
terminate() {
|
|
29
|
+
return new Promise((resolve) => {
|
|
30
|
+
const cleanup = () => {
|
|
31
|
+
this.off('exit', onexit)
|
|
32
|
+
this._stream.off('close', onclose)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const onexit = (code) => {
|
|
36
|
+
cleanup()
|
|
37
|
+
resolve(code)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const onclose = () => {
|
|
41
|
+
cleanup()
|
|
42
|
+
resolve(undefined)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
this.once('exit', onexit).send('terminate')
|
|
46
|
+
this._stream.once('close', onclose)
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
_destroy(err, cb) {
|
|
51
|
+
if (this._stream.destroyed) return cb(null)
|
|
52
|
+
|
|
53
|
+
this.send('terminate')
|
|
54
|
+
this._stream.once('close', () => cb(null))
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
exports.IPC = IPC
|
|
59
|
+
|
|
60
|
+
exports.wrap = function wrap(stream) {
|
|
61
|
+
return new IPC(stream)
|
|
62
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Duplex } from 'bare-stream'
|
|
2
|
+
|
|
3
|
+
declare const CONTROL: number
|
|
4
|
+
declare const USER: number
|
|
5
|
+
|
|
6
|
+
interface Protocol extends Duplex {
|
|
7
|
+
send(type: string, payload?: object): Promise<void>
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
declare class Protocol {
|
|
11
|
+
constructor(stream: Duplex)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
declare function attach(stream: Duplex): Protocol
|
|
15
|
+
|
|
16
|
+
export { type Protocol, attach, CONTROL, USER }
|
package/lib/protocol.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
const { Duplex, Writable } = require('bare-stream')
|
|
2
|
+
|
|
3
|
+
const CONTROL = 0x00
|
|
4
|
+
const USER = 0x01
|
|
5
|
+
|
|
6
|
+
exports.CONTROL = CONTROL
|
|
7
|
+
exports.USER = USER
|
|
8
|
+
|
|
9
|
+
const HEADER_LENGTH = 5
|
|
10
|
+
|
|
11
|
+
class Protocol extends Duplex {
|
|
12
|
+
constructor(stream) {
|
|
13
|
+
super()
|
|
14
|
+
|
|
15
|
+
this._stream = stream
|
|
16
|
+
this._buffer = []
|
|
17
|
+
this._buffered = 0
|
|
18
|
+
this._frame = -1
|
|
19
|
+
|
|
20
|
+
stream
|
|
21
|
+
.on('data', (chunk) => this._ondata(chunk))
|
|
22
|
+
.on('end', () => this.push(null))
|
|
23
|
+
.on('close', () => this.destroy())
|
|
24
|
+
.on('error', (err) => this.destroy(err))
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
send(type, payload) {
|
|
28
|
+
const frame = encodeFrame(CONTROL, Buffer.from(JSON.stringify({ type, ...payload })))
|
|
29
|
+
|
|
30
|
+
this._stream.write(frame)
|
|
31
|
+
|
|
32
|
+
return Writable.drained(this._stream)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
_write(data, encoding, cb) {
|
|
36
|
+
const frame = encodeFrame(USER, data)
|
|
37
|
+
|
|
38
|
+
const ok = this._stream.write(frame)
|
|
39
|
+
|
|
40
|
+
if (ok) return cb(null)
|
|
41
|
+
|
|
42
|
+
this._stream.once('drain', () => cb(null))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
_destroy(err, cb) {
|
|
46
|
+
this._stream.destroy()
|
|
47
|
+
cb(null)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
_ondata(chunk) {
|
|
51
|
+
this._buffer.push(chunk)
|
|
52
|
+
this._buffered += chunk.byteLength
|
|
53
|
+
|
|
54
|
+
if (this._frame === -1) this._onbeforeframe()
|
|
55
|
+
else this._onafterframe()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
_onbeforeframe() {
|
|
59
|
+
if (this._buffered < 4) return
|
|
60
|
+
|
|
61
|
+
const buffer = this._buffer.length === 1 ? this._buffer[0] : Buffer.concat(this._buffer)
|
|
62
|
+
|
|
63
|
+
this._buffer = [buffer]
|
|
64
|
+
this._frame = 4 + buffer.readUInt32LE(0)
|
|
65
|
+
|
|
66
|
+
this._onafterframe()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
_onafterframe() {
|
|
70
|
+
if (this._buffered < this._frame) return
|
|
71
|
+
|
|
72
|
+
const buffer = this._buffer.length === 1 ? this._buffer[0] : Buffer.concat(this._buffer)
|
|
73
|
+
const frame = this._frame
|
|
74
|
+
|
|
75
|
+
this._buffered -= frame
|
|
76
|
+
this._buffer = this._buffered > 0 ? [buffer.subarray(frame)] : []
|
|
77
|
+
this._frame = -1
|
|
78
|
+
|
|
79
|
+
this._onframe(buffer.subarray(0, frame))
|
|
80
|
+
this._onbeforeframe()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
_onframe(frame) {
|
|
84
|
+
const tag = frame.readUInt8(4)
|
|
85
|
+
const payload = frame.subarray(HEADER_LENGTH)
|
|
86
|
+
|
|
87
|
+
if (tag === USER) {
|
|
88
|
+
this.push(Buffer.from(payload))
|
|
89
|
+
} else if (tag === CONTROL) {
|
|
90
|
+
let message
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
message = JSON.parse(payload.toString())
|
|
94
|
+
} catch {
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (message.type === 'error') {
|
|
99
|
+
const err = new Error(message.message)
|
|
100
|
+
|
|
101
|
+
if (message.stack) err.stack = message.stack
|
|
102
|
+
|
|
103
|
+
this.destroy(err)
|
|
104
|
+
} else if (message.type === 'exit') {
|
|
105
|
+
this.emit('exit', message.code)
|
|
106
|
+
} else {
|
|
107
|
+
this.emit(message.type, message)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
exports.Protocol = Protocol
|
|
114
|
+
|
|
115
|
+
exports.attach = function attach(stream) {
|
|
116
|
+
return new Protocol(stream)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function encodeFrame(tag, data) {
|
|
120
|
+
const length = data.length + 1
|
|
121
|
+
const frame = Buffer.alloc(HEADER_LENGTH + data.length)
|
|
122
|
+
|
|
123
|
+
frame.writeUInt32LE(length, 0)
|
|
124
|
+
frame.writeUInt8(tag, 4)
|
|
125
|
+
|
|
126
|
+
data.copy(frame, HEADER_LENGTH)
|
|
127
|
+
|
|
128
|
+
return frame
|
|
129
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
exports.name = 'bare-rpc'
|
|
2
|
+
|
|
3
|
+
exports.generate = function generate(opts) {
|
|
4
|
+
const { ipc, rpc } = opts
|
|
5
|
+
|
|
6
|
+
const esm = opts.module === 'esm'
|
|
7
|
+
|
|
8
|
+
return [
|
|
9
|
+
{
|
|
10
|
+
source: `\
|
|
11
|
+
${esm ? "const { default: RPC } = await import('bare-rpc')" : "const RPC = require('bare-rpc')"}
|
|
12
|
+
|
|
13
|
+
const router = new RPC.CommandRouter()
|
|
14
|
+
const ${rpc} = new RPC(${ipc}, router)
|
|
15
|
+
|
|
16
|
+
${rpc}.respond = router.respond.bind(router)
|
|
17
|
+
`
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
extension: '.d.ts',
|
|
21
|
+
source: "import('bare-rpc')"
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
package/lib/rpc.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const registry = {
|
|
2
|
+
'bare-rpc': () => require('./rpc/bare-rpc')
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
module.exports = function rpc(name, resolve) {
|
|
6
|
+
if (typeof name !== 'string') return validate(name)
|
|
7
|
+
|
|
8
|
+
const load = registry[name]
|
|
9
|
+
|
|
10
|
+
if (load) return load()
|
|
11
|
+
|
|
12
|
+
if (resolve) return validate(resolve(name))
|
|
13
|
+
|
|
14
|
+
throw new Error(`Unknown RPC library '${name}'`)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function validate(rpc) {
|
|
18
|
+
if (!rpc || typeof rpc.generate !== 'function') {
|
|
19
|
+
throw new Error('RPC library must provide a generate() function')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return rpc
|
|
23
|
+
}
|
package/lib/shim.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
const { fileURLToPath } = require('url')
|
|
3
|
+
|
|
4
|
+
module.exports = exports = function shim(entry, shimURL, opts = {}) {
|
|
5
|
+
// `server` is a pre-rendered RPC server setup fragment (binding `rpc`), or
|
|
6
|
+
// null. The bundler resolves the RPC library and renders it; the shim only
|
|
7
|
+
// decides where the fragment goes.
|
|
8
|
+
const { server = null } = opts
|
|
9
|
+
|
|
10
|
+
const specifier = relativeSpecifier(shimURL, entry)
|
|
11
|
+
|
|
12
|
+
const setup = server
|
|
13
|
+
|
|
14
|
+
const arg = setup ? '{ rpc, ipc }' : 'ipc'
|
|
15
|
+
|
|
16
|
+
return `\
|
|
17
|
+
import protocol from 'bare-stow/protocol'
|
|
18
|
+
|
|
19
|
+
const ipc = protocol.attach(Bare.IPC)
|
|
20
|
+
${setup ? '\n' + setup : ''}
|
|
21
|
+
let stop = null
|
|
22
|
+
let readied = false
|
|
23
|
+
let exited = false
|
|
24
|
+
|
|
25
|
+
function ready() {
|
|
26
|
+
if (readied) return
|
|
27
|
+
readied = true
|
|
28
|
+
|
|
29
|
+
ipc.send('ready')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function shutdown(code, err = null) {
|
|
33
|
+
if (exited) return
|
|
34
|
+
exited = true
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
if (stop) await stop()
|
|
38
|
+
} catch (e) {
|
|
39
|
+
err = err || e
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (err) ipc.send('error', { message: err.message, stack: err.stack })
|
|
43
|
+
|
|
44
|
+
await ipc.send('exit', { code })
|
|
45
|
+
|
|
46
|
+
Bare.exit(code)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
ipc.on('terminate', () => shutdown(0))
|
|
50
|
+
|
|
51
|
+
Bare
|
|
52
|
+
.on('beforeExit', (exitCode) => shutdown(exitCode))
|
|
53
|
+
.on('uncaughtException', (err) => shutdown(1, err))
|
|
54
|
+
.on('unhandledRejection', (err) => shutdown(1, err))
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const entry = await import(${JSON.stringify(specifier)})
|
|
58
|
+
const start = entry.default
|
|
59
|
+
|
|
60
|
+
stop = await start(${arg}, ready) || null
|
|
61
|
+
|
|
62
|
+
ready()
|
|
63
|
+
} catch (err) {
|
|
64
|
+
shutdown(1, err)
|
|
65
|
+
}
|
|
66
|
+
`
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
exports.url = function url(base) {
|
|
70
|
+
return new URL('__main__.mjs', base)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function relativeSpecifier(from, to) {
|
|
74
|
+
if (from.protocol === 'file:' && to.protocol === 'file:') {
|
|
75
|
+
const fromPath = path.dirname(fileURLToPath(from))
|
|
76
|
+
const toPath = fileURLToPath(to)
|
|
77
|
+
|
|
78
|
+
let relative = path.relative(fromPath, toPath).split(path.sep).join('/')
|
|
79
|
+
|
|
80
|
+
if (!relative.startsWith('.')) relative = './' + relative
|
|
81
|
+
|
|
82
|
+
return relative
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return to.href
|
|
86
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
exports.name = 'bare-sidecar'
|
|
2
|
+
exports.linked = false
|
|
3
|
+
exports.offload = true
|
|
4
|
+
exports.format = 'bundle'
|
|
5
|
+
exports.encoding = null
|
|
6
|
+
exports.extension = '.bundle'
|
|
7
|
+
exports.module = 'cjs'
|
|
8
|
+
exports.hosts = [
|
|
9
|
+
'darwin-arm64',
|
|
10
|
+
'darwin-x64',
|
|
11
|
+
'linux-arm64',
|
|
12
|
+
'linux-x64',
|
|
13
|
+
'win32-arm64',
|
|
14
|
+
'win32-x64'
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
exports.generate = function generate(opts) {
|
|
18
|
+
const { bundleSpecifier, ipc, rpc, client = null } = opts
|
|
19
|
+
|
|
20
|
+
const returned = client ? `{ ${ipc}, ${rpc} }` : `{ ${ipc} }`
|
|
21
|
+
|
|
22
|
+
const handle = client
|
|
23
|
+
? `{ ${ipc}: import('bare-stow/host').IPC; ${rpc}: ${client.type} }`
|
|
24
|
+
: `{ ${ipc}: import('bare-stow/host').IPC }`
|
|
25
|
+
|
|
26
|
+
return [
|
|
27
|
+
{
|
|
28
|
+
source: `\
|
|
29
|
+
const path = require('path')
|
|
30
|
+
const Sidecar = require('bare-sidecar')
|
|
31
|
+
const stow = require('bare-stow/host')
|
|
32
|
+
|
|
33
|
+
const bundle = path.join(__dirname, ${JSON.stringify(bundleSpecifier)})
|
|
34
|
+
|
|
35
|
+
module.exports = {
|
|
36
|
+
async start(opts = {}) {
|
|
37
|
+
const sidecar = new Sidecar(bundle, opts)
|
|
38
|
+
|
|
39
|
+
const ${ipc} = stow.wrap(sidecar)
|
|
40
|
+
${client ? '\n' + indent(client.source, 4) : ''}
|
|
41
|
+
await ${ipc}.ready
|
|
42
|
+
|
|
43
|
+
return ${returned}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
`
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
extension: '.d.ts',
|
|
50
|
+
source: `\
|
|
51
|
+
export function start(opts?: import('bare-sidecar').SidecarOptions): Promise<${handle}>
|
|
52
|
+
`
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function indent(source, spaces) {
|
|
58
|
+
const pad = ' '.repeat(spaces)
|
|
59
|
+
return source
|
|
60
|
+
.split('\n')
|
|
61
|
+
.map((line) => (line.length === 0 ? line : pad + line))
|
|
62
|
+
.join('\n')
|
|
63
|
+
}
|