bare-stow 0.1.5 → 0.1.6

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 CHANGED
@@ -66,6 +66,8 @@ ipc.on('exit', (code) => {
66
66
  ipc.destroy()
67
67
  ```
68
68
 
69
+ For the `bare-sidecar` target, `start()` accepts the options of `bare-sidecar`. The sidecar inherits the standard streams of the host unless `stdio` says otherwise.
70
+
69
71
  ## Protocol
70
72
 
71
73
  The harness multiplexes a control channel and a user data channel over the underlying binary duplex stream. The `ipc` returned from `start()` is itself a duplex stream carrying the user data; control frames ride alongside it on the same handle. RPC libraries (such as `bare-rpc`) bind to `ipc` just like a raw stream.
@@ -137,7 +139,7 @@ opts = {
137
139
 
138
140
  - `client`: The RPC library to wire into the harness as a client, given either as a built-in name (`'bare-rpc'`) or as an [RPC provider](#targets-and-rpc-providers) object.
139
141
  - `server`: The RPC library to wire into the bundle entry shim as a server, given either as a built-in name (`'bare-rpc'`) or as an [RPC provider](#targets-and-rpc-providers) object.
140
- - `base`: The base `URL` of the module graph. Defaults to the directory containing `entry`.
142
+ - `base`: The base `URL` of the module graph, which must contain every module in the bundle. Defaults to the closest directory that does.
141
143
  - `hosts`: An array of host triples to build for. Must be a subset of the host triples supported by the target; passing a host the target does not support throws. Defaults to all host triples supported by the target.
142
144
  - `resolveTarget`: A function mapping a target name to a [target provider](#targets-and-rpc-providers) object, called when `target` is a name not in the built-in registry (which knows `'bare-sidecar'` and `'bare-worker'`).
143
145
  - `resolveRPC`: A function mapping an RPC library name to an [RPC provider](#targets-and-rpc-providers) object, called when `client` or `server` is a name not in the built-in registry (which only knows `'bare-rpc'`).
package/bin.js CHANGED
@@ -22,7 +22,10 @@ const cmd = command(
22
22
  '--server <name>',
23
23
  'The RPC server to include (bare-rpc, or a bare-stow-rpc-<name> package)'
24
24
  ),
25
- flag('--base <path>', 'The base path of the bundle'),
25
+ flag(
26
+ '--base <path>',
27
+ 'The base path of the bundle (defaults to the closest directory containing every bundled module)'
28
+ ),
26
29
  flag('--out|-o <path>', 'The output path of the harness'),
27
30
  flag('--builtins <path>', 'A list of builtin modules'),
28
31
  flag('--imports <path>', 'A map of global import overrides'),
@@ -35,7 +38,7 @@ const cmd = command(
35
38
  target,
36
39
  client,
37
40
  server,
38
- base = '.',
41
+ base,
39
42
  out,
40
43
  builtins,
41
44
  imports,
@@ -60,9 +63,7 @@ const cmd = command(
60
63
  if ('default' in imports) imports = imports.default
61
64
  }
62
65
 
63
- base = pathToFileURL(base)
64
-
65
- if (!base.pathname.endsWith('/')) base.pathname += '/'
66
+ if (base) base = pathToFileURL(path.resolve(base) + '/')
66
67
 
67
68
  for await (const _ of stow(pathToFileURL(entry), target, pathToFileURL(out), {
68
69
  client,
package/index.js CHANGED
@@ -21,7 +21,6 @@ module.exports = async function* stow(entry, target, out, opts = {}) {
21
21
 
22
22
  entry = new URL(entry)
23
23
  out = new URL(out)
24
- base = base ? new URL(base) : new URL('./', entry)
25
24
 
26
25
  // The target may pin a module system; otherwise it follows the output path.
27
26
  const module = t.module || (await resolveModule(out))
@@ -36,7 +35,7 @@ module.exports = async function* stow(entry, target, out, opts = {}) {
36
35
  hosts = t.hosts
37
36
  }
38
37
 
39
- const shimURL = shim.url(base)
38
+ const shimURL = shim.url(new URL('./', entry))
40
39
 
41
40
  // The server only contributes runtime wiring to the shim; the shim is not a
42
41
  // typed, host-facing module so it has no declaration artifact.
@@ -63,9 +62,11 @@ module.exports = async function* stow(entry, target, out, opts = {}) {
63
62
 
64
63
  let writeFile
65
64
 
66
- if (isOffloadEnabled(t.offload)) writeFile = collectOffloaded(base, out, offloaded)
65
+ if (isOffloadEnabled(t.offload)) writeFile = collectOffloaded(offloaded)
67
66
 
68
- const bundle = await pack(
67
+ // The base of the bundle depends on where its modules live, so the graph is
68
+ // packed unbased and unmounted once that is known.
69
+ let bundle = await pack(
69
70
  shimURL,
70
71
  {
71
72
  ...packOpts,
@@ -75,7 +76,6 @@ module.exports = async function* stow(entry, target, out, opts = {}) {
75
76
  '.mts': '.mjs',
76
77
  '.cts': '.cjs'
77
78
  },
78
- base,
79
79
  hosts,
80
80
  linked: t.linked,
81
81
  offload: t.offload
@@ -85,6 +85,10 @@ module.exports = async function* stow(entry, target, out, opts = {}) {
85
85
  writeFile
86
86
  )
87
87
 
88
+ base = resolveBase(base, bundle)
89
+
90
+ bundle = rerootOffloaded(bundle.unmount(base), base, out, offloaded)
91
+
88
92
  bundle.id = id(bundle).toString('hex')
89
93
 
90
94
  // The client contributes both runtime wiring (spliced into the harness) and a
@@ -132,6 +136,44 @@ module.exports = async function* stow(entry, target, out, opts = {}) {
132
136
  }
133
137
  }
134
138
 
139
+ function resolveBase(base, graph) {
140
+ const modules = [...graph.keys()].map((href) => new URL(href))
141
+
142
+ if (base) {
143
+ base = new URL(base)
144
+
145
+ if (!base.pathname.endsWith('/')) base.pathname += '/'
146
+
147
+ for (const url of modules) {
148
+ if (url.protocol === base.protocol && !url.href.startsWith(base.href)) {
149
+ throw new Error(`Module '${url.href}' is outside base '${base.href}'`)
150
+ }
151
+ }
152
+
153
+ return base
154
+ }
155
+
156
+ let common = null
157
+
158
+ for (const url of modules) {
159
+ if (url.protocol !== 'file:') continue
160
+
161
+ const dir = new URL('./', url)
162
+
163
+ if (common === null) common = dir
164
+
165
+ while (!dir.href.startsWith(common.href)) {
166
+ const parent = new URL('../', common)
167
+
168
+ if (parent.href === common.href) throw new Error('Modules share no common base')
169
+
170
+ common = parent
171
+ }
172
+ }
173
+
174
+ return common
175
+ }
176
+
135
177
  function isOffloadEnabled(offload) {
136
178
  if (offload === true) return true
137
179
  if (offload && (offload.addons || offload.assets)) return true
@@ -153,26 +195,83 @@ function wrapReadModule(readModule, shimURL, shimSource) {
153
195
  }
154
196
  }
155
197
 
156
- function collectOffloaded(base, out, sink) {
198
+ function collectOffloaded(sink) {
199
+ return function writeFile(url, source) {
200
+ sink.push({ url, source })
201
+
202
+ return null
203
+ }
204
+ }
205
+
206
+ // Offloaded files are written next to the harness, so their resolutions are
207
+ // rewritten to point beside the bundle, along with the directories that hold
208
+ // them, as `bare-pack` does for a bundle packed with a base.
209
+ function rerootOffloaded(bundle, base, out, offloaded) {
210
+ if (offloaded.length === 0) return bundle
211
+
157
212
  const dir = new URL('./', out)
213
+ const rewrites = new Map()
158
214
 
159
- return function writeFile(url, source) {
160
- let relative
215
+ for (const file of offloaded) {
216
+ const relative = offloadedPath(file.url, base)
217
+
218
+ let key = '/' + path.posix.relative(base.pathname, file.url.pathname)
219
+ let value = '/../' + relative
220
+
221
+ rewrites.set(key, value)
222
+
223
+ for (;;) {
224
+ key = key.substring(0, key.lastIndexOf('/'))
225
+
226
+ if (isTerminator(key)) break
227
+
228
+ value = value.substring(0, value.lastIndexOf('/'))
161
229
 
162
- const nm = url.pathname.indexOf('/node_modules/')
230
+ if (isTerminator(value)) break
163
231
 
164
- if (nm >= 0) {
165
- relative = url.pathname.slice(nm + 1)
166
- } else if (url.pathname.startsWith(base.pathname)) {
167
- relative = url.pathname.slice(base.pathname.length)
168
- } else {
169
- relative = url.pathname.replace(/^\//, '')
232
+ rewrites.set(key, value)
170
233
  }
171
234
 
172
- sink.push({ url: new URL(relative, dir), source })
235
+ file.url = new URL(relative, dir)
236
+ }
173
237
 
174
- return null
238
+ const resolutions = {}
239
+
240
+ for (const [href, imports] of Object.entries(bundle.resolutions)) {
241
+ resolutions[href] = rewriteImports(imports, rewrites)
242
+ }
243
+
244
+ bundle.resolutions = resolutions
245
+
246
+ return bundle
247
+ }
248
+
249
+ function offloadedPath(url, base) {
250
+ const nm = url.pathname.indexOf('/node_modules/')
251
+
252
+ if (nm >= 0) return url.pathname.slice(nm + 1)
253
+
254
+ if (url.pathname.startsWith(base.pathname)) return url.pathname.slice(base.pathname.length)
255
+
256
+ return url.pathname.replace(/^\//, '')
257
+ }
258
+
259
+ function rewriteImports(imports, rewrites) {
260
+ if (typeof imports === 'string') return rewrites.get(imports) || imports
261
+
262
+ if (typeof imports !== 'object' || imports === null) return imports
263
+
264
+ const rewritten = {}
265
+
266
+ for (const [condition, value] of Object.entries(imports)) {
267
+ rewritten[condition] = rewriteImports(value, rewrites)
175
268
  }
269
+
270
+ return rewritten
271
+ }
272
+
273
+ function isTerminator(input) {
274
+ return input === '' || input.endsWith('/') || input.endsWith(':')
176
275
  }
177
276
 
178
277
  async function resolveModule(out) {
@@ -33,7 +33,7 @@ import { fileURLToPath } from 'url'
33
33
  const bundle = fileURLToPath(new URL(${JSON.stringify(bundleSpecifier)}, import.meta.url))
34
34
 
35
35
  export async function start(opts = {}) {
36
- const sidecar = new Sidecar(bundle, opts)
36
+ const sidecar = new Sidecar(bundle, { stdio: 'inherit', ...opts })
37
37
 
38
38
  const ${ipc} = stow.wrap(sidecar)
39
39
  ${client ? '\n' + indent(client.source, 2) : ''}
@@ -51,7 +51,7 @@ const bundle = path.join(__dirname, ${JSON.stringify(bundleSpecifier)})
51
51
 
52
52
  module.exports = {
53
53
  async start(opts = {}) {
54
- const sidecar = new Sidecar(bundle, opts)
54
+ const sidecar = new Sidecar(bundle, { stdio: 'inherit', ...opts })
55
55
 
56
56
  const ${ipc} = stow.wrap(sidecar)
57
57
  ${client ? '\n' + indent(client.source, 4) : ''}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bare-stow",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Module bundling and harness generation for Bare",
5
5
  "exports": {
6
6
  "./package": "./package.json",
@@ -76,7 +76,7 @@
76
76
  },
77
77
  "devDependencies": {
78
78
  "bare-rpc": "^1.3.2",
79
- "bare-sidecar": "^0.5.0",
79
+ "bare-sidecar": "^0.5.8",
80
80
  "bare-worker": "^4.3.0",
81
81
  "brittle": "^4.0.2",
82
82
  "lunte": "^1.7.0",