bare-stow 0.1.9 → 0.3.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.
@@ -1,24 +1,156 @@
1
1
  exports.name = 'bare-rpc'
2
2
 
3
- exports.generate = function generate(opts) {
4
- const { ipc, rpc } = opts
3
+ exports.languages = {
4
+ javascript: { generate: javascript },
5
+ kotlin: { generate: kotlin },
6
+ python: { generate: python },
7
+ swift: { generate: swift }
8
+ }
9
+
10
+ function kotlin({ options }) {
11
+ const source = `\
12
+ package ${options.package}
13
+
14
+ import kotlinx.coroutines.CoroutineScope
15
+ import kotlinx.coroutines.Dispatchers
16
+ import to.holepunch.bare.rpc.CommandRouter
17
+ import to.holepunch.bare.rpc.Rpc
18
+ import to.holepunch.bare.rpc.Transport
19
+ import to.holepunch.bare.stow.host.Ipc
20
+
21
+ typealias Handlers = CommandRouter.() -> Unit
22
+
23
+ internal fun attach(ipc: Ipc, handlers: Handlers): Rpc {
24
+ val router = CommandRouter().apply(handlers)
25
+
26
+ return Rpc(
27
+ Transport(ipc.data, ipc::write, ipc::close),
28
+ CoroutineScope(Dispatchers.Default),
29
+ router,
30
+ )
31
+ }
32
+ `
33
+
34
+ return {
35
+ source,
36
+ dependencies: {
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'
39
+ }
40
+ }
41
+ }
42
+
43
+ function swift() {
44
+ const source = `\
45
+ import BareRPC
46
+ import BareStowHost
47
+ import Foundation
5
48
 
6
- const esm = opts.module === 'esm'
49
+ public typealias Handlers = (CommandRouter) -> Void
7
50
 
8
- return [
9
- {
10
- source: `\
11
- ${esm ? "const { default: RPC } = await import('bare-rpc')" : "const RPC = require('bare-rpc')"}
51
+ extension BareStowHost.IPC: @retroactive BareRPC.Transport {}
12
52
 
13
- const router = new RPC.CommandRouter()
14
- const ${rpc} = new RPC(${ipc}, router)
53
+ func attach(_ ipc: IPC, _ handlers: Handlers?) -> RPC {
54
+ let router = CommandRouter()
15
55
 
16
- ${rpc}.respond = router.respond.bind(router)
56
+ handlers?(router)
57
+
58
+ return RPC(ipc, handler: router)
59
+ }
17
60
  `
18
- },
19
- {
20
- extension: '.d.ts',
21
- source: "import('bare-rpc')"
61
+
62
+ return {
63
+ source,
64
+ dependencies: {
65
+ 'bare-rpc-swift': {
66
+ url: 'https://github.com/holepunchto/bare-rpc-swift',
67
+ from: '2.0.0',
68
+ products: ['BareRPC']
69
+ },
70
+ 'bare-stow-host-swift': {
71
+ url: 'https://github.com/holepunchto/bare-stow-host-swift',
72
+ from: '0.2.0',
73
+ products: ['BareStowHost']
74
+ }
22
75
  }
23
- ]
76
+ }
77
+ }
78
+
79
+ function python() {
80
+ const source = `\
81
+ from collections.abc import Callable
82
+
83
+ from bare_rpc import RPC, CommandRouter
84
+ from bare_stow_host import IPC
85
+
86
+ Handlers = Callable[[CommandRouter], None]
87
+
88
+
89
+ def attach(ipc: IPC, handlers: Handlers) -> RPC:
90
+ router = CommandRouter()
91
+
92
+ handlers(router)
93
+
94
+ return RPC(ipc, handler=router)
95
+ `
96
+
97
+ return {
98
+ source,
99
+ dependencies: {
100
+ 'bare-rpc': '~=0.2.0',
101
+ 'bare-stow-host': '~=0.2.0'
102
+ }
103
+ }
104
+ }
105
+
106
+ function javascript({ role, options }) {
107
+ const esm = options.module === 'esm'
108
+
109
+ // The bundle registers its handlers in `start()`, before it signals ready.
110
+ const server = role === 'server'
111
+
112
+ const params = server ? 'ipc' : 'ipc, handlers'
113
+
114
+ const body = server
115
+ ? `\
116
+ const router = new RPC.CommandRouter()
117
+ const rpc = new RPC(ipc, router)
118
+
119
+ rpc.respond = router.respond.bind(router)
120
+
121
+ return rpc
122
+ }
123
+ `
124
+ : `\
125
+ const router = new RPC.CommandRouter()
126
+
127
+ handlers(router)
128
+
129
+ return new RPC(ipc, router)
130
+ }
131
+ `
132
+
133
+ const source = esm
134
+ ? `\
135
+ import RPC from 'bare-rpc'
136
+
137
+ export function attach(${params}) {
138
+ ${body}`
139
+ : `\
140
+ const RPC = require('bare-rpc')
141
+
142
+ exports.attach = function attach(${params}) {
143
+ ${body}`
144
+
145
+ const types = server
146
+ ? `\
147
+ export function attach(ipc: import('bare-stow-host').IPC): import('bare-rpc')
148
+ `
149
+ : `\
150
+ export type Handlers = (router: import('bare-rpc').CommandRouter) => void
151
+
152
+ export function attach(ipc: import('bare-stow-host').IPC, handlers: Handlers): import('bare-rpc')
153
+ `
154
+
155
+ return { source, types, dependencies: { 'bare-rpc': '^1.5.0' } }
24
156
  }
package/lib/rpc.js CHANGED
@@ -1,23 +1,11 @@
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]
1
+ const { registry, validateLanguages } = require('./provider')
9
2
 
10
- if (load) return load()
11
-
12
- if (resolve) return validate(resolve(name))
13
-
14
- throw new Error(`Unknown RPC library '${name}'`)
3
+ const providers = {
4
+ 'bare-rpc': () => require('./rpc/bare-rpc')
15
5
  }
16
6
 
17
- function validate(rpc) {
18
- if (!rpc || typeof rpc.generate !== 'function') {
19
- throw new Error('RPC library must provide a generate() function')
20
- }
7
+ module.exports = registry('RPC library', providers, function validate(rpc) {
8
+ validateLanguages('RPC library', rpc?.languages)
21
9
 
22
10
  return rpc
23
- }
11
+ })
package/lib/shim.js CHANGED
@@ -1,40 +1,33 @@
1
- const path = require('path')
2
- const { fileURLToPath } = require('url')
1
+ const javascript = require('./language/javascript')
2
+ const { select } = require('./provider')
3
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
4
+ // The bundle runs in Bare, so its side of every provider is JavaScript.
5
+ module.exports = function shim(entry, isolation, server = null) {
6
+ if (server) server = select('RPC library', server, javascript.name)
9
7
 
10
- const specifier = relativeSpecifier(shimURL, entry)
8
+ const dir = new URL('./', entry)
9
+ const url = new URL('__main__.mjs', dir)
10
+ const serverURL = server ? new URL('__server__.mjs', dir) : null
11
11
 
12
- const arg = server ? '{ rpc, ipc }' : 'ipc'
12
+ const modules = new Map()
13
13
 
14
- return `\
15
- import runtime from 'bare-stow/runtime'
14
+ if (server) {
15
+ modules.set(
16
+ serverURL.href,
17
+ server.generate({ role: 'server', options: { module: 'esm' } }).source
18
+ )
19
+ }
16
20
 
21
+ modules.set(
22
+ url.href,
23
+ `\
24
+ import runtime from '${isolation === 'process' ? 'bare-stow-runtime/process' : 'bare-stow-runtime'}'
25
+ ${server ? `import * as server from ${JSON.stringify(javascript.reference(url, serverURL))}\n` : ''}
17
26
  const ipc = runtime.attach(Bare.IPC)
18
- ${server ? '\n' + server : ''}
19
- ipc.run(() => import(${JSON.stringify(specifier)}), ${arg})
27
+ ${server ? '\nconst rpc = server.attach(ipc)\n' : ''}
28
+ ipc.run(() => import(${JSON.stringify(javascript.reference(url, entry))}), ${server ? '{ rpc, ipc }' : 'ipc'})
20
29
  `
21
- }
22
-
23
- exports.url = function url(base) {
24
- return new URL('__main__.mjs', base)
25
- }
26
-
27
- function relativeSpecifier(from, to) {
28
- if (from.protocol === 'file:' && to.protocol === 'file:') {
29
- const fromPath = path.dirname(fileURLToPath(from))
30
- const toPath = fileURLToPath(to)
31
-
32
- let relative = path.relative(fromPath, toPath).split(path.sep).join('/')
33
-
34
- if (!relative.startsWith('.')) relative = './' + relative
35
-
36
- return relative
37
- }
30
+ )
38
31
 
39
- return to.href
32
+ return { url, modules }
40
33
  }
@@ -0,0 +1,238 @@
1
+ const path = require('path')
2
+ const swift = require('./swift')
3
+
4
+ exports.name = 'bare-kit'
5
+ exports.isolation = 'thread'
6
+ exports.linked = (context) => !isJvm(context)
7
+ exports.linking = linking
8
+ exports.offload = isJvm
9
+ exports.format = 'bundle'
10
+ exports.encoding = null
11
+ exports.extension = '.bundle'
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
+ }
30
+
31
+ // Applications with several bundles link their addons once with `link()`.
32
+ exports.options = function options(given = {}) {
33
+ const { linking = 'package', ...unknown } = given
34
+
35
+ for (const option of Object.keys(unknown)) {
36
+ throw new Error(`Unknown option '${option}' for target 'bare-kit'`)
37
+ }
38
+
39
+ if (linking !== 'package' && linking !== 'app') {
40
+ throw new Error(`Option 'linking' must be 'package' or 'app', got '${linking}'`)
41
+ }
42
+
43
+ return { linking }
44
+ }
45
+
46
+ const bareKitSwift = {
47
+ url: 'https://github.com/holepunchto/bare-kit-swift',
48
+ from: '2.2.0',
49
+ products: ['BareKit']
50
+ }
51
+
52
+ const bareKitKotlin = {
53
+ 'com.github.holepunchto.bare-kit-kotlin:bare-kit-kotlin': 'v0.2.2'
54
+ }
55
+
56
+ const desktop = [
57
+ 'darwin-arm64',
58
+ 'darwin-x64',
59
+ 'linux-arm64',
60
+ 'linux-x64',
61
+ 'win32-arm64',
62
+ 'win32-x64'
63
+ ]
64
+
65
+ const hosts = {
66
+ kotlin: ['android-arm64', 'android-arm', 'android-x64', 'android-ia32'],
67
+ swift: ['darwin-arm64', 'darwin-x64', 'ios-arm64', 'ios-arm64-simulator', 'ios-x64-simulator']
68
+ }
69
+
70
+ exports.languages = {
71
+ kotlin: {
72
+ generate: kotlin,
73
+ link() {
74
+ return { source: null, dependencies: bareKitKotlin }
75
+ }
76
+ },
77
+ swift: {
78
+ // Re-exports Bare Kit, so that code linking the module can run worklets.
79
+ link() {
80
+ return {
81
+ source: '@_exported import BareKit\n',
82
+ dependencies: { 'bare-kit-swift': bareKitSwift }
83
+ }
84
+ },
85
+ generate(context) {
86
+ const start = context.resources
87
+ ? `try worklet.start(${JSON.stringify(context.resources + '.bundle')}, source: Data(contentsOf: bundle))`
88
+ : 'try worklet.start(bundle)'
89
+
90
+ return swift(context, {
91
+ module: 'BareKit',
92
+ conformance: 'extension BareKit.IPC: @retroactive BareStowHost.Transport {}',
93
+ fields: { worklet: 'Worklet' },
94
+ params: ['options: Worklet.Options = Worklet.Options()'],
95
+ setup: `\
96
+ let worklet = try Worklet(options: options)
97
+
98
+ let transport = try BareKit.IPC(worklet: worklet)
99
+
100
+ ${start}
101
+
102
+ let ipc = BareStowHost.IPC(
103
+ transport, kill: { worklet.terminate() }, timeout: timeout, grace: grace)
104
+
105
+ Task { ipc.exited(code: await worklet.wait()) }`,
106
+ dependencies: { 'bare-kit-swift': bareKitSwift }
107
+ })
108
+ }
109
+ }
110
+ }
111
+
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()
128
+
129
+ const source = `\
130
+ package ${options.package}
131
+
132
+ ${imports.map((name) => 'import ' + name).join('\n')}
133
+
134
+ class Started(val worklet: Worklet, val ipc: Ipc${client ? ', val rpc: Rpc' : ''})
135
+ ${platform.declarations}
136
+ suspend fun start(
137
+ ${platform.params}\
138
+ options: Worklet.Options = Worklet.Options(),
139
+ timeout: Duration? = null,
140
+ grace: Duration = Ipc.DEFAULT_GRACE,${client ? '\n handlers: Handlers = {},' : ''}
141
+ ): Started {
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
+ }
151
+
152
+ try {
153
+ ${platform.start}
154
+ } catch (error: Throwable) {
155
+ transport.close()
156
+ worklet.close()
157
+ throw error
158
+ }
159
+
160
+ val ipc =
161
+ Ipc(
162
+ transport.data,
163
+ transport::write,
164
+ kill = { worklet.terminate() },
165
+ timeout = timeout,
166
+ grace = grace,
167
+ )
168
+
169
+ CoroutineScope(Dispatchers.Default).launch {
170
+ ipc.exited(worklet.wait())
171
+
172
+ transport.close()
173
+ worklet.close()
174
+ }
175
+ ${client ? '\n val rpc = attach(ipc, handlers)\n' : ''}
176
+ ipc.ready()
177
+
178
+ return Started(worklet, ipc${client ? ', rpc' : ''})
179
+ }
180
+ `
181
+
182
+ return {
183
+ source,
184
+ dependencies: {
185
+ ...bareKitKotlin,
186
+ 'com.github.holepunchto.bare-stow-host-kotlin:bare-stow-host-kotlin': 'v0.2.1'
187
+ }
188
+ }
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
+ }
@@ -1,4 +1,8 @@
1
+ const harness = require('./javascript')
2
+ const swift = require('./swift')
3
+
1
4
  exports.name = 'bare-sidecar'
5
+ exports.isolation = 'process'
2
6
  exports.linked = false
3
7
  exports.offload = true
4
8
  exports.format = 'bundle'
@@ -13,74 +17,103 @@ exports.hosts = [
13
17
  'win32-x64'
14
18
  ]
15
19
 
16
- exports.generate = function generate(opts) {
17
- const { bundleSpecifier, ipc, rpc, module = 'cjs', client = null } = opts
20
+ exports.languages = {
21
+ javascript: {
22
+ generate(context) {
23
+ return harness(context, {
24
+ name: 'bare-sidecar',
25
+ version: '^0.5.15',
26
+ binding: 'Sidecar',
27
+ url: true,
28
+ setup: `\
29
+ const sidecar = new Sidecar(bundle, { stdio: 'inherit', ...opts })
30
+
31
+ const ipc = stow.wrap(sidecar, { kill: () => sidecar.kill('SIGKILL'), timeout, grace })
32
+
33
+ sidecar.on('exit', (code, signal) => ipc.exited(code, signal))`,
34
+ types: "import('bare-sidecar').SidecarOptions"
35
+ })
36
+ }
37
+ },
38
+ python: { generate: python },
39
+ swift: {
40
+ generate(context) {
41
+ return swift(context, {
42
+ module: 'BareSidecar',
43
+ conformance: 'extension Sidecar: @retroactive BareStowHost.Transport {}',
44
+ setup: `\
45
+ let sidecar = try await Sidecar.spawn(bundle)
46
+
47
+ let ipc = BareStowHost.IPC(sidecar, kill: { sidecar.kill() }, timeout: timeout, grace: grace)
48
+
49
+ Task {
50
+ switch await sidecar.wait() {
51
+ case .exit(let code): ipc.exited(code: code)
52
+ case .signal(let signal): ipc.exited(code: nil, signal: signal)
53
+ }
54
+ }`,
55
+ dependencies: {
56
+ 'bare-sidecar-swift': {
57
+ url: 'https://github.com/holepunchto/bare-sidecar-swift',
58
+ from: '0.1.2',
59
+ products: ['BareSidecar']
60
+ }
61
+ }
62
+ })
63
+ }
64
+ }
65
+ }
18
66
 
19
- const esm = module === 'esm'
67
+ function python({ bundle, client }) {
68
+ const source = `\
69
+ import asyncio
70
+ from pathlib import Path
71
+ from typing import NamedTuple
20
72
 
21
- const returned = client ? `{ ${ipc}, ${rpc} }` : `{ ${ipc} }`
73
+ ${client ? 'from bare_rpc import RPC\n' : ''}from bare_sidecar import spawn
74
+ from bare_stow_host import DEFAULT_GRACE, IPC, wrap
75
+ ${client ? `\nfrom ${client} import Handlers, attach\n` : ''}
76
+ bundle = Path(__file__).parent / ${JSON.stringify(bundle)}
22
77
 
23
- const handle = client
24
- ? `{ ${ipc}: import('bare-stow/host').IPC; ${rpc}: ${client.type} }`
25
- : `{ ${ipc}: import('bare-stow/host').IPC }`
78
+ _watching = set()
26
79
 
27
- const source = esm
28
- ? `\
29
- import Sidecar from 'bare-sidecar'
30
- import stow from 'bare-stow/host'
31
80
 
32
- const bundle = new URL(import.meta.resolve(${JSON.stringify(bundleSpecifier)}))
81
+ class Started(NamedTuple):
82
+ ipc: IPC${client ? '\n rpc: RPC' : ''}
33
83
 
34
- export async function start({ timeout, grace, ...opts } = {}) {
35
- const sidecar = new Sidecar(bundle, { stdio: 'inherit', ...opts })
36
84
 
37
- const ${ipc} = stow.wrap(sidecar, { kill: () => sidecar.kill('SIGKILL'), timeout, grace })
85
+ async def start(
86
+ *,
87
+ timeout=None,
88
+ grace=DEFAULT_GRACE,${client ? '\n handlers: Handlers = lambda router: None,' : ''}
89
+ **opts,
90
+ ) -> Started:
91
+ sidecar = await spawn(bundle, **opts)
38
92
 
39
- sidecar.on('exit', (code, signal) => ${ipc}.exited(code, signal))
40
- ${client ? '\n' + indent(client.source, 2) : ''}
41
- await ${ipc}.ready
93
+ ipc = wrap(sidecar, kill=sidecar.kill, timeout=timeout, grace=grace)
42
94
 
43
- return ${returned}
44
- }
45
- `
46
- : `\
47
- const Sidecar = require('bare-sidecar')
48
- const stow = require('bare-stow/host')
95
+ watching = asyncio.ensure_future(_watch(sidecar, ipc))
96
+ _watching.add(watching)
97
+ watching.add_done_callback(_watching.discard)
98
+ ${client ? '\n rpc = attach(ipc, handlers)\n' : ''}
99
+ await ipc.ready
49
100
 
50
- const bundle = require.resolve(${JSON.stringify(bundleSpecifier)})
101
+ return Started(ipc${client ? ', rpc' : ''})
51
102
 
52
- module.exports = {
53
- async start({ timeout, grace, ...opts } = {}) {
54
- const sidecar = new Sidecar(bundle, { stdio: 'inherit', ...opts })
55
103
 
56
- const ${ipc} = stow.wrap(sidecar, { kill: () => sidecar.kill('SIGKILL'), timeout, grace })
104
+ async def _watch(sidecar, ipc):
105
+ await sidecar.wait()
57
106
 
58
- sidecar.on('exit', (code, signal) => ${ipc}.exited(code, signal))
59
- ${client ? '\n' + indent(client.source, 4) : ''}
60
- await ${ipc}.ready
107
+ ipc.exited(sidecar.exit_code, sidecar.signal_code)
61
108
 
62
- return ${returned}
63
- }
64
- }
109
+ await sidecar.close()
65
110
  `
66
111
 
67
- return [
68
- { source },
69
- {
70
- extension: '.d.ts',
71
- source: `\
72
- export function start(
73
- opts?: import('bare-sidecar').SidecarOptions & Omit<import('bare-stow/host').IPCOptions, 'kill'>
74
- ): Promise<${handle}>
75
- `
112
+ return {
113
+ source,
114
+ dependencies: {
115
+ 'bare-sidecar': '~=0.1.3',
116
+ 'bare-stow-host': '~=0.2.0'
76
117
  }
77
- ]
78
- }
79
-
80
- function indent(source, spaces) {
81
- const pad = ' '.repeat(spaces)
82
- return source
83
- .split('\n')
84
- .map((line) => (line.length === 0 ? line : pad + line))
85
- .join('\n')
118
+ }
86
119
  }