bare-stow 0.1.9 → 0.2.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/README.md +90 -111
- package/bin.js +142 -53
- package/index.d.ts +301 -81
- package/index.js +132 -204
- package/lib/assets.js +54 -0
- package/lib/fs.js +2 -4
- package/lib/graph.js +96 -0
- package/lib/language/javascript.js +74 -0
- package/lib/language/kotlin.js +109 -0
- package/lib/language/python.js +65 -0
- package/lib/language/swift.js +111 -0
- package/lib/language.js +18 -0
- package/lib/link.js +141 -0
- package/lib/provider.js +33 -0
- package/lib/rpc/bare-rpc.js +220 -15
- package/lib/rpc.js +6 -18
- package/lib/shim.js +24 -31
- package/lib/target/bare-kit.js +144 -0
- package/lib/target/bare-sidecar.js +86 -53
- package/lib/target/bare-worker.js +18 -72
- package/lib/target/javascript.js +63 -0
- package/lib/target/swift.js +57 -0
- package/lib/target.js +43 -12
- package/package.json +5 -21
- package/lib/host.d.ts +0 -64
- package/lib/host.js +0 -114
- package/lib/protocol.d.ts +0 -46
- package/lib/protocol.js +0 -139
- package/lib/runtime.d.ts +0 -29
- package/lib/runtime.js +0 -92
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
|
|
3
|
+
exports.name = 'kotlin'
|
|
4
|
+
|
|
5
|
+
exports.options = function options(given = {}) {
|
|
6
|
+
const { package: name, ...unknown } = given
|
|
7
|
+
|
|
8
|
+
for (const option of Object.keys(unknown)) {
|
|
9
|
+
throw new Error(`Unknown option '${option}' for language 'kotlin'`)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (name === undefined) throw new Error("Option 'package' is required for language 'kotlin'")
|
|
13
|
+
|
|
14
|
+
if (!/^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)*$/.test(name)) {
|
|
15
|
+
throw new Error(`'${name}' is not a valid Kotlin package name`)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return { package: name }
|
|
19
|
+
}
|
|
20
|
+
|
|
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.
|
|
23
|
+
exports.layout = function layout(out, options) {
|
|
24
|
+
const dir = options.package.replace(/\./g, '/')
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
harness: new URL(`src/main/kotlin/${dir}/Harness.kt`, out),
|
|
28
|
+
artifacts: new URL(`src/main/assets/${dir}/`, out),
|
|
29
|
+
name: options.package,
|
|
30
|
+
manifest: new URL('build.gradle', out),
|
|
31
|
+
addons: new URL('src/main/addons/', out),
|
|
32
|
+
resources: new URL('src/main/assets/', out)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
exports.sibling = function sibling(url, name) {
|
|
37
|
+
return new URL(capitalize(name) + '.kt', url)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Assets are referenced by their path within the assets of the application.
|
|
41
|
+
exports.reference = function reference(from, to) {
|
|
42
|
+
const assets = to.pathname.lastIndexOf('/src/main/assets/')
|
|
43
|
+
|
|
44
|
+
if (assets !== -1) return to.pathname.slice(assets + '/src/main/assets/'.length)
|
|
45
|
+
|
|
46
|
+
return path.posix.relative(path.posix.dirname(from.pathname), to.pathname)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// The dependencies of Kotlin generators map Maven coordinates without a version
|
|
50
|
+
// to the version that the module uses. They are part of the API of the module,
|
|
51
|
+
// as the harness exposes their types.
|
|
52
|
+
exports.manifest = function manifest({ name, dependencies, addons = [] }) {
|
|
53
|
+
const apis = Object.entries(dependencies).map(
|
|
54
|
+
([coordinates, version]) => ` api ${JSON.stringify(coordinates + ':' + version)}`
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
// Linked addons are native libraries in a directory per ABI, along with the
|
|
58
|
+
// archives of any Java classes they use.
|
|
59
|
+
const libraries = [
|
|
60
|
+
...new Set(
|
|
61
|
+
addons
|
|
62
|
+
.filter((addon) => addon.endsWith('.so'))
|
|
63
|
+
.map((addon) => path.posix.dirname(path.posix.dirname(addon)))
|
|
64
|
+
)
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
for (const addon of addons) {
|
|
68
|
+
if (addon.endsWith('.jar')) apis.push(` api files(${JSON.stringify(addon)})`)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const sourceSets = libraries.length
|
|
72
|
+
? `
|
|
73
|
+
|
|
74
|
+
sourceSets {
|
|
75
|
+
main {
|
|
76
|
+
${libraries.map((dir) => ` jniLibs.directories.add(${JSON.stringify(dir)})`).join('\n')}
|
|
77
|
+
}
|
|
78
|
+
}`
|
|
79
|
+
: ''
|
|
80
|
+
|
|
81
|
+
// Projects that opt out of the Kotlin support built into AGP 9, such as those
|
|
82
|
+
// of Expo, need the Kotlin plugin to compile the harness.
|
|
83
|
+
return `\
|
|
84
|
+
plugins {
|
|
85
|
+
id "com.android.library"
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (findProperty("android.builtInKotlin") == "false") {
|
|
89
|
+
apply plugin: "org.jetbrains.kotlin.android"
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
android {
|
|
93
|
+
namespace = ${JSON.stringify(name)}
|
|
94
|
+
compileSdk = 36
|
|
95
|
+
|
|
96
|
+
defaultConfig {
|
|
97
|
+
minSdk = 29
|
|
98
|
+
}${sourceSets}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
dependencies {
|
|
102
|
+
${apis.join('\n')}
|
|
103
|
+
}
|
|
104
|
+
`
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function capitalize(name) {
|
|
108
|
+
return name.charAt(0).toUpperCase() + name.slice(1)
|
|
109
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
|
|
3
|
+
exports.name = 'python'
|
|
4
|
+
|
|
5
|
+
exports.options = function options(given = {}) {
|
|
6
|
+
for (const name of Object.keys(given)) {
|
|
7
|
+
throw new Error(`Unknown option '${name}' for language 'python'`)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return {}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
exports.layout = function layout(out) {
|
|
14
|
+
const name = path.posix.basename(out.pathname).replace(/-/g, '_')
|
|
15
|
+
|
|
16
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
|
|
17
|
+
throw new Error(`'${name}' is not a valid Python package name`)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
harness: new URL(`${name}/__init__.py`, out),
|
|
22
|
+
name,
|
|
23
|
+
manifest: new URL('pyproject.toml', out)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
exports.sibling = function sibling(url, name) {
|
|
28
|
+
return new URL(`${name}.py`, url)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
exports.reference = function reference(from, to) {
|
|
32
|
+
const relative = path.posix.relative(path.posix.dirname(from.pathname), to.pathname)
|
|
33
|
+
|
|
34
|
+
if (relative.endsWith('.py') && !relative.includes('/')) {
|
|
35
|
+
return '.' + path.posix.basename(relative, '.py')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return relative
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
exports.manifest = function manifest({ name, dependencies, artifacts }) {
|
|
42
|
+
const requirements = Object.entries(dependencies).map(
|
|
43
|
+
([dependency, version]) => ` ${JSON.stringify(dependency + version)},`
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
// The bundle is listed as an artifact so that it is included in the wheel
|
|
47
|
+
// even when ignored by version control.
|
|
48
|
+
return `\
|
|
49
|
+
[build-system]
|
|
50
|
+
requires = ["hatchling"]
|
|
51
|
+
build-backend = "hatchling.build"
|
|
52
|
+
|
|
53
|
+
[project]
|
|
54
|
+
name = ${JSON.stringify(name)}
|
|
55
|
+
version = "0.0.0"
|
|
56
|
+
requires-python = ">=3.10"
|
|
57
|
+
dependencies = [
|
|
58
|
+
${requirements.join('\n')}
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
[tool.hatch.build.targets.wheel]
|
|
62
|
+
packages = [${JSON.stringify(name)}]
|
|
63
|
+
artifacts = [${artifacts.map((artifact) => JSON.stringify(artifact)).join(', ')}]
|
|
64
|
+
`
|
|
65
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
|
|
3
|
+
exports.name = 'swift'
|
|
4
|
+
|
|
5
|
+
exports.options = function options(given = {}) {
|
|
6
|
+
for (const name of Object.keys(given)) {
|
|
7
|
+
throw new Error(`Unknown option '${name}' for language 'swift'`)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return {}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
exports.layout = function layout(out) {
|
|
14
|
+
const name = path.posix.basename(out.pathname).split(/[-_.]/).map(capitalize).join('')
|
|
15
|
+
|
|
16
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
|
|
17
|
+
throw new Error(`'${name}' is not a valid Swift module name`)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
harness: new URL(`Sources/${name}/Harness.swift`, out),
|
|
22
|
+
name,
|
|
23
|
+
manifest: new URL('Package.swift', out),
|
|
24
|
+
addons: new URL('Frameworks/', out),
|
|
25
|
+
resources: out
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
exports.sibling = function sibling(url, name) {
|
|
30
|
+
return new URL(capitalize(name) + '.swift', url)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
exports.reference = function reference(from, to) {
|
|
34
|
+
return path.posix.relative(path.posix.dirname(from.pathname), to.pathname)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// The oldest versions of the Apple platforms that the Swift runtimes support.
|
|
38
|
+
const platforms = {
|
|
39
|
+
darwin: '.macOS(.v13)',
|
|
40
|
+
ios: '.iOS(.v15)'
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// The dependencies of Swift generators map package identities to the URL, the
|
|
44
|
+
// minimum version, and the products of the package that the module uses. Like
|
|
45
|
+
// npm, a minor release of a package before 1.0.0 may break compatibility.
|
|
46
|
+
exports.manifest = function manifest({ name, dependencies, hosts, artifacts, addons = [] }) {
|
|
47
|
+
const supported = Object.entries(platforms)
|
|
48
|
+
.filter(([platform]) => hosts.some((host) => host.startsWith(platform + '-')))
|
|
49
|
+
.map(([, minimum]) => minimum)
|
|
50
|
+
|
|
51
|
+
const packages = Object.values(dependencies).map(({ url, from }) => {
|
|
52
|
+
const version = from.startsWith('0.')
|
|
53
|
+
? `.upToNextMinor(from: ${JSON.stringify(from)})`
|
|
54
|
+
: `from: ${JSON.stringify(from)}`
|
|
55
|
+
|
|
56
|
+
return ` .package(url: ${JSON.stringify(url)}, ${version}),`
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
const products = Object.entries(dependencies).flatMap(([identity, { products }]) =>
|
|
60
|
+
products.map(
|
|
61
|
+
(product) =>
|
|
62
|
+
` .product(name: ${JSON.stringify(product)}, package: ${JSON.stringify(identity)}),`
|
|
63
|
+
)
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
// Linked addons are binary targets that the module depends on.
|
|
67
|
+
const frameworks = addons
|
|
68
|
+
.filter((addon) => addon.endsWith('.xcframework'))
|
|
69
|
+
.map((addon) => ({ name: path.posix.basename(addon, '.xcframework'), path: addon }))
|
|
70
|
+
|
|
71
|
+
for (const framework of frameworks) products.push(` ${JSON.stringify(framework.name)},`)
|
|
72
|
+
|
|
73
|
+
const binaries = frameworks.map(
|
|
74
|
+
(framework) =>
|
|
75
|
+
`,\n .binaryTarget(name: ${JSON.stringify(framework.name)}, path: ${JSON.stringify(framework.path)})`
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
// Resources are named relative to the directory of the module.
|
|
79
|
+
const resources = artifacts.map(
|
|
80
|
+
(artifact) => `.copy(${JSON.stringify(path.posix.relative(`Sources/${name}`, artifact))})`
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
return `\
|
|
84
|
+
// swift-tools-version: 5.10
|
|
85
|
+
import PackageDescription
|
|
86
|
+
|
|
87
|
+
let package = Package(
|
|
88
|
+
name: ${JSON.stringify(name)},
|
|
89
|
+
platforms: [${supported.join(', ')}],
|
|
90
|
+
products: [
|
|
91
|
+
.library(name: ${JSON.stringify(name)}, targets: [${JSON.stringify(name)}])
|
|
92
|
+
],
|
|
93
|
+
dependencies: [
|
|
94
|
+
${packages.join('\n')}
|
|
95
|
+
],
|
|
96
|
+
targets: [
|
|
97
|
+
.target(
|
|
98
|
+
name: ${JSON.stringify(name)},
|
|
99
|
+
dependencies: [
|
|
100
|
+
${products.join('\n')}
|
|
101
|
+
],
|
|
102
|
+
resources: [${resources.join(', ')}]
|
|
103
|
+
)${binaries.join('')}
|
|
104
|
+
]
|
|
105
|
+
)
|
|
106
|
+
`
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function capitalize(name) {
|
|
110
|
+
return name.charAt(0).toUpperCase() + name.slice(1)
|
|
111
|
+
}
|
package/lib/language.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const { registry } = require('./provider')
|
|
2
|
+
|
|
3
|
+
const providers = {
|
|
4
|
+
javascript: () => require('./language/javascript'),
|
|
5
|
+
kotlin: () => require('./language/kotlin'),
|
|
6
|
+
python: () => require('./language/python'),
|
|
7
|
+
swift: () => require('./language/swift')
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
module.exports = registry('language', providers, function validate(language) {
|
|
11
|
+
for (const method of ['options', 'layout', 'sibling', 'reference', 'manifest']) {
|
|
12
|
+
if (typeof language?.[method] !== 'function') {
|
|
13
|
+
throw new Error(`Language must provide a ${method}() function`)
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return language
|
|
18
|
+
})
|
package/lib/link.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
const pack = require('bare-pack')
|
|
2
|
+
const fs = require('./fs')
|
|
3
|
+
const assets = require('./assets')
|
|
4
|
+
const shim = require('./shim')
|
|
5
|
+
const graph = require('./graph')
|
|
6
|
+
const rpcs = require('./rpc')
|
|
7
|
+
|
|
8
|
+
module.exports = async function* link(entries, target, out, opts = {}) {
|
|
9
|
+
if (!target) throw new Error("'target' is required")
|
|
10
|
+
if (!out) throw new Error("'out' is required")
|
|
11
|
+
|
|
12
|
+
let {
|
|
13
|
+
language,
|
|
14
|
+
languageOptions,
|
|
15
|
+
targetOptions,
|
|
16
|
+
server,
|
|
17
|
+
hosts,
|
|
18
|
+
resolveTarget,
|
|
19
|
+
resolveRPC,
|
|
20
|
+
resolveLanguage,
|
|
21
|
+
...packOpts
|
|
22
|
+
} = opts
|
|
23
|
+
|
|
24
|
+
entries = await Promise.all([].concat(entries).map((entry) => fs.resolveModule(new URL(entry))))
|
|
25
|
+
|
|
26
|
+
if (entries.length === 0) throw new Error("'entries' must not be empty")
|
|
27
|
+
out = new URL(out)
|
|
28
|
+
|
|
29
|
+
if (!out.pathname.endsWith('/')) out.pathname += '/'
|
|
30
|
+
|
|
31
|
+
const resolved = graph.providers(target, {
|
|
32
|
+
language,
|
|
33
|
+
languageOptions,
|
|
34
|
+
targetOptions,
|
|
35
|
+
hosts,
|
|
36
|
+
resolveTarget,
|
|
37
|
+
resolveLanguage
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
const { harness, options } = resolved
|
|
41
|
+
|
|
42
|
+
target = resolved.target
|
|
43
|
+
language = resolved.language
|
|
44
|
+
hosts = resolved.hosts
|
|
45
|
+
|
|
46
|
+
if (!target.linked) throw new Error(`Target '${target.name}' does not link addons`)
|
|
47
|
+
|
|
48
|
+
if (typeof harness.link !== 'function') {
|
|
49
|
+
throw new Error(
|
|
50
|
+
`Target '${target.name}' does not support linking for language '${language.name}'`
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (server) server = rpcs(server, resolveRPC)
|
|
55
|
+
|
|
56
|
+
const layout = language.layout(out, options)
|
|
57
|
+
|
|
58
|
+
if (!layout.addons) throw new Error(`Language '${language.name}' does not support linked addons`)
|
|
59
|
+
|
|
60
|
+
// Every shim imports the same runtime and RPC server, so one covers them all.
|
|
61
|
+
const main = shim(entries[0], target.isolation, server)
|
|
62
|
+
|
|
63
|
+
const traversal = graph.traversal(packOpts, hosts)
|
|
64
|
+
const readModule = graph.readModule(main.modules)
|
|
65
|
+
|
|
66
|
+
const addons = []
|
|
67
|
+
|
|
68
|
+
for await (const url of graph.link(
|
|
69
|
+
[main.url, ...entries.slice(1)],
|
|
70
|
+
layout.addons,
|
|
71
|
+
traversal,
|
|
72
|
+
readModule
|
|
73
|
+
)) {
|
|
74
|
+
addons.push(url)
|
|
75
|
+
yield { url }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const linked = await collectAssets(entries, traversal, readModule)
|
|
79
|
+
|
|
80
|
+
if (linked.length > 0) {
|
|
81
|
+
if (!layout.resources) {
|
|
82
|
+
throw new Error(`Language '${language.name}' does not support linked assets`)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
for await (const url of assets.write(layout.resources, assets.app, linked)) yield { url }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const generated = harness.link({ options })
|
|
89
|
+
|
|
90
|
+
const files = []
|
|
91
|
+
|
|
92
|
+
if (generated.source) files.push({ url: layout.harness, source: generated.source })
|
|
93
|
+
|
|
94
|
+
files.push({
|
|
95
|
+
url: layout.manifest,
|
|
96
|
+
source: language.manifest({
|
|
97
|
+
name: layout.name,
|
|
98
|
+
dependencies: generated.dependencies || {},
|
|
99
|
+
options,
|
|
100
|
+
hosts,
|
|
101
|
+
artifacts: [],
|
|
102
|
+
addons: addons.map((url) => language.reference(layout.manifest, url))
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
for (const file of files) {
|
|
107
|
+
await fs.writeFile(file.url, file.source)
|
|
108
|
+
yield { url: file.url }
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function collectAssets(entries, traversal, readModule) {
|
|
113
|
+
const collected = new Map()
|
|
114
|
+
|
|
115
|
+
for (const entry of entries) {
|
|
116
|
+
const bundle = await pack(
|
|
117
|
+
entry,
|
|
118
|
+
{ ...traversal, linked: true },
|
|
119
|
+
readModule,
|
|
120
|
+
fs.listPrefix,
|
|
121
|
+
fs.resolveModule
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
for (const key of bundle.assets) {
|
|
125
|
+
const url = new URL(key)
|
|
126
|
+
const relative = await assets.path(url)
|
|
127
|
+
const source = bundle.read(key)
|
|
128
|
+
const existing = collected.get(relative)
|
|
129
|
+
|
|
130
|
+
if (existing && !existing.source.equals(source)) {
|
|
131
|
+
throw new Error(
|
|
132
|
+
`Assets '${existing.url.href}' and '${url.href}' both link as '${relative}'`
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
collected.set(relative, { path: relative, url, source })
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return [...collected.values()]
|
|
141
|
+
}
|
package/lib/provider.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
exports.registry = function registry(kind, providers, validate) {
|
|
2
|
+
return function provider(name, resolve) {
|
|
3
|
+
if (typeof name !== 'string') return validate(name)
|
|
4
|
+
|
|
5
|
+
const load = providers[name]
|
|
6
|
+
|
|
7
|
+
if (load) return load()
|
|
8
|
+
|
|
9
|
+
if (resolve) return validate(resolve(name))
|
|
10
|
+
|
|
11
|
+
throw new Error(`Unknown ${kind} '${name}'`)
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
exports.validateLanguages = function validateLanguages(kind, languages) {
|
|
16
|
+
if (typeof languages !== 'object' || languages === null) {
|
|
17
|
+
throw new Error(`${kind} must provide a languages object`)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
for (const [name, entry] of Object.entries(languages)) {
|
|
21
|
+
if (!entry || typeof entry.generate !== 'function') {
|
|
22
|
+
throw new Error(`${kind} must provide a generate() function for language '${name}'`)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
exports.select = function select(kind, provider, language) {
|
|
28
|
+
if (!Object.hasOwn(provider.languages, language)) {
|
|
29
|
+
throw new Error(`${kind} '${provider.name}' does not support language '${language}'`)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return provider.languages[language]
|
|
33
|
+
}
|