bare-stow 0.2.0 → 0.3.1

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
@@ -69,11 +69,29 @@ The bundle imports `bare-stow-runtime`, and the RPC library if a `server` is req
69
69
 
70
70
  `start()` accepts `timeout`, how long to wait in milliseconds for the bundle to signal ready, and `grace`, how long a terminating bundle gets to run `stop()` and exit before it's killed. `timeout` defaults to waiting indefinitely and `grace` to 5 seconds. On Windows, a sidecar is killed along with its host, so `stop()` only runs when the host terminates it.
71
71
 
72
+ For `bare-sidecar` and `bare-worker`, the harness also exports `connect()`, which takes a sidecar or worker that the host started itself, such as one running a development entry with arguments, and resolves like `start()`. It accepts the same options, except those of the sidecar or worker. As the entry doesn't go through `stow()`, it attaches to the IPC itself, as described in <https://github.com/holepunchto/bare-stow-runtime>:
73
+
74
+ ```js
75
+ import runtime from 'bare-stow-runtime/process'
76
+
77
+ const ipc = runtime.attach(Bare.IPC)
78
+
79
+ ipc.run(() => import('./core.js'), ipc)
80
+ ```
81
+
82
+ ```js
83
+ const sidecar = new Sidecar('./dev.js', ['--verbose'], { stdio: 'inherit' })
84
+
85
+ const { ipc } = await harness.connect(sidecar)
86
+ ```
87
+
88
+ `stow()` and `link()` read the module graph from disk by default. To read it from elsewhere, such as modules that aren't written to disk, pass `readModule`, `listPrefix`, and `resolveModule`. Generated files are still written to disk. A `readModule` that changes the type of a module also passes `aliases`, as for <https://github.com/holepunchto/bare-pack>, such as `{ '.mjs': '.js' }` to have ES modules converted to CommonJS loaded as such. They're merged over the aliases that `bare-stow` uses for TypeScript.
89
+
72
90
  ## Targets
73
91
 
74
92
  - `bare-sidecar` runs the bundle in a separate process using <https://github.com/holepunchto/bare-sidecar>, with the `javascript`, `python`, and `swift` languages on desktop. In JavaScript and Python, `start()` also accepts the options of `bare-sidecar`.
75
93
  - `bare-worker` runs the bundle in a thread of a Bare host using <https://github.com/holepunchto/bare-worker>, with the `javascript` language.
76
- - `bare-kit` runs the bundle in a worklet using <https://github.com/holepunchto/bare-kit>, with the `swift` language on macOS and iOS and the `kotlin` language on Android. `start()` also accepts the options of the worklet and returns it alongside `ipc`, and in Kotlin takes the Android `Context` first. Native addons are linked with <https://github.com/holepunchto/bare-link> and declared in the manifest, and assets are written next to the bundle. With the target option `linking` set to `'app'`, both are left to `link()` instead.
94
+ - `bare-kit` runs the bundle in a worklet using <https://github.com/holepunchto/bare-kit>, with the `swift` language on macOS and iOS and the `kotlin` language on Android and the JVM. `start()` also accepts the options of the worklet and returns it alongside `ipc`, and on Android takes the `Context` first. In Kotlin, the worklet is closed once it exits, so it needs no closing of its own, and in Swift it's released with its last reference. Native addons are linked with <https://github.com/holepunchto/bare-link> and declared in the manifest, and assets are written next to the bundle. With the target option `linking` set to `'app'`, both are left to `link()` instead. On the JVM, addons and assets are written next to the bundle as files, which `start()` extracts along with the bundle to a temporary directory, so `linking` must be `'package'`.
77
95
 
78
96
  ## Linking
79
97
 
@@ -101,7 +119,7 @@ The language lays out the package and the target adds what loads the addons. For
101
119
  The harness is generated in one of these languages, selected with `language` and configured with `languageOptions`:
102
120
 
103
121
  - `javascript` writes an npm package. The `module` option is either `'esm'`, the default, or `'cjs'`.
104
- - `kotlin` writes an Android library module. The `package` option names the package of the generated code and is required. The dependencies are published to JitPack (<https://jitpack.io>), so add it to the repositories of the application.
122
+ - `kotlin` writes a Gradle module. The `package` option names the package of the generated code and is required. The `platform` option is either `'android'`, the default, for an Android library module, or `'jvm'` for a Kotlin/JVM library module on Linux, macOS, and Windows. The dependencies are published to JitPack (<https://jitpack.io>), so add it to the repositories of the application.
105
123
  - `python` writes a Python package.
106
124
  - `swift` writes a Swift package.
107
125
 
package/index.d.ts CHANGED
@@ -31,6 +31,11 @@ interface Layout {
31
31
  addons?: URL
32
32
  /** The directory the assets of linked bundles are linked into, or unset if unsupported. */
33
33
  resources?: URL
34
+ /**
35
+ * Whether the files written next to the bundle are listed in `<bundle>.files`, one `/<path>` per
36
+ * line, for a harness that extracts them along with the bundle.
37
+ */
38
+ listing?: boolean
34
39
  }
35
40
 
36
41
  /** The context passed to a language's `manifest()`. */
@@ -68,6 +73,8 @@ interface JavaScriptOptions {
68
73
  interface KotlinOptions {
69
74
  /** The package of the generated code, such as `'com.example.core'`, which is also its namespace. */
70
75
  package: string
76
+ /** The platform of the module, either `'android'`, the default, or `'jvm'`. */
77
+ platform: 'android' | 'jvm'
71
78
  }
72
79
 
73
80
  type LanguageName = 'javascript' | 'kotlin' | 'python' | 'swift'
@@ -269,7 +276,7 @@ interface RPC {
269
276
  }
270
277
 
271
278
  /** Options for `stow()`. */
272
- interface StowOptions extends Omit<PackOptions, 'offload' | 'linked' | 'resolve' | 'aliases'> {
279
+ interface StowOptions extends Omit<PackOptions, 'offload' | 'linked' | 'resolve'> {
273
280
  /** The language of the harness (or its name). Defaults to `javascript`. */
274
281
  language?: Language | LanguageName
275
282
  /** The options given to the language, such as `{ module: 'cjs' }` for `javascript`. */
@@ -295,6 +302,15 @@ interface StowOptions extends Omit<PackOptions, 'offload' | 'linked' | 'resolve'
295
302
  * @param name - The language name to resolve to a `Language`.
296
303
  */
297
304
  resolveLanguage?(name: string): Language
305
+ /**
306
+ * Read the source of the module at `url`, or `null` if there is none, such as to stow modules
307
+ * that aren't written to disk. Defaults to reading the file.
308
+ */
309
+ readModule?: (url: URL) => Promise<Uint8Array | string | null>
310
+ /** List the files under the prefix `url`. Defaults to listing the directory. */
311
+ listPrefix?: (url: URL) => AsyncIterable<URL>
312
+ /** Resolve the module at `url`. Defaults to its real path, following symbolic links. */
313
+ resolveModule?: (url: URL) => Promise<URL>
298
314
  }
299
315
 
300
316
  /** Options for `stow.link()`, which shape the graphs as for `stow()`. */
@@ -325,7 +341,7 @@ interface StowArtifact {
325
341
  * @returns An async generator that yields each written artifact's `url` as it is produced: any
326
342
  * addons linked into the package, the harness and its type declaration, the RPC client and its type
327
343
  * declaration, the package manifest, the bundle along with its `base`, and then any assets of a
328
- * linked bundle and any offloaded addon or asset files.
344
+ * linked bundle, any offloaded addon or asset files, and the listing of those files.
329
345
  * @throws The `target` argument is missing.
330
346
  * @throws The `out` argument is missing.
331
347
  * @throws A language option is unknown or invalid, or `out` is not valid as a package.
package/index.js CHANGED
@@ -23,10 +23,13 @@ module.exports = exports = async function* stow(entry, target, out, opts = {}) {
23
23
  resolveTarget,
24
24
  resolveRPC,
25
25
  resolveLanguage,
26
+ readModule = fs.readModule,
27
+ listPrefix = fs.listPrefix,
28
+ resolveModule = fs.resolveModule,
26
29
  ...packOpts
27
30
  } = opts
28
31
 
29
- entry = await fs.resolveModule(new URL(entry))
32
+ entry = await resolveModule(new URL(entry))
30
33
  out = new URL(out)
31
34
 
32
35
  if (!out.pathname.endsWith('/')) out.pathname += '/'
@@ -81,7 +84,11 @@ module.exports = exports = async function* stow(entry, target, out, opts = {}) {
81
84
 
82
85
  const main = shim(entry, target.isolation, server)
83
86
 
84
- const readModule = graph.readModule(main.modules)
87
+ const hooks = {
88
+ readModule: graph.readModule(main.modules, readModule),
89
+ listPrefix,
90
+ resolveModule
91
+ }
85
92
 
86
93
  const offloaded = []
87
94
 
@@ -96,9 +103,9 @@ module.exports = exports = async function* stow(entry, target, out, opts = {}) {
96
103
  let bundle = await pack(
97
104
  main.url,
98
105
  { ...traversal, linked: target.linked, offload: target.offload },
99
- readModule,
100
- fs.listPrefix,
101
- fs.resolveModule,
106
+ hooks.readModule,
107
+ hooks.listPrefix,
108
+ hooks.resolveModule,
102
109
  writeFile
103
110
  )
104
111
 
@@ -109,7 +116,7 @@ module.exports = exports = async function* stow(entry, target, out, opts = {}) {
109
116
  let unpacked = []
110
117
 
111
118
  if (target.linked) {
112
- const result = await assets.unpack(bundle, base, name)
119
+ const result = await assets.unpack(bundle, base, name, hooks.readModule)
113
120
 
114
121
  bundle = result.bundle
115
122
 
@@ -126,6 +133,8 @@ module.exports = exports = async function* stow(entry, target, out, opts = {}) {
126
133
  artifacts.push(new URL(name + '/', bundleURL), new URL(name + '.assets', bundleURL))
127
134
  }
128
135
 
136
+ if (layout.listing) artifacts.push(listingURL(bundleURL))
137
+
129
138
  const addons = []
130
139
 
131
140
  // Targets that leave linking to the application use `link()` instead.
@@ -134,7 +143,7 @@ module.exports = exports = async function* stow(entry, target, out, opts = {}) {
134
143
  throw new Error(`Language '${language.name}' does not support linked addons`)
135
144
  }
136
145
 
137
- for await (const url of graph.link([main.url], layout.addons, traversal, readModule)) {
146
+ for await (const url of graph.link([main.url], layout.addons, traversal, hooks)) {
138
147
  addons.push(url)
139
148
  yield { url }
140
149
  }
@@ -179,6 +188,25 @@ module.exports = exports = async function* stow(entry, target, out, opts = {}) {
179
188
  await fs.writeFile(artifact.url, artifact.source)
180
189
  yield { url: artifact.url }
181
190
  }
191
+
192
+ if (layout.listing) {
193
+ const url = listingURL(bundleURL)
194
+ const dir = path.posix.dirname(bundleURL.pathname)
195
+
196
+ const listing = offloaded.map(
197
+ (artifact) => '/' + decodeURIComponent(path.posix.relative(dir, artifact.url.pathname)) + '\n'
198
+ )
199
+
200
+ await fs.writeFile(url, listing.join(''))
201
+ yield { url }
202
+ }
203
+ }
204
+
205
+ // Languages that package the bundle where the runtime can't read it, such as
206
+ // in a jar, list the files beside it, one `/<path>` per line, for the harness to
207
+ // extract along with the bundle.
208
+ function listingURL(bundleURL) {
209
+ return new URL(path.basename(bundleURL.pathname) + '.files', bundleURL)
182
210
  }
183
211
 
184
212
  function resolveBase(base, bundle) {
package/lib/assets.js CHANGED
@@ -7,11 +7,11 @@ exports.app = 'bare-stow'
7
7
 
8
8
  // Assets are laid out the way Bare Kit expects: the assets of `<name>.bundle`
9
9
  // live in `<name>/`, listed in `<name>.assets` for platforms that extract them.
10
- exports.unpack = async function (bundle, base, name) {
10
+ exports.unpack = async function (bundle, base, name, readModule) {
11
11
  const assets = []
12
12
 
13
13
  bundle = await unpack(bundle, { files: false, assets: true }, async (key) => {
14
- const relative = await exports.path(new URL('.' + key, base))
14
+ const relative = await exports.path(new URL('.' + key, base), readModule)
15
15
 
16
16
  assets.push({ path: relative, source: bundle.read(key) })
17
17
 
@@ -23,13 +23,13 @@ exports.unpack = async function (bundle, base, name) {
23
23
 
24
24
  // Assets of dependencies keep their path from `node_modules`, and others their
25
25
  // path within their package, so that every bundle places an asset alike.
26
- exports.path = async function (url) {
26
+ exports.path = async function (url, readModule) {
27
27
  const nm = url.pathname.indexOf('/node_modules/')
28
28
 
29
29
  if (nm >= 0) return url.pathname.slice(nm + 1)
30
30
 
31
31
  for (let dir = new URL('./', url); ; dir = new URL('../', dir)) {
32
- if (await fs.isFile(new URL('package.json', dir))) {
32
+ if ((await readModule(new URL('package.json', dir))) !== null) {
33
33
  return url.pathname.slice(dir.pathname.length)
34
34
  }
35
35
 
package/lib/graph.js CHANGED
@@ -2,7 +2,6 @@ const { fileURLToPath, pathToFileURL } = require('url')
2
2
  const bareLink = require('bare-link')
3
3
  const strip = require('bare-type-stripper')
4
4
  const { resolve } = require('bare-module-traverse')
5
- const fs = require('./fs')
6
5
  const targets = require('./target')
7
6
  const languages = require('./language')
8
7
  const { select } = require('./provider')
@@ -55,17 +54,18 @@ exports.traversal = function traversal(opts, hosts) {
55
54
  aliases: {
56
55
  '.ts': '.js',
57
56
  '.mts': '.mjs',
58
- '.cts': '.cjs'
57
+ '.cts': '.cjs',
58
+ ...opts.aliases
59
59
  },
60
60
  hosts
61
61
  }
62
62
  }
63
63
 
64
- exports.readModule = function readModule(modules) {
64
+ exports.readModule = function readModule(modules, read) {
65
65
  return async function (url) {
66
66
  if (modules.has(url.href)) return modules.get(url.href)
67
67
 
68
- const source = await fs.readModule(url)
68
+ const source = await read(url)
69
69
 
70
70
  if (source === null) return null
71
71
 
@@ -77,16 +77,14 @@ exports.readModule = function readModule(modules) {
77
77
 
78
78
  // The layout must not depend on the hosts, as Swift packages only accept
79
79
  // XCFrameworks. Resources linked outside of the directory are intermediate.
80
- exports.link = async function* link(entries, dir, traversal, readModule) {
80
+ exports.link = async function* link(entries, dir, traversal, hooks) {
81
81
  for await (const resource of bareLink(
82
82
  entries.map((url) => fileURLToPath(url)),
83
83
  {
84
84
  ...traversal,
85
85
  out: fileURLToPath(dir),
86
86
  alwaysMultiHost: true,
87
- readModule,
88
- listPrefix: fs.listPrefix,
89
- resolveModule: fs.resolveModule
87
+ ...hooks
90
88
  }
91
89
  )) {
92
90
  const url = pathToFileURL(resource)
@@ -3,7 +3,7 @@ const path = require('path')
3
3
  exports.name = 'kotlin'
4
4
 
5
5
  exports.options = function options(given = {}) {
6
- const { package: name, ...unknown } = given
6
+ const { package: name, platform = 'android', ...unknown } = given
7
7
 
8
8
  for (const option of Object.keys(unknown)) {
9
9
  throw new Error(`Unknown option '${option}' for language 'kotlin'`)
@@ -15,19 +15,33 @@ exports.options = function options(given = {}) {
15
15
  throw new Error(`'${name}' is not a valid Kotlin package name`)
16
16
  }
17
17
 
18
- return { package: name }
18
+ if (platform !== 'android' && platform !== 'jvm') {
19
+ throw new Error(`Option 'platform' must be 'android' or 'jvm', got '${platform}'`)
20
+ }
21
+
22
+ return { package: name, platform }
19
23
  }
20
24
 
21
- // The bundle is an asset of the module, under the directory of its package as
22
- // the assets of every module of the application share one namespace.
25
+ // The bundle is an asset or resource of the module, under the directory of its
26
+ // package as those of every module of the application share one namespace. A
27
+ // JVM module can't read its resources as files, so it lists the files beside
28
+ // the bundle for the harness to extract.
23
29
  exports.layout = function layout(out, options) {
24
30
  const dir = options.package.replace(/\./g, '/')
25
31
 
26
- return {
32
+ const module = {
27
33
  harness: new URL(`src/main/kotlin/${dir}/Harness.kt`, out),
28
- artifacts: new URL(`src/main/assets/${dir}/`, out),
29
34
  name: options.package,
30
- manifest: new URL('build.gradle', out),
35
+ manifest: new URL('build.gradle', out)
36
+ }
37
+
38
+ if (options.platform === 'jvm') {
39
+ return { ...module, artifacts: new URL(`src/main/resources/${dir}/`, out), listing: true }
40
+ }
41
+
42
+ return {
43
+ ...module,
44
+ artifacts: new URL(`src/main/assets/${dir}/`, out),
31
45
  addons: new URL('src/main/addons/', out),
32
46
  resources: new URL('src/main/assets/', out)
33
47
  }
@@ -37,11 +51,14 @@ exports.sibling = function sibling(url, name) {
37
51
  return new URL(capitalize(name) + '.kt', url)
38
52
  }
39
53
 
40
- // Assets are referenced by their path within the assets of the application.
54
+ // Assets and resources are referenced by their path within those of the
55
+ // application.
41
56
  exports.reference = function reference(from, to) {
42
- const assets = to.pathname.lastIndexOf('/src/main/assets/')
57
+ for (const dir of ['/src/main/assets/', '/src/main/resources/']) {
58
+ const index = to.pathname.lastIndexOf(dir)
43
59
 
44
- if (assets !== -1) return to.pathname.slice(assets + '/src/main/assets/'.length)
60
+ if (index !== -1) return to.pathname.slice(index + dir.length)
61
+ }
45
62
 
46
63
  return path.posix.relative(path.posix.dirname(from.pathname), to.pathname)
47
64
  }
@@ -49,11 +66,24 @@ exports.reference = function reference(from, to) {
49
66
  // The dependencies of Kotlin generators map Maven coordinates without a version
50
67
  // to the version that the module uses. They are part of the API of the module,
51
68
  // as the harness exposes their types.
52
- exports.manifest = function manifest({ name, dependencies, addons = [] }) {
69
+ exports.manifest = function manifest({ name, dependencies, options = {}, addons = [] }) {
53
70
  const apis = Object.entries(dependencies).map(
54
71
  ([coordinates, version]) => ` api ${JSON.stringify(coordinates + ':' + version)}`
55
72
  )
56
73
 
74
+ if (options.platform === 'jvm') {
75
+ return `\
76
+ plugins {
77
+ id "java-library"
78
+ id "org.jetbrains.kotlin.jvm"
79
+ }
80
+
81
+ dependencies {
82
+ ${apis.join('\n')}
83
+ }
84
+ `
85
+ }
86
+
57
87
  // Linked addons are native libraries in a directory per ABI, along with the
58
88
  // archives of any Java classes they use.
59
89
  const libraries = [
package/lib/link.js CHANGED
@@ -18,10 +18,13 @@ module.exports = async function* link(entries, target, out, opts = {}) {
18
18
  resolveTarget,
19
19
  resolveRPC,
20
20
  resolveLanguage,
21
+ readModule = fs.readModule,
22
+ listPrefix = fs.listPrefix,
23
+ resolveModule = fs.resolveModule,
21
24
  ...packOpts
22
25
  } = opts
23
26
 
24
- entries = await Promise.all([].concat(entries).map((entry) => fs.resolveModule(new URL(entry))))
27
+ entries = await Promise.all([].concat(entries).map((entry) => resolveModule(new URL(entry))))
25
28
 
26
29
  if (entries.length === 0) throw new Error("'entries' must not be empty")
27
30
  out = new URL(out)
@@ -61,7 +64,12 @@ module.exports = async function* link(entries, target, out, opts = {}) {
61
64
  const main = shim(entries[0], target.isolation, server)
62
65
 
63
66
  const traversal = graph.traversal(packOpts, hosts)
64
- const readModule = graph.readModule(main.modules)
67
+
68
+ const hooks = {
69
+ readModule: graph.readModule(main.modules, readModule),
70
+ listPrefix,
71
+ resolveModule
72
+ }
65
73
 
66
74
  const addons = []
67
75
 
@@ -69,13 +77,13 @@ module.exports = async function* link(entries, target, out, opts = {}) {
69
77
  [main.url, ...entries.slice(1)],
70
78
  layout.addons,
71
79
  traversal,
72
- readModule
80
+ hooks
73
81
  )) {
74
82
  addons.push(url)
75
83
  yield { url }
76
84
  }
77
85
 
78
- const linked = await collectAssets(entries, traversal, readModule)
86
+ const linked = await collectAssets(entries, traversal, hooks)
79
87
 
80
88
  if (linked.length > 0) {
81
89
  if (!layout.resources) {
@@ -109,21 +117,21 @@ module.exports = async function* link(entries, target, out, opts = {}) {
109
117
  }
110
118
  }
111
119
 
112
- async function collectAssets(entries, traversal, readModule) {
120
+ async function collectAssets(entries, traversal, hooks) {
113
121
  const collected = new Map()
114
122
 
115
123
  for (const entry of entries) {
116
124
  const bundle = await pack(
117
125
  entry,
118
126
  { ...traversal, linked: true },
119
- readModule,
120
- fs.listPrefix,
121
- fs.resolveModule
127
+ hooks.readModule,
128
+ hooks.listPrefix,
129
+ hooks.resolveModule
122
130
  )
123
131
 
124
132
  for (const key of bundle.assets) {
125
133
  const url = new URL(key)
126
- const relative = await assets.path(url)
134
+ const relative = await assets.path(url, hooks.readModule)
127
135
  const source = bundle.read(key)
128
136
  const existing = collected.get(relative)
129
137
 
@@ -15,21 +15,18 @@ import kotlinx.coroutines.CoroutineScope
15
15
  import kotlinx.coroutines.Dispatchers
16
16
  import to.holepunch.bare.rpc.CommandRouter
17
17
  import to.holepunch.bare.rpc.Rpc
18
+ import to.holepunch.bare.rpc.Transport
18
19
  import to.holepunch.bare.stow.host.Ipc
19
20
 
20
- typealias Client = Rpc
21
-
22
21
  typealias Handlers = CommandRouter.() -> Unit
23
22
 
24
- internal fun attach(ipc: Ipc, handlers: Handlers): Client {
23
+ internal fun attach(ipc: Ipc, handlers: Handlers): Rpc {
25
24
  val router = CommandRouter().apply(handlers)
26
25
 
27
26
  return Rpc(
28
- ipc.data,
29
- ipc::write,
27
+ Transport(ipc.data, ipc::write, ipc::close),
30
28
  CoroutineScope(Dispatchers.Default),
31
- onEvent = router::dispatch,
32
- onRequest = router::dispatch,
29
+ router,
33
30
  )
34
31
  }
35
32
  `
@@ -37,8 +34,8 @@ internal fun attach(ipc: Ipc, handlers: Handlers): Client {
37
34
  return {
38
35
  source,
39
36
  dependencies: {
40
- 'com.github.holepunchto.bare-rpc-kotlin:bare-rpc-kotlin': 'v0.4.0',
41
- 'com.github.holepunchto.bare-stow-host-kotlin:bare-stow-host-kotlin': 'v0.1.1'
37
+ 'com.github.holepunchto.bare-rpc-kotlin:bare-rpc-kotlin': 'v0.5.0',
38
+ 'com.github.holepunchto.bare-stow-host-kotlin:bare-stow-host-kotlin': 'v0.2.1'
42
39
  }
43
40
  }
44
41
  }
@@ -49,80 +46,16 @@ import BareRPC
49
46
  import BareStowHost
50
47
  import Foundation
51
48
 
52
- public final class Client: @unchecked Sendable {
53
- public let rpc: RPC
54
-
55
- private let delegate: Delegate
56
-
57
- fileprivate init(rpc: RPC, delegate: Delegate) {
58
- self.rpc = rpc
59
- self.delegate = delegate
60
- }
61
-
62
- public func request(_ command: UInt, data: Data? = nil) async throws -> Data? {
63
- try await rpc.request(command, data: data)
64
- }
65
-
66
- public func event(_ command: UInt, data: Data? = nil) async {
67
- await rpc.event(command, data: data)
68
- }
69
- }
70
-
71
49
  public typealias Handlers = (CommandRouter) -> Void
72
50
 
73
- func attach(_ ipc: IPC, _ handlers: Handlers?) -> Client {
51
+ extension BareStowHost.IPC: @retroactive BareRPC.Transport {}
52
+
53
+ func attach(_ ipc: IPC, _ handlers: Handlers?) -> RPC {
74
54
  let router = CommandRouter()
75
55
 
76
56
  handlers?(router)
77
57
 
78
- let delegate = Delegate(router: router)
79
- let rpc = RPC(delegate: delegate)
80
-
81
- Task {
82
- for await frame in delegate.frames {
83
- try? await ipc.write(frame)
84
- }
85
- }
86
-
87
- Task {
88
- do {
89
- for try await data in ipc {
90
- await rpc.receive(data)
91
- }
92
- } catch {}
93
-
94
- delegate.finish()
95
- }
96
-
97
- return Client(rpc: rpc, delegate: delegate)
98
- }
99
-
100
- private final class Delegate: RPCDelegate, @unchecked Sendable {
101
- let router: CommandRouter
102
- let frames: AsyncStream<Data>
103
-
104
- private let continuation: AsyncStream<Data>.Continuation
105
-
106
- init(router: CommandRouter) {
107
- self.router = router
108
- (frames, continuation) = AsyncStream.makeStream()
109
- }
110
-
111
- func rpc(_ rpc: RPC, send data: Data) {
112
- continuation.yield(data)
113
- }
114
-
115
- func rpc(_ rpc: RPC, didReceiveRequest request: IncomingRequest) async throws {
116
- await router.dispatch(request)
117
- }
118
-
119
- func rpc(_ rpc: RPC, didReceiveEvent event: IncomingEvent) async {
120
- await router.dispatch(event)
121
- }
122
-
123
- func finish() {
124
- continuation.finish()
125
- }
58
+ return RPC(ipc, handler: router)
126
59
  }
127
60
  `
128
61
 
@@ -131,12 +64,12 @@ private final class Delegate: RPCDelegate, @unchecked Sendable {
131
64
  dependencies: {
132
65
  'bare-rpc-swift': {
133
66
  url: 'https://github.com/holepunchto/bare-rpc-swift',
134
- from: '1.1.0',
67
+ from: '2.0.0',
135
68
  products: ['BareRPC']
136
69
  },
137
70
  'bare-stow-host-swift': {
138
71
  url: 'https://github.com/holepunchto/bare-stow-host-swift',
139
- from: '0.1.0',
72
+ from: '0.2.0',
140
73
  products: ['BareStowHost']
141
74
  }
142
75
  }
@@ -150,28 +83,22 @@ from collections.abc import Callable
150
83
  from bare_rpc import RPC, CommandRouter
151
84
  from bare_stow_host import IPC
152
85
 
153
- Client = RPC
154
-
155
86
  Handlers = Callable[[CommandRouter], None]
156
87
 
157
88
 
158
- def attach(ipc: IPC, handlers: Handlers) -> Client:
89
+ def attach(ipc: IPC, handlers: Handlers) -> RPC:
159
90
  router = CommandRouter()
160
91
 
161
92
  handlers(router)
162
93
 
163
- return RPC(
164
- ipc,
165
- on_request=router.dispatch_request,
166
- on_event=router.dispatch_event,
167
- )
94
+ return RPC(ipc, handler=router)
168
95
  `
169
96
 
170
97
  return {
171
98
  source,
172
99
  dependencies: {
173
- 'bare-rpc': '~=0.1.0',
174
- 'bare-stow-host': '~=0.1.0'
100
+ 'bare-rpc': '~=0.2.0',
101
+ 'bare-stow-host': '~=0.2.0'
175
102
  }
176
103
  }
177
104
  }
@@ -225,5 +152,5 @@ export type Handlers = (router: import('bare-rpc').CommandRouter) => void
225
152
  export function attach(ipc: import('bare-stow-host').IPC, handlers: Handlers): import('bare-rpc')
226
153
  `
227
154
 
228
- return { source, types, dependencies: { 'bare-rpc': '^1.3.9' } }
155
+ return { source, types, dependencies: { 'bare-rpc': '^1.5.0' } }
229
156
  }
@@ -1,14 +1,32 @@
1
+ const path = require('path')
1
2
  const swift = require('./swift')
2
3
 
3
4
  exports.name = 'bare-kit'
4
5
  exports.isolation = 'thread'
5
- exports.linked = true
6
- exports.linking = ({ targetOptions }) => targetOptions.linking
7
- exports.offload = false
6
+ exports.linked = (context) => !isJvm(context)
7
+ exports.linking = linking
8
+ exports.offload = isJvm
8
9
  exports.format = 'bundle'
9
10
  exports.encoding = null
10
11
  exports.extension = '.bundle'
11
- exports.hosts = ({ language }) => hosts[language]
12
+ exports.hosts = (context) => (isJvm(context) ? desktop : hosts[context.language])
13
+
14
+ // On the JVM, the harness extracts the bundle from the resources of its module,
15
+ // so the addons and assets of the bundle are offloaded beside it rather than
16
+ // linked.
17
+ function isJvm({ language, options }) {
18
+ return language === 'kotlin' && options.platform === 'jvm'
19
+ }
20
+
21
+ function linking(context) {
22
+ const { targetOptions } = context
23
+
24
+ if (isJvm(context) && targetOptions.linking === 'app') {
25
+ throw new Error("Option 'linking' must be 'package' for the JVM")
26
+ }
27
+
28
+ return targetOptions.linking
29
+ }
12
30
 
13
31
  // Applications with several bundles link their addons once with `link()`.
14
32
  exports.options = function options(given = {}) {
@@ -27,14 +45,23 @@ exports.options = function options(given = {}) {
27
45
 
28
46
  const bareKitSwift = {
29
47
  url: 'https://github.com/holepunchto/bare-kit-swift',
30
- from: '2.0.0',
48
+ from: '2.2.0',
31
49
  products: ['BareKit']
32
50
  }
33
51
 
34
52
  const bareKitKotlin = {
35
- 'com.github.holepunchto.bare-kit-kotlin:bare-kit-kotlin': 'v0.1.1'
53
+ 'com.github.holepunchto.bare-kit-kotlin:bare-kit-kotlin': 'v0.2.2'
36
54
  }
37
55
 
56
+ const desktop = [
57
+ 'darwin-arm64',
58
+ 'darwin-x64',
59
+ 'linux-arm64',
60
+ 'linux-x64',
61
+ 'win32-arm64',
62
+ 'win32-x64'
63
+ ]
64
+
38
65
  const hosts = {
39
66
  kotlin: ['android-arm64', 'android-arm', 'android-x64', 'android-ia32'],
40
67
  swift: ['darwin-arm64', 'darwin-x64', 'ios-arm64', 'ios-arm64-simulator', 'ios-x64-simulator']
@@ -62,7 +89,7 @@ exports.languages = {
62
89
 
63
90
  return swift(context, {
64
91
  module: 'BareKit',
65
- conformance: 'extension BareKit.IPC: @retroactive Transport {}',
92
+ conformance: 'extension BareKit.IPC: @retroactive BareStowHost.Transport {}',
66
93
  fields: { worklet: 'Worklet' },
67
94
  params: ['options: Worklet.Options = Worklet.Options()'],
68
95
  setup: `\
@@ -82,36 +109,53 @@ Task { ipc.exited(code: await worklet.wait()) }`,
82
109
  }
83
110
  }
84
111
 
85
- function kotlin({ bundle, client, resources, options }) {
86
- const start = resources
87
- ? `worklet.start(${JSON.stringify(resources + '.bundle')}, context.assets.open(${JSON.stringify(bundle)}).use { it.readBytes() })`
88
- : `worklet.start(${JSON.stringify(bundle)})`
112
+ function kotlin(context) {
113
+ const { client, options } = context
114
+
115
+ const platform = options.platform === 'jvm' ? jvm(context) : android(context)
116
+
117
+ const imports = [
118
+ ...platform.imports,
119
+ 'kotlin.time.Duration',
120
+ 'kotlinx.coroutines.CoroutineScope',
121
+ 'kotlinx.coroutines.Dispatchers',
122
+ 'kotlinx.coroutines.launch',
123
+ 'to.holepunch.bare.kit.Ipc as WorkletIpc',
124
+ 'to.holepunch.bare.kit.Worklet',
125
+ 'to.holepunch.bare.stow.host.Ipc',
126
+ ...(client ? ['to.holepunch.bare.rpc.Rpc'] : [])
127
+ ].sort()
89
128
 
90
129
  const source = `\
91
130
  package ${options.package}
92
131
 
93
- import android.content.Context
94
- import kotlin.time.Duration
95
- import kotlinx.coroutines.CoroutineScope
96
- import kotlinx.coroutines.Dispatchers
97
- import kotlinx.coroutines.launch
98
- import to.holepunch.bare.kit.Ipc as WorkletIpc
99
- import to.holepunch.bare.kit.Worklet
100
- import to.holepunch.bare.stow.host.Ipc
101
-
102
- class Started(val worklet: Worklet, val ipc: Ipc${client ? ', val rpc: Client' : ''})
132
+ ${imports.map((name) => 'import ' + name).join('\n')}
103
133
 
134
+ class Started(val worklet: Worklet, val ipc: Ipc${client ? ', val rpc: Rpc' : ''})
135
+ ${platform.declarations}
104
136
  suspend fun start(
105
- context: Context,
137
+ ${platform.params}\
106
138
  options: Worklet.Options = Worklet.Options(),
107
139
  timeout: Duration? = null,
108
140
  grace: Duration = Ipc.DEFAULT_GRACE,${client ? '\n handlers: Handlers = {},' : ''}
109
141
  ): Started {
110
- val worklet = Worklet(context, options)
111
-
112
- val transport = WorkletIpc(worklet)
142
+ val worklet = ${platform.worklet}
143
+
144
+ val transport =
145
+ try {
146
+ WorkletIpc(worklet)
147
+ } catch (error: Throwable) {
148
+ worklet.close()
149
+ throw error
150
+ }
113
151
 
114
- ${start}
152
+ try {
153
+ ${platform.start}
154
+ } catch (error: Throwable) {
155
+ transport.close()
156
+ worklet.close()
157
+ throw error
158
+ }
115
159
 
116
160
  val ipc =
117
161
  Ipc(
@@ -126,6 +170,7 @@ suspend fun start(
126
170
  ipc.exited(worklet.wait())
127
171
 
128
172
  transport.close()
173
+ worklet.close()
129
174
  }
130
175
  ${client ? '\n val rpc = attach(ipc, handlers)\n' : ''}
131
176
  ipc.ready()
@@ -138,7 +183,56 @@ ${client ? '\n val rpc = attach(ipc, handlers)\n' : ''}
138
183
  source,
139
184
  dependencies: {
140
185
  ...bareKitKotlin,
141
- 'com.github.holepunchto.bare-stow-host-kotlin:bare-stow-host-kotlin': 'v0.1.1'
186
+ 'com.github.holepunchto.bare-stow-host-kotlin:bare-stow-host-kotlin': 'v0.2.1'
142
187
  }
143
188
  }
144
189
  }
190
+
191
+ function android({ bundle, resources }) {
192
+ return {
193
+ imports: ['android.content.Context'],
194
+ declarations: '',
195
+ params: ' context: Context,\n',
196
+ worklet: 'Worklet(context, options)',
197
+ start: resources
198
+ ? `worklet.start(${JSON.stringify(resources + '.bundle')}, context.assets.open(${JSON.stringify(bundle)}).use { it.readBytes() })`
199
+ : `worklet.start(${JSON.stringify(bundle)})`
200
+ }
201
+ }
202
+
203
+ // The bundle and the files listed beside it are extracted once per process, to
204
+ // a directory of its own that is removed on exit.
205
+ function jvm({ bundle }) {
206
+ const dir = path.posix.dirname(bundle)
207
+ const name = '/' + path.posix.basename(bundle)
208
+
209
+ return {
210
+ imports: ['java.io.File', 'java.nio.file.Files', 'kotlinx.coroutines.withContext'],
211
+ declarations: `
212
+ private val bundle: File by lazy {
213
+ val dir = Files.createTempDirectory("bare-stow").toFile()
214
+
215
+ Runtime.getRuntime().addShutdownHook(Thread { dir.deleteRecursively() })
216
+
217
+ val loader = Started::class.java.classLoader
218
+
219
+ fun open(name: String) = loader.getResourceAsStream(${JSON.stringify(dir)} + name)!!
220
+
221
+ val listed = open(${JSON.stringify(name + '.files')}).bufferedReader().use { it.readLines() }
222
+
223
+ for (name in listOf(${JSON.stringify(name)}) + listed) {
224
+ val file = File(dir, name)
225
+
226
+ file.parentFile.mkdirs()
227
+
228
+ open(name).use { input -> file.outputStream().use { input.copyTo(it) } }
229
+ }
230
+
231
+ File(dir, ${JSON.stringify(name)})
232
+ }
233
+ `,
234
+ params: '',
235
+ worklet: 'Worklet(options)',
236
+ start: 'worklet.start(withContext(Dispatchers.IO) { bundle }.path)'
237
+ }
238
+ }
@@ -22,12 +22,12 @@ exports.languages = {
22
22
  generate(context) {
23
23
  return harness(context, {
24
24
  name: 'bare-sidecar',
25
- version: '^0.5.12',
25
+ version: '^0.5.15',
26
26
  binding: 'Sidecar',
27
27
  url: true,
28
- setup: `\
29
- const sidecar = new Sidecar(bundle, { stdio: 'inherit', ...opts })
30
-
28
+ handle: 'sidecar',
29
+ spawn: "new Sidecar(bundle, { stdio: 'inherit', ...opts })",
30
+ connect: `\
31
31
  const ipc = stow.wrap(sidecar, { kill: () => sidecar.kill('SIGKILL'), timeout, grace })
32
32
 
33
33
  sidecar.on('exit', (code, signal) => ipc.exited(code, signal))`,
@@ -40,10 +40,10 @@ sidecar.on('exit', (code, signal) => ipc.exited(code, signal))`,
40
40
  generate(context) {
41
41
  return swift(context, {
42
42
  module: 'BareSidecar',
43
- conformance: 'extension Sidecar: @retroactive Transport {}',
43
+ conformance: 'extension Sidecar: @retroactive BareStowHost.Transport {}',
44
+ handle: { name: 'sidecar', type: 'Sidecar' },
45
+ spawn: 'try await Sidecar.spawn(bundle)',
44
46
  setup: `\
45
- let sidecar = try await Sidecar.spawn(bundle)
46
-
47
47
  let ipc = BareStowHost.IPC(sidecar, kill: { sidecar.kill() }, timeout: timeout, grace: grace)
48
48
 
49
49
  Task {
@@ -55,7 +55,7 @@ Task {
55
55
  dependencies: {
56
56
  'bare-sidecar-swift': {
57
57
  url: 'https://github.com/holepunchto/bare-sidecar-swift',
58
- from: '0.1.0',
58
+ from: '0.1.3',
59
59
  products: ['BareSidecar']
60
60
  }
61
61
  }
@@ -70,16 +70,16 @@ import asyncio
70
70
  from pathlib import Path
71
71
  from typing import NamedTuple
72
72
 
73
- from bare_sidecar import spawn
73
+ ${client ? 'from bare_rpc import RPC\n' : ''}from bare_sidecar import Sidecar, spawn
74
74
  from bare_stow_host import DEFAULT_GRACE, IPC, wrap
75
- ${client ? `\nfrom ${client} import Client, Handlers, attach\n` : ''}
75
+ ${client ? `\nfrom ${client} import Handlers, attach\n` : ''}
76
76
  bundle = Path(__file__).parent / ${JSON.stringify(bundle)}
77
77
 
78
78
  _watching = set()
79
79
 
80
80
 
81
81
  class Started(NamedTuple):
82
- ipc: IPC${client ? '\n rpc: Client' : ''}
82
+ ipc: IPC${client ? '\n rpc: RPC' : ''}
83
83
 
84
84
 
85
85
  async def start(
@@ -90,6 +90,15 @@ async def start(
90
90
  ) -> Started:
91
91
  sidecar = await spawn(bundle, **opts)
92
92
 
93
+ return await connect(sidecar, timeout=timeout, grace=grace${client ? ', handlers=handlers' : ''})
94
+
95
+
96
+ async def connect(
97
+ sidecar: Sidecar,
98
+ *,
99
+ timeout=None,
100
+ grace=DEFAULT_GRACE,${client ? '\n handlers: Handlers = lambda router: None,' : ''}
101
+ ) -> Started:
93
102
  ipc = wrap(sidecar, kill=sidecar.kill, timeout=timeout, grace=grace)
94
103
 
95
104
  watching = asyncio.ensure_future(_watch(sidecar, ipc))
@@ -112,8 +121,8 @@ async def _watch(sidecar, ipc):
112
121
  return {
113
122
  source,
114
123
  dependencies: {
115
- 'bare-sidecar': '~=0.1.0',
116
- 'bare-stow-host': '~=0.1.0'
124
+ 'bare-sidecar': '~=0.1.3',
125
+ 'bare-stow-host': '~=0.2.0'
117
126
  }
118
127
  }
119
128
  }
@@ -21,11 +21,11 @@ exports.languages = {
21
21
  generate(context) {
22
22
  return harness(context, {
23
23
  name: 'bare-worker',
24
- version: '^4.3.0',
24
+ version: '^4.5.3',
25
25
  binding: 'Worker',
26
- setup: `\
27
- const worker = new Worker(bundle, opts)
28
-
26
+ handle: 'worker',
27
+ spawn: 'new Worker(bundle, opts)',
28
+ connect: `\
29
29
  const ipc = stow.wrap(worker.IPC, { kill: () => worker.terminate(), timeout, grace })
30
30
 
31
31
  worker.IPC.on('end', () => worker.IPC.destroy())
@@ -1,11 +1,18 @@
1
1
  module.exports = function harness(context, target) {
2
2
  const { bundle, client, options } = context
3
- const { name, version, binding, url = false, setup, types } = target
3
+ const { name, version, binding, url = false, handle, spawn, connect, types } = target
4
4
 
5
5
  const esm = options.module === 'esm'
6
6
 
7
+ const params = `{ timeout, grace${client ? ', handlers = () => {}' : ''} } = {}`
8
+
9
+ const start = `\
10
+ const ${handle} = ${spawn}
11
+
12
+ return await connect(${handle}, { timeout, grace${client ? ', handlers' : ''} })`
13
+
7
14
  const body = `\
8
- ${setup}
15
+ ${connect}
9
16
  ${client ? '\nconst rpc = client.attach(ipc, handlers)\n' : ''}
10
17
  await ipc.ready
11
18
 
@@ -18,7 +25,11 @@ import stow from 'bare-stow-host'
18
25
  ${client ? `import * as client from ${JSON.stringify(client)}\n` : ''}
19
26
  const bundle = ${url ? `new URL(import.meta.resolve(${JSON.stringify(bundle)}))` : `import.meta.resolve(${JSON.stringify(bundle)})`}
20
27
 
21
- export async function start({ timeout, grace, ${client ? 'handlers = () => {}, ' : ''}...opts } = {}) {
28
+ export async function start({ timeout, grace, ${client ? 'handlers, ' : ''}...opts } = {}) {
29
+ ${indent(start, 2)}
30
+ }
31
+
32
+ export async function connect(${handle}, ${params}) {
22
33
  ${indent(body, 2)}
23
34
  }
24
35
  `
@@ -28,14 +39,18 @@ const stow = require('bare-stow-host')
28
39
  ${client ? `const client = require(${JSON.stringify(client)})\n` : ''}
29
40
  const bundle = require.resolve(${JSON.stringify(bundle)})
30
41
 
31
- module.exports = {
32
- async start({ timeout, grace, ${client ? 'handlers = () => {}, ' : ''}...opts } = {}) {
33
- ${indent(body, 4)}
34
- }
42
+ module.exports = { start, connect }
43
+
44
+ async function start({ timeout, grace, ${client ? 'handlers, ' : ''}...opts } = {}) {
45
+ ${indent(start, 2)}
46
+ }
47
+
48
+ async function connect(${handle}, ${params}) {
49
+ ${indent(body, 2)}
35
50
  }
36
51
  `
37
52
 
38
- const handle = client
53
+ const started = client
39
54
  ? `{ ipc: import('bare-stow-host').IPC; rpc: ReturnType<typeof import(${JSON.stringify(client)}).attach> }`
40
55
  : `{ ipc: import('bare-stow-host').IPC }`
41
56
 
@@ -43,12 +58,14 @@ ${indent(body, 4)}
43
58
  source,
44
59
  dependencies: {
45
60
  [name]: version,
46
- 'bare-stow-host': '^0.1.0'
61
+ 'bare-stow-host': '^0.2.0'
47
62
  },
48
63
  types: `\
49
- export function start(
50
- opts?: ${types} & Omit<import('bare-stow-host').IPCOptions, 'kill'>${client ? ` & { handlers?: import(${JSON.stringify(client)}).Handlers }` : ''}
51
- ): Promise<${handle}>
64
+ type ConnectOptions = Omit<import('bare-stow-host').IPCOptions, 'kill'>${client ? ` & { handlers?: import(${JSON.stringify(client)}).Handlers }` : ''}
65
+
66
+ export function start(opts?: ${types} & ConnectOptions): Promise<${started}>
67
+
68
+ export function connect(${handle}: import('${name}'), opts?: ConnectOptions): Promise<${started}>
52
69
  `
53
70
  }
54
71
  }
@@ -2,15 +2,62 @@
2
2
  // an `IPC` of their own.
3
3
  module.exports = function harness(context, target) {
4
4
  const { bundle, client } = context
5
- const { module, conformance, fields = {}, params = [], setup, dependencies } = target
5
+ const {
6
+ module,
7
+ conformance,
8
+ fields = {},
9
+ params = [],
10
+ handle,
11
+ spawn,
12
+ setup,
13
+ dependencies
14
+ } = target
6
15
 
7
16
  const started = { ...fields, ipc: 'BareStowHost.IPC' }
8
17
 
9
- if (client) started.rpc = 'Client'
18
+ if (client) started.rpc = 'RPC'
19
+
20
+ const options = [
21
+ 'timeout: TimeInterval? = nil',
22
+ 'grace: TimeInterval = BareStowHost.IPC.defaultGrace',
23
+ ...(client ? ['handlers: Handlers? = nil'] : [])
24
+ ]
25
+
26
+ const forwarded = ['timeout', 'grace', ...(client ? ['handlers'] : [])].map(
27
+ (name) => `${name}: ${name}`
28
+ )
29
+
30
+ const body = `\
31
+ ${setup}
32
+ ${client ? '\nlet rpc = attach(ipc, handlers)\n' : ''}
33
+ try await ipc.ready()
34
+
35
+ return Started(${Object.keys(started)
36
+ .map((name) => `${name}: ${name}`)
37
+ .join(', ')})`
38
+
39
+ const resource = `let bundle = Bundle.module.url(forResource: ${JSON.stringify(bundle)}, withExtension: nil)!`
40
+
41
+ // Targets that spawn the bundle let hosts connect to one they spawned, such
42
+ // as from another entry.
43
+ const start = handle
44
+ ? `\
45
+ ${resource}
46
+
47
+ let ${handle.name} = ${spawn}
48
+
49
+ return try await connect(${[handle.name, ...forwarded].join(', ')})`
50
+ : `${resource}\n\n${body}`
51
+
52
+ const functions = [func('start', [...params, ...options], start)]
53
+
54
+ if (handle) {
55
+ functions.push(func('connect', [`_ ${handle.name}: ${handle.type}`, ...options], body))
56
+ }
10
57
 
11
58
  const source = `\
12
59
  import ${module}
13
- import BareStowHost
60
+ ${client ? 'import BareRPC\n' : ''}import BareStowHost
14
61
  import Foundation
15
62
 
16
63
  ${conformance}
@@ -21,26 +68,7 @@ ${Object.entries(started)
21
68
  .join('\n')}
22
69
  }
23
70
 
24
- public func start(
25
- ${[
26
- ...params,
27
- 'timeout: TimeInterval? = nil',
28
- 'grace: TimeInterval = BareStowHost.IPC.defaultGrace',
29
- ...(client ? ['handlers: Handlers? = nil'] : [])
30
- ]
31
- .map((param) => ' ' + param)
32
- .join(',\n')}
33
- ) async throws -> Started {
34
- let bundle = Bundle.module.url(forResource: ${JSON.stringify(bundle)}, withExtension: nil)!
35
-
36
- ${setup.replace(/^(?=.)/gm, ' ')}
37
- ${client ? '\n let rpc = attach(ipc, handlers)\n' : ''}
38
- try await ipc.ready()
39
-
40
- return Started(${Object.keys(started)
41
- .map((name) => `${name}: ${name}`)
42
- .join(', ')})
43
- }
71
+ ${functions.join('\n\n')}
44
72
  `
45
73
 
46
74
  return {
@@ -49,9 +77,25 @@ ${client ? '\n let rpc = attach(ipc, handlers)\n' : ''}
49
77
  ...dependencies,
50
78
  'bare-stow-host-swift': {
51
79
  url: 'https://github.com/holepunchto/bare-stow-host-swift',
52
- from: '0.1.0',
80
+ from: '0.2.0',
53
81
  products: ['BareStowHost']
54
82
  }
55
83
  }
56
84
  }
57
85
  }
86
+
87
+ function func(name, params, body) {
88
+ return `\
89
+ public func ${name}(
90
+ ${params.map((param) => ' ' + param).join(',\n')}
91
+ ) async throws -> Started {
92
+ ${indent(body)}
93
+ }`
94
+ }
95
+
96
+ function indent(source) {
97
+ return source
98
+ .split('\n')
99
+ .map((line) => (line.length === 0 ? line : ' ' + line))
100
+ .join('\n')
101
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bare-stow",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Module bundling and harness generation for Bare",
5
5
  "exports": {
6
6
  "./package": "./package.json",
@@ -54,27 +54,27 @@
54
54
  },
55
55
  "homepage": "https://github.com/holepunchto/bare-stow#readme",
56
56
  "dependencies": {
57
- "bare-bundle": "^1.8.3",
58
- "bare-bundle-id": "^1.0.0",
59
- "bare-fs": "^4.2.1",
60
- "bare-link": "^4.1.0",
61
- "bare-module-traverse": "^2.2.0",
62
- "bare-pack": "^3.0.0",
63
- "bare-path": "^3.0.0",
64
- "bare-type-stripper": "^0.1.2",
65
- "bare-unpack": "^1.1.3",
66
- "paparam": "^1.5.0"
57
+ "bare-bundle": "^1.11.0",
58
+ "bare-bundle-id": "^1.0.3",
59
+ "bare-fs": "^4.8.4",
60
+ "bare-link": "^4.2.0",
61
+ "bare-module-traverse": "^2.7.4",
62
+ "bare-pack": "^3.0.2",
63
+ "bare-path": "^3.1.2",
64
+ "bare-type-stripper": "^0.1.7",
65
+ "bare-unpack": "^1.1.4",
66
+ "paparam": "^1.15.0"
67
67
  },
68
68
  "devDependencies": {
69
- "bare-os": "^3.0.0",
70
- "bare-rpc": "^1.3.2",
71
- "bare-sidecar": "^0.5.11",
72
- "bare-stow-host": "^0.1.0",
69
+ "bare-os": "^3.9.4",
70
+ "bare-rpc": "^1.5.0",
71
+ "bare-sidecar": "^0.5.15",
72
+ "bare-stow-host": "^0.2.0",
73
73
  "bare-stow-runtime": "^0.1.0",
74
- "bare-worker": "^4.3.0",
75
- "brittle": "^4.0.2",
76
- "lunte": "^1.7.0",
77
- "prettier": "^3.3.3",
74
+ "bare-worker": "^4.5.3",
75
+ "brittle": "^4.5.0",
76
+ "lunte": "^1.8.6",
77
+ "prettier": "^3.9.10",
78
78
  "prettier-config-holepunch": "^2.0.0",
79
79
  "which-runtime": "^1.4.0"
80
80
  },