peachsim 0.1.674 → 0.1.675
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/dist-lib/agent-daemon-client.cjs +1 -1
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/agent-identity.cjs +1 -1
- package/dist-lib/agent-sessions.cjs +1 -1
- package/dist-lib/attached-projects.cjs +1 -1
- package/dist-lib/auth/shared-session.cjs +1 -1
- package/dist-lib/backend-origin.cjs +1 -1
- package/dist-lib/beta.cjs +1 -1
- package/dist-lib/beta.mjs +1 -1
- package/dist-lib/bridge-constants.cjs +1 -1
- package/dist-lib/bridge-contract-input.cjs +1 -1
- package/dist-lib/bridge-contract-input.mjs +1 -1
- package/dist-lib/bridge-contract.cjs +1 -1
- package/dist-lib/bridge-contract.mjs +1 -1
- package/dist-lib/capture-contract.cjs +1 -1
- package/dist-lib/capture-contract.mjs +1 -1
- package/dist-lib/cli-constants.cjs +1 -1
- package/dist-lib/cloud-contract.cjs +1 -1
- package/dist-lib/cloud-contract.mjs +1 -1
- package/dist-lib/cloud-test-args.cjs +1 -1
- package/dist-lib/cloud-test-args.mjs +1 -1
- package/dist-lib/cloud.cjs +1 -1
- package/dist-lib/cloud.mjs +1 -1
- package/dist-lib/config.cjs +1 -1
- package/dist-lib/detox/index.cjs +1 -1
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/home-paths.cjs +1 -1
- package/dist-lib/host/bridge-host.cjs +1 -1
- package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
- package/dist-lib/host/replacement-module-handler.cjs +1 -1
- package/dist-lib/host/websocket-proxy.cjs +1 -1
- package/dist-lib/index.cjs +1 -1
- package/dist-lib/jump-to-source-babel.cjs +1 -1
- package/dist-lib/jump-to-source-native.cjs +1 -1
- package/dist-lib/kotlin-host.cjs +78 -2
- package/dist-lib/menu.cjs +1 -1
- package/dist-lib/menu.mjs +1 -1
- package/dist-lib/metro-fingerprint-registry.cjs +1 -1
- package/dist-lib/metro-fingerprint-registry.mjs +1 -1
- package/dist-lib/metro-production-bundle.cjs +1 -1
- package/dist-lib/metro-production-bundle.mjs +1 -1
- package/dist-lib/metro.cjs +1 -1
- package/dist-lib/native-app-identity.cjs +1 -1
- package/dist-lib/native-app-identity.mjs +1 -1
- package/dist-lib/native-firebase-metadata.cjs +1 -1
- package/dist-lib/native-firebase-metadata.mjs +1 -1
- package/dist-lib/plist.cjs +1 -1
- package/dist-lib/plist.mjs +1 -1
- package/dist-lib/profiles.cjs +1 -1
- package/dist-lib/public-brand.cjs +1 -1
- package/dist-lib/react-native-host-modules.cjs +1 -1
- package/dist-lib/react-native-host-modules.mjs +1 -1
- package/dist-lib/render-mode.cjs +1 -1
- package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
- package/dist-lib/sdk.cjs +1 -1
- package/dist-lib/sdk.mjs +1 -1
- package/dist-lib/skills.cjs +1 -1
- package/dist-lib/swift-compile.cjs +1 -1
- package/dist-lib/swift-compile.mjs +1 -1
- package/dist-lib/swift-package.cjs +1 -1
- package/dist-lib/swift-package.mjs +1 -1
- package/dist-lib/swift-submit.cjs +1 -1
- package/dist-lib/swift-submit.mjs +1 -1
- package/dist-lib/swift.cjs +1 -1
- package/dist-lib/vite-swift.cjs +1 -1
- package/dist-lib/vite.cjs +1 -1
- package/kotlin/AppSources/android/content/Context.kt +4 -0
- package/kotlin/AppSources/android/content/res/Configuration.kt +4 -1
- package/kotlin/AppSources/android/content/res/Resources.kt +45 -1
- package/kotlin/AppSources/androidx/compose/ui/platform/AndroidCompositionLocals.kt +19 -0
- package/kotlin/AppSources/androidx/compose/ui/res/StringResources.kt +53 -0
- package/kotlin/AppSources/dev/peach/app/JavaFormat.kt +264 -0
- package/kotlin/AppSources/dev/peach/app/PeachApp.kt +23 -7
- package/kotlin/fixtures/kotlin-app-variants/Strings.kt +49 -0
- package/package.json +1 -1
- package/src/android-strings.ts +163 -0
- package/src/android-xml.ts +3 -3
- package/src/kotlin-app.ts +4 -0
- package/src/kotlin-host.ts +98 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
// an Android module's string resources as aapt2 compiles them: each
|
|
2
|
+
// <string>, <plurals> and <string-array> under res/values and its locale
|
|
3
|
+
// qualified directories, the text with its style tags removed (getString
|
|
4
|
+
// returns the unstyled text) and aapt2's StringBuilder rules applied. a
|
|
5
|
+
// qualifier Peach does not resolve yet, or a value that is a reference, is
|
|
6
|
+
// rejected by name, never read as the default or as text.
|
|
7
|
+
import fs from 'node:fs'
|
|
8
|
+
import path from 'node:path'
|
|
9
|
+
import { decodeXmlText } from './android-xml'
|
|
10
|
+
|
|
11
|
+
// one resource's value per locale: '' for values, then a language or a
|
|
12
|
+
// language and region as a BCP 47 tag (values-en-rGB is en-GB). the device
|
|
13
|
+
// resolves a whole plurals or array resource to one locale, never per item
|
|
14
|
+
export interface AndroidResource<Value> {
|
|
15
|
+
name: string
|
|
16
|
+
values: Record<string, Value>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type PluralQuantity = 'zero' | 'one' | 'two' | 'few' | 'many' | 'other'
|
|
20
|
+
|
|
21
|
+
export interface AndroidStringResources {
|
|
22
|
+
strings: AndroidResource<string>[]
|
|
23
|
+
plurals: AndroidResource<Partial<Record<PluralQuantity, string>>>[]
|
|
24
|
+
arrays: AndroidResource<string[]>[]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const VALUES_DIR = /^values(?:-([a-z]{2,3})(?:-r([A-Z]{2}))?)?$/
|
|
28
|
+
const ELEMENT =
|
|
29
|
+
/<(string|plurals|string-array)(?=[\s/>])([^>]*?)(?:\/>|>([\s\S]*?)<\/\1\s*>)/g
|
|
30
|
+
const ITEM = /<item(?=[\s/>])([^>]*?)(?:\/>|>([\s\S]*?)<\/item\s*>)/g
|
|
31
|
+
const NAME = /\bname\s*=\s*"([^"]*)"/
|
|
32
|
+
const QUANTITY = /\bquantity\s*=\s*"([^"]*)"/
|
|
33
|
+
const QUANTITIES: readonly PluralQuantity[] = [
|
|
34
|
+
'zero',
|
|
35
|
+
'one',
|
|
36
|
+
'two',
|
|
37
|
+
'few',
|
|
38
|
+
'many',
|
|
39
|
+
'other',
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
const isQuantity = (value: string): value is PluralQuantity =>
|
|
43
|
+
QUANTITIES.some((quantity) => quantity === value)
|
|
44
|
+
|
|
45
|
+
export function readAndroidStrings(moduleDir: string): AndroidStringResources {
|
|
46
|
+
const strings = new Map<string, Record<string, string>>()
|
|
47
|
+
const plurals = new Map<
|
|
48
|
+
string,
|
|
49
|
+
Record<string, Partial<Record<PluralQuantity, string>>>
|
|
50
|
+
>()
|
|
51
|
+
const arrays = new Map<string, Record<string, string[]>>()
|
|
52
|
+
const res = path.join(moduleDir, 'src/main/res')
|
|
53
|
+
const dirs = fs.existsSync(res) ? fs.readdirSync(res).sort() : []
|
|
54
|
+
for (const dir of dirs) {
|
|
55
|
+
if (!dir.startsWith('values')) continue
|
|
56
|
+
const qualifier = VALUES_DIR.exec(dir)
|
|
57
|
+
for (const entry of fs.readdirSync(path.join(res, dir)).sort()) {
|
|
58
|
+
if (!entry.endsWith('.xml')) continue
|
|
59
|
+
const file = path.join(res, dir, entry)
|
|
60
|
+
const text = fs.readFileSync(file, 'utf8').replace(/<!--[\s\S]*?-->/g, '')
|
|
61
|
+
for (const match of text.matchAll(ELEMENT)) {
|
|
62
|
+
const kind = match[1] ?? ''
|
|
63
|
+
const name = NAME.exec(match[2] ?? '')?.[1]
|
|
64
|
+
if (!name) throw new Error(`${file}: a <${kind}> without a name`)
|
|
65
|
+
if (!qualifier)
|
|
66
|
+
throw new Error(
|
|
67
|
+
`${file}: ${kind} ${name} under ${dir} is not resolved by peach kotlin yet (only values, values-<language> and values-<language>-r<REGION>)`,
|
|
68
|
+
)
|
|
69
|
+
const locale = qualifier[2]
|
|
70
|
+
? `${qualifier[1]}-${qualifier[2]}`
|
|
71
|
+
: (qualifier[1] ?? '')
|
|
72
|
+
const body = match[3] ?? ''
|
|
73
|
+
const where = `${file}: ${kind} ${name}`
|
|
74
|
+
if (kind === 'string')
|
|
75
|
+
add(strings, name, locale, compileString(body, where), where)
|
|
76
|
+
else if (kind === 'plurals') {
|
|
77
|
+
const items: Partial<Record<PluralQuantity, string>> = {}
|
|
78
|
+
for (const item of body.matchAll(ITEM)) {
|
|
79
|
+
const quantity = QUANTITY.exec(item[1] ?? '')?.[1] ?? ''
|
|
80
|
+
if (!isQuantity(quantity))
|
|
81
|
+
throw new Error(`${where} has an item with quantity "${quantity}"`)
|
|
82
|
+
if (quantity in items) throw new Error(`${where} defines ${quantity} twice`)
|
|
83
|
+
items[quantity] = compileString(item[2] ?? '', `${where} ${quantity}`)
|
|
84
|
+
}
|
|
85
|
+
add(plurals, name, locale, items, where)
|
|
86
|
+
} else
|
|
87
|
+
add(
|
|
88
|
+
arrays,
|
|
89
|
+
name,
|
|
90
|
+
locale,
|
|
91
|
+
[...body.matchAll(ITEM)].map((item, index) =>
|
|
92
|
+
compileString(item[2] ?? '', `${where} item ${index}`),
|
|
93
|
+
),
|
|
94
|
+
where,
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return { strings: sorted(strings), plurals: sorted(plurals), arrays: sorted(arrays) }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function add<Value>(
|
|
103
|
+
byName: Map<string, Record<string, Value>>,
|
|
104
|
+
name: string,
|
|
105
|
+
locale: string,
|
|
106
|
+
value: Value,
|
|
107
|
+
where: string,
|
|
108
|
+
) {
|
|
109
|
+
const values = byName.get(name) ?? {}
|
|
110
|
+
if (locale in values) throw new Error(`${where} is defined twice for one locale`)
|
|
111
|
+
values[locale] = value
|
|
112
|
+
byName.set(name, values)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// the R class code compiles against lists only the resources the default
|
|
116
|
+
// values define (AGP's ParseLibraryResourcesTask reads the unqualified
|
|
117
|
+
// directories), so a resource only a qualified directory defines has no field
|
|
118
|
+
const sorted = <Value>(
|
|
119
|
+
byName: Map<string, Record<string, Value>>,
|
|
120
|
+
): AndroidResource<Value>[] =>
|
|
121
|
+
[...byName.entries()]
|
|
122
|
+
.filter(([, values]) => '' in values)
|
|
123
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
|
|
124
|
+
.map(([name, values]) => ({ name, values }))
|
|
125
|
+
|
|
126
|
+
// aapt2's StringBuilder over the decoded text: outside double quotes a run of
|
|
127
|
+
// whitespace is one space and the ends are trimmed, an unescaped apostrophe is
|
|
128
|
+
// an error; a double quote toggles quoting and is dropped; a backslash escapes
|
|
129
|
+
// the next character (\n, \t and \uXXXX, any other character as itself). a
|
|
130
|
+
// value beginning with @ or ? is a reference aapt2 resolves, which Peach does
|
|
131
|
+
// not yet
|
|
132
|
+
function compileString(raw: string, where: string): string {
|
|
133
|
+
const text = decodeXmlText(raw.replace(/<[^>]*>/g, ''), where)
|
|
134
|
+
if (/^\s*[@?]/.test(text))
|
|
135
|
+
throw new Error(`${where} is a resource reference, not resolved by peach kotlin yet`)
|
|
136
|
+
const out: { char: string; soft: boolean }[] = []
|
|
137
|
+
let quoted = false
|
|
138
|
+
for (let index = 0; index < text.length; index++) {
|
|
139
|
+
const char = text[index] ?? ''
|
|
140
|
+
if (char === '\\') {
|
|
141
|
+
const next = text[index + 1]
|
|
142
|
+
if (next === undefined) break
|
|
143
|
+
index++
|
|
144
|
+
if (next === 'n') out.push({ char: '\n', soft: false })
|
|
145
|
+
else if (next === 't') out.push({ char: '\t', soft: false })
|
|
146
|
+
else if (next === 'u') {
|
|
147
|
+
const hex = text.slice(index + 1, index + 5)
|
|
148
|
+
if (!/^[0-9a-fA-F]{4}$/.test(hex))
|
|
149
|
+
throw new Error(`${where} has an invalid unicode escape \\u${hex}`)
|
|
150
|
+
out.push({ char: String.fromCharCode(Number.parseInt(hex, 16)), soft: false })
|
|
151
|
+
index += 4
|
|
152
|
+
} else out.push({ char: next, soft: false })
|
|
153
|
+
} else if (char === '"') quoted = !quoted
|
|
154
|
+
else if (char === "'" && !quoted)
|
|
155
|
+
throw new Error(`${where} has an unescaped apostrophe`)
|
|
156
|
+
else if (!quoted && /\s/.test(char)) {
|
|
157
|
+
if (out.at(-1)?.soft !== true) out.push({ char: ' ', soft: true })
|
|
158
|
+
} else out.push({ char, soft: false })
|
|
159
|
+
}
|
|
160
|
+
while (out[0]?.soft) out.shift()
|
|
161
|
+
while (out.at(-1)?.soft) out.pop()
|
|
162
|
+
return out.map((part) => part.char).join('')
|
|
163
|
+
}
|
package/src/android-xml.ts
CHANGED
|
@@ -21,7 +21,7 @@ const ENTITIES: Record<string, string> = {
|
|
|
21
21
|
const TAG = /<(\/?)([\w:.-]+)((?:\s+[\w:.-]+\s*=\s*(?:"[^"]*"|'[^']*'))*)\s*(\/?)>/y
|
|
22
22
|
const ATTRIBUTE = /([\w:.-]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g
|
|
23
23
|
|
|
24
|
-
function
|
|
24
|
+
export function decodeXmlText(value: string, file: string): string {
|
|
25
25
|
return value.replace(/&(#x[0-9a-fA-F]+|#\d+|\w+);/g, (_match, entity: string) => {
|
|
26
26
|
if (entity.startsWith('#x'))
|
|
27
27
|
return String.fromCodePoint(Number.parseInt(entity.slice(2), 16))
|
|
@@ -42,7 +42,7 @@ export function parseXml(text: string, file: string): XmlElement {
|
|
|
42
42
|
const open = body.indexOf('<', index)
|
|
43
43
|
const content = body.slice(index, open === -1 ? body.length : open)
|
|
44
44
|
const parent = stack.at(-1)
|
|
45
|
-
if (parent && content.trim()) parent.text +=
|
|
45
|
+
if (parent && content.trim()) parent.text += decodeXmlText(content, file)
|
|
46
46
|
if (open === -1) break
|
|
47
47
|
TAG.lastIndex = open
|
|
48
48
|
const match = TAG.exec(body)
|
|
@@ -59,7 +59,7 @@ export function parseXml(text: string, file: string): XmlElement {
|
|
|
59
59
|
}
|
|
60
60
|
const attributes: Record<string, string> = {}
|
|
61
61
|
for (const [, key, double, single] of (rawAttributes ?? '').matchAll(ATTRIBUTE))
|
|
62
|
-
if (key) attributes[key] =
|
|
62
|
+
if (key) attributes[key] = decodeXmlText(double ?? single ?? '', file)
|
|
63
63
|
const element: XmlElement = { name, attributes, children: [], text: '' }
|
|
64
64
|
stack.at(-1)?.children.push(element)
|
|
65
65
|
if (!selfClosing) stack.push(element)
|
package/src/kotlin-app.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import fs from 'node:fs'
|
|
7
7
|
import path from 'node:path'
|
|
8
|
+
import { readAndroidStrings, type AndroidStringResources } from './android-strings'
|
|
8
9
|
import {
|
|
9
10
|
frameworkColor,
|
|
10
11
|
readResourceValues,
|
|
@@ -47,6 +48,8 @@ export interface KotlinAppProject {
|
|
|
47
48
|
// how the activity's window meets the keyboard: pan over a covered focused
|
|
48
49
|
// view, resize above the keyboard, or nothing
|
|
49
50
|
softInputAdjust: 'pan' | 'resize' | 'nothing'
|
|
51
|
+
// the module's string resources, which its R class names
|
|
52
|
+
strings: AndroidStringResources
|
|
50
53
|
}
|
|
51
54
|
|
|
52
55
|
export interface AndroidPins {
|
|
@@ -501,5 +504,6 @@ export function readKotlinApp(
|
|
|
501
504
|
predictiveBack,
|
|
502
505
|
handlesUiMode,
|
|
503
506
|
softInputAdjust,
|
|
507
|
+
strings: readAndroidStrings(moduleDir),
|
|
504
508
|
}
|
|
505
509
|
}
|
package/src/kotlin-host.ts
CHANGED
|
@@ -410,6 +410,7 @@ export async function kotlinAppArtifact(
|
|
|
410
410
|
'PeachEntry.kt': entrySource([]),
|
|
411
411
|
'PeachViews.kt': viewsSource([]),
|
|
412
412
|
'PeachAppEntry.kt': appEntry([]),
|
|
413
|
+
...appResourceSources(app),
|
|
413
414
|
}
|
|
414
415
|
const shims = [
|
|
415
416
|
...kotlinSources(path.join(moduleRoot, 'Sources')),
|
|
@@ -435,6 +436,103 @@ export async function kotlinAppArtifact(
|
|
|
435
436
|
)
|
|
436
437
|
}
|
|
437
438
|
|
|
439
|
+
// the module's R class in its namespace and the string, plurals and array
|
|
440
|
+
// tables Resources reads. an id is 0x7f, the type, then the entry in name
|
|
441
|
+
// order. a device's ids also count the module's other resource types and the
|
|
442
|
+
// resources its libraries merge in, which Peach does not build, so the
|
|
443
|
+
// numbers themselves differ
|
|
444
|
+
function appResourceSources(app: KotlinAppProject): Record<string, string> {
|
|
445
|
+
const escapes: Record<string, string> = {
|
|
446
|
+
'\\': '\\\\',
|
|
447
|
+
'"': '\\"',
|
|
448
|
+
$: '\\$',
|
|
449
|
+
'\n': '\\n',
|
|
450
|
+
'\r': '\\r',
|
|
451
|
+
'\t': '\\t',
|
|
452
|
+
}
|
|
453
|
+
const literal = (value: string) =>
|
|
454
|
+
`"${value.replace(
|
|
455
|
+
/[\\"$\u0000-\u001f]/g,
|
|
456
|
+
(char) => escapes[char] ?? `\\u${char.charCodeAt(0).toString(16).padStart(4, '0')}`,
|
|
457
|
+
)}"`
|
|
458
|
+
// aapt2 writes a resource name's dots and dashes as underscores in R
|
|
459
|
+
const field = (name: string) => `\`${name.replace(/[.-]/g, '_')}\``
|
|
460
|
+
const id = (type: number, entry: number) =>
|
|
461
|
+
`0x7f${type.toString(16).padStart(2, '0')}${entry.toString(16).padStart(4, '0')}`
|
|
462
|
+
const kinds = [
|
|
463
|
+
{
|
|
464
|
+
type: 'string',
|
|
465
|
+
table: 'peachAppStrings',
|
|
466
|
+
value: 'String',
|
|
467
|
+
resources: app.strings.strings,
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
type: 'plurals',
|
|
471
|
+
table: 'peachAppPlurals',
|
|
472
|
+
value: 'Map<String, String>',
|
|
473
|
+
resources: app.strings.plurals,
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
type: 'array',
|
|
477
|
+
table: 'peachAppArrays',
|
|
478
|
+
value: 'List<String>',
|
|
479
|
+
resources: app.strings.arrays,
|
|
480
|
+
},
|
|
481
|
+
].map((kind, index) => ({
|
|
482
|
+
...kind,
|
|
483
|
+
entries: kind.resources.map((resource, entry) => ({
|
|
484
|
+
name: resource.name,
|
|
485
|
+
id: id(index + 1, entry),
|
|
486
|
+
values: Object.entries(resource.values).map(
|
|
487
|
+
([locale, value]: [
|
|
488
|
+
string,
|
|
489
|
+
string | string[] | Partial<Record<string, string>>,
|
|
490
|
+
]) =>
|
|
491
|
+
`${literal(locale)} to ${
|
|
492
|
+
typeof value === 'string'
|
|
493
|
+
? literal(value)
|
|
494
|
+
: Array.isArray(value)
|
|
495
|
+
? `listOf(${value.map(literal).join(', ')})`
|
|
496
|
+
: `mapOf(${Object.entries(value)
|
|
497
|
+
.map(
|
|
498
|
+
([quantity, text]) =>
|
|
499
|
+
`${literal(quantity)} to ${literal(text ?? '')}`,
|
|
500
|
+
)
|
|
501
|
+
.join(', ')})`
|
|
502
|
+
}`,
|
|
503
|
+
),
|
|
504
|
+
})),
|
|
505
|
+
}))
|
|
506
|
+
return {
|
|
507
|
+
'R.kt': [
|
|
508
|
+
`package ${app.namespace}`,
|
|
509
|
+
'',
|
|
510
|
+
'object R {',
|
|
511
|
+
...kinds.flatMap((kind) => [
|
|
512
|
+
` object ${kind.type} {`,
|
|
513
|
+
...kind.entries.map(
|
|
514
|
+
(entry) => ` const val ${field(entry.name)}: Int = ${entry.id}`,
|
|
515
|
+
),
|
|
516
|
+
' }',
|
|
517
|
+
]),
|
|
518
|
+
'}',
|
|
519
|
+
'',
|
|
520
|
+
].join('\n'),
|
|
521
|
+
'PeachAppResources.kt': [
|
|
522
|
+
'package dev.peach.app',
|
|
523
|
+
'',
|
|
524
|
+
...kinds.flatMap((kind) => [
|
|
525
|
+
`internal val ${kind.table}: Map<Int, Map<String, ${kind.value}>> = mapOf(`,
|
|
526
|
+
...kind.entries.map(
|
|
527
|
+
(entry) => ` ${entry.id} to mapOf(${entry.values.join(', ')}),`,
|
|
528
|
+
),
|
|
529
|
+
')',
|
|
530
|
+
'',
|
|
531
|
+
]),
|
|
532
|
+
].join('\n'),
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
438
536
|
async function compileKotlin(
|
|
439
537
|
input: {
|
|
440
538
|
root: string
|