peachsim 0.1.2199 → 0.1.2200
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/cli/commands/inspect/settling.ts +20 -20
- package/cli/outbound-endpoints.ts +12 -0
- package/dist-lib/agent-daemon-client.cjs +1 -1
- package/dist-lib/agent-events.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/bundle-channel.cjs +1421 -1117
- package/dist-lib/capture-contract.cjs +1 -1
- package/dist-lib/capture-contract.mjs +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/config.mjs +1 -1
- package/dist-lib/detox/index.cjs +22 -22
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/expo-swift-bundle.cjs +45896 -39977
- package/dist-lib/expo-swift-metadata.cjs +1 -1
- package/dist-lib/expo-swift-metadata.mjs +1 -1
- package/dist-lib/home-paths.cjs +1 -1
- package/dist-lib/host/bridge-host.cjs +50877 -49773
- package/dist-lib/host/fetch-proxy-handler.cjs +16 -4
- 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 +36 -24
- 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 +100 -10
- 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 +16 -4
- package/dist-lib/native-app-identity-project.cjs +14 -2
- package/dist-lib/native-app-identity-project.mjs +14 -2
- 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/pdfium-raster.cjs +1 -1
- package/dist-lib/pdfium-raster.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/public-origins-lib-esm.json +24 -24
- package/dist-lib/public-origins-lib.json +302 -289
- package/dist-lib/react-native-host-modules.cjs +1 -1
- package/dist-lib/react-native-host-modules.mjs +1 -1
- package/dist-lib/reap-browser.cjs +1 -1
- package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
- package/dist-lib/sdk.cjs +21 -21
- package/dist-lib/sdk.mjs +21 -21
- package/dist-lib/skills.cjs +3518 -2919
- package/dist-lib/swift-compile.cjs +34 -3
- package/dist-lib/swift-compile.mjs +34 -3
- 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-toolchain.cjs +1 -1
- package/dist-lib/swift.cjs +1 -1
- package/dist-lib/vite-swift.cjs +41 -7
- package/dist-lib/vite.cjs +16 -4
- package/dist-lib/worklet-executable.cjs +1 -1
- package/dist-lib/worklet-executable.mjs +1 -1
- package/dist-types/packages/peach-engine/src/engine/scene-table.d.ts +2 -0
- package/dist-types/packages/peach-engine/src/render-worker/app-prefetch-contract.d.ts +5 -0
- package/dist-types/packages/peach-engine/src/render-worker/protocol.d.ts +2 -0
- package/dist-types/packages/peach-engine/src/render-worker/tree-sync-port.d.ts +5 -0
- package/kotlin/sdk.tar.gz +0 -0
- package/package.json +1 -1
- package/runtime-companion.json +1 -1
- package/sdk/ios/System/Library/Frameworks/QuartzCore.framework/Headers/CAValueFunction.h +1 -1
- package/sdk/ios/System/Library/Frameworks/QuartzCore.framework/Modules/module.modulemap +1 -0
- package/sdk/ios/System/Library/Frameworks/UIKitText.framework/Headers/UIKitText.apinotes +14 -0
- package/src/android-layout-ids.ts +50 -0
- package/src/android-view-classes.ts +41 -0
- package/src/expo-swift-host.ts +6 -1
- package/src/java-module-abi.ts +2 -0
- package/src/javaAbiContract.ts +2 -0
- package/src/kotlin-app.ts +33 -7
- package/src/kotlin-host.ts +83 -6
- package/src/kotlin-maven-sources.ts +248 -0
- package/src/native-prebuilt.ts +2 -2
- package/src/react-native-core-build.ts +176 -0
- package/src/react-native-core-digest.ts +1 -1
- package/src/react-native-core.ts +24 -7
- package/src/swift-standin-emit.ts +9 -1
- package/src/swift-wasm-prune.ts +41 -3
- package/src/turbo-compile.ts +2 -1
package/src/kotlin-app.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import fs from 'node:fs'
|
|
7
7
|
import path from 'node:path'
|
|
8
8
|
import { readAndroidDrawables, type AndroidDrawable } from './android-drawables'
|
|
9
|
+
import { readAndroidLayoutIds, type AndroidLayoutIds } from './android-layout-ids'
|
|
9
10
|
import { readAndroidStrings, type AndroidStringResources } from './android-strings'
|
|
10
11
|
import {
|
|
11
12
|
readResourceValues,
|
|
@@ -18,6 +19,7 @@ import { parseXml, type XmlElement } from './android-xml'
|
|
|
18
19
|
import frameworkThemes from './generated/android-framework-themes.json' with { type: 'json' }
|
|
19
20
|
import { isRecord } from './is-record'
|
|
20
21
|
import { composeBomVersions } from './kotlin-compose-bom'
|
|
22
|
+
import { mavenKotlinSources } from './kotlin-maven-sources'
|
|
21
23
|
import { KotlinVersionCatalog } from './kotlin-version-catalog'
|
|
22
24
|
import type { AndroidGradleDependency } from './androidGradleContract'
|
|
23
25
|
|
|
@@ -60,6 +62,7 @@ export interface KotlinAppProject {
|
|
|
60
62
|
strings: AndroidStringResources
|
|
61
63
|
colors: KotlinAppColor[]
|
|
62
64
|
drawables: AndroidDrawable[]
|
|
65
|
+
layoutIds: AndroidLayoutIds
|
|
63
66
|
}
|
|
64
67
|
|
|
65
68
|
// a color resource's ARGB in the day and the night configuration
|
|
@@ -244,6 +247,7 @@ function kotlinPluginVersion(
|
|
|
244
247
|
}
|
|
245
248
|
|
|
246
249
|
async function checkModuleGradle(
|
|
250
|
+
projectDir: string,
|
|
247
251
|
gradle: { file: string; text: string },
|
|
248
252
|
pins: AndroidPins,
|
|
249
253
|
catalog: KotlinVersionCatalog,
|
|
@@ -258,7 +262,7 @@ async function checkModuleGradle(
|
|
|
258
262
|
throw new Error(`${where}: ${value} is not a literal or catalog dependency`)
|
|
259
263
|
}
|
|
260
264
|
const platforms = new Map<string, string>()
|
|
261
|
-
const dependencies: { coordinate: string; where: string }[] = []
|
|
265
|
+
const dependencies: { coordinate: string; where: string; compile: boolean }[] = []
|
|
262
266
|
let usesSerialization = false
|
|
263
267
|
for (const match of gradle.text.matchAll(
|
|
264
268
|
/\bid\(\s*"([^"]+)"\s*\)|\bkotlin\(\s*"([^"]+)"\s*\)|\balias\(\s*libs\.plugins\.([\w.]+)\s*\)/g,
|
|
@@ -291,6 +295,7 @@ async function checkModuleGradle(
|
|
|
291
295
|
dependencies.push({
|
|
292
296
|
coordinate: `${dependency.group}:${dependency.name}:${dependency.version}`,
|
|
293
297
|
where: gradle.file,
|
|
298
|
+
compile: true,
|
|
294
299
|
})
|
|
295
300
|
}
|
|
296
301
|
} else
|
|
@@ -321,6 +326,10 @@ async function checkModuleGradle(
|
|
|
321
326
|
if (!call) throw new Error(`${where}: ${text} is not a literal dependency`)
|
|
322
327
|
const [, configuration = '', value = ''] = call
|
|
323
328
|
if (/^(test|androidTest|debug|release)/.test(configuration)) continue
|
|
329
|
+
const compile =
|
|
330
|
+
configuration === 'implementation' ||
|
|
331
|
+
configuration === 'api' ||
|
|
332
|
+
configuration === 'compileOnly'
|
|
324
333
|
const platform = /^platform\(\s*(.*?)\s*\)$/.exec(
|
|
325
334
|
platformVariables.get(value) ?? value,
|
|
326
335
|
)
|
|
@@ -338,11 +347,13 @@ async function checkModuleGradle(
|
|
|
338
347
|
}
|
|
339
348
|
} else {
|
|
340
349
|
for (const coordinate of coordinates(value, where))
|
|
341
|
-
dependencies.push({ coordinate, where })
|
|
350
|
+
dependencies.push({ coordinate, where, compile })
|
|
342
351
|
}
|
|
343
352
|
}
|
|
344
353
|
}
|
|
345
|
-
|
|
354
|
+
const librarySources: string[] = []
|
|
355
|
+
const seen = new Set<string>()
|
|
356
|
+
for (const { coordinate, where, compile } of dependencies) {
|
|
346
357
|
const parts = /^([^:]+):([^:]+)(?::([^:]+))?$/.exec(coordinate)
|
|
347
358
|
if (!parts)
|
|
348
359
|
throw new Error(`${where}: ${coordinate} is not a group:artifact[:version]`)
|
|
@@ -361,10 +372,22 @@ async function checkModuleGradle(
|
|
|
361
372
|
throw new Error(
|
|
362
373
|
`${where}: ${name}:${version} differs from Peach's ${pin} pin ${pins[pin]}`,
|
|
363
374
|
)
|
|
364
|
-
} else if (!PLATFORM_GROUPS.has(group) && !PLATFORM_ARTIFACTS.has(name))
|
|
365
|
-
|
|
375
|
+
} else if (!PLATFORM_GROUPS.has(group) && !PLATFORM_ARTIFACTS.has(name)) {
|
|
376
|
+
const sources = compile
|
|
377
|
+
? await mavenKotlinSources(projectDir, { group, artifact, version }, where)
|
|
378
|
+
: null
|
|
379
|
+
if (!sources)
|
|
380
|
+
throw new Error(
|
|
381
|
+
`${where}: ${group}:${artifact}:${version} is not in Peach's Kotlin closure`,
|
|
382
|
+
)
|
|
383
|
+
for (const file of sources) {
|
|
384
|
+
if (seen.has(file)) continue
|
|
385
|
+
seen.add(file)
|
|
386
|
+
librarySources.push(file)
|
|
387
|
+
}
|
|
388
|
+
}
|
|
366
389
|
}
|
|
367
|
-
return { usesSerialization }
|
|
390
|
+
return { usesSerialization, librarySources }
|
|
368
391
|
}
|
|
369
392
|
|
|
370
393
|
// Resources.selectDefaultTheme: the theme a context without one gets for its
|
|
@@ -566,7 +589,8 @@ export async function readKotlinApp(
|
|
|
566
589
|
targetSdk: model.targetSdk,
|
|
567
590
|
}
|
|
568
591
|
})()
|
|
569
|
-
const { usesSerialization } = await checkModuleGradle(
|
|
592
|
+
const { usesSerialization, librarySources } = await checkModuleGradle(
|
|
593
|
+
projectDir,
|
|
570
594
|
gradle,
|
|
571
595
|
options.pins,
|
|
572
596
|
catalog,
|
|
@@ -729,6 +753,7 @@ export async function readKotlinApp(
|
|
|
729
753
|
}
|
|
730
754
|
walk(dir)
|
|
731
755
|
}
|
|
756
|
+
sources.push(...librarySources)
|
|
732
757
|
if (sources.length === 0)
|
|
733
758
|
throw new Error(`${moduleDir}/src/main holds no Kotlin sources`)
|
|
734
759
|
const resources = {
|
|
@@ -757,5 +782,6 @@ export async function readKotlinApp(
|
|
|
757
782
|
strings: readAndroidStrings(moduleDir),
|
|
758
783
|
colors: readAppColors(moduleDir, resources),
|
|
759
784
|
drawables: readAndroidDrawables(moduleDir, resources),
|
|
785
|
+
layoutIds: readAndroidLayoutIds(moduleDir),
|
|
760
786
|
}
|
|
761
787
|
}
|
package/src/kotlin-host.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
type BridgeModule,
|
|
19
19
|
type BridgeSelection,
|
|
20
20
|
} from './android-bridge-packages'
|
|
21
|
+
import { layoutViewClasses } from './android-view-classes'
|
|
21
22
|
import { rnxHomeDir } from './home-paths'
|
|
22
23
|
import { isRecord } from './is-record'
|
|
23
24
|
import {
|
|
@@ -39,6 +40,7 @@ import {
|
|
|
39
40
|
type ViewModelClass,
|
|
40
41
|
} from './kotlin-view-models'
|
|
41
42
|
import type { DayNight, DrawableFile } from './android-drawables'
|
|
43
|
+
import type { XmlElement } from './android-xml'
|
|
42
44
|
import type { ExpoKotlinModules } from './expo-kotlin-host'
|
|
43
45
|
import type { KotlinAppProject, KotlinWindowTheme } from './kotlin-app'
|
|
44
46
|
import type { KotlinSdkProfile, KotlinToolchain } from './kotlinSdkContract'
|
|
@@ -968,7 +970,7 @@ function appResourceSources(app: KotlinAppProject): Record<string, string> {
|
|
|
968
970
|
.map(([locale, value]) => `${literal(locale)} to ${render(value)}`)
|
|
969
971
|
.join(', ')})`,
|
|
970
972
|
}))
|
|
971
|
-
const
|
|
973
|
+
const tableKinds = [
|
|
972
974
|
{
|
|
973
975
|
type: 'string',
|
|
974
976
|
table: 'peachAppStrings',
|
|
@@ -1007,6 +1009,16 @@ function appResourceSources(app: KotlinAppProject): Record<string, string> {
|
|
|
1007
1009
|
.join(',\n')}\n)`,
|
|
1008
1010
|
})),
|
|
1009
1011
|
},
|
|
1012
|
+
// a layout is its element tree; one only in a qualified directory has none
|
|
1013
|
+
{
|
|
1014
|
+
type: 'layout',
|
|
1015
|
+
table: 'peachAppLayouts',
|
|
1016
|
+
value: 'LayoutNode?',
|
|
1017
|
+
entries: app.layoutIds.layouts.map((name) => {
|
|
1018
|
+
const element = app.layoutIds.elements.get(name)
|
|
1019
|
+
return { name, init: element ? layoutInit(element) : 'null' }
|
|
1020
|
+
}),
|
|
1021
|
+
},
|
|
1010
1022
|
// the day and the night ARGB
|
|
1011
1023
|
{
|
|
1012
1024
|
type: 'color',
|
|
@@ -1017,19 +1029,38 @@ function appResourceSources(app: KotlinAppProject): Record<string, string> {
|
|
|
1017
1029
|
init: `intArrayOf(${argb(color.day)}, ${argb(color.night)})`,
|
|
1018
1030
|
})),
|
|
1019
1031
|
},
|
|
1020
|
-
]
|
|
1032
|
+
]
|
|
1033
|
+
// ids are named in R and in the name table; they hold no Resources value
|
|
1034
|
+
const nameOnly = [{ type: 'id', names: app.layoutIds.ids }]
|
|
1035
|
+
const numbered = <Kind extends { entries: Array<{ name: string }> }>(
|
|
1036
|
+
kind: Kind,
|
|
1037
|
+
index: number,
|
|
1038
|
+
): Omit<Kind, 'entries'> & {
|
|
1039
|
+
entries: Array<Kind['entries'][number] & { id: string }>
|
|
1040
|
+
} => ({
|
|
1021
1041
|
...kind,
|
|
1022
1042
|
entries: kind.entries.map((entry, number) => ({
|
|
1023
1043
|
...entry,
|
|
1024
1044
|
id: id(index + 1, number),
|
|
1025
1045
|
})),
|
|
1026
|
-
})
|
|
1046
|
+
})
|
|
1047
|
+
const kinds = tableKinds.map((kind, index) => numbered(kind, index))
|
|
1048
|
+
const viewClasses = layoutViewClasses(app.layoutIds.elements.values())
|
|
1049
|
+
const rKinds = [
|
|
1050
|
+
...kinds,
|
|
1051
|
+
...nameOnly.map((kind, index) =>
|
|
1052
|
+
numbered(
|
|
1053
|
+
{ type: kind.type, entries: kind.names.map((name) => ({ name })) },
|
|
1054
|
+
kinds.length + index,
|
|
1055
|
+
),
|
|
1056
|
+
),
|
|
1057
|
+
]
|
|
1027
1058
|
return {
|
|
1028
1059
|
'R.kt': [
|
|
1029
1060
|
`package ${app.namespace}`,
|
|
1030
1061
|
'',
|
|
1031
1062
|
'object R {',
|
|
1032
|
-
...
|
|
1063
|
+
...rKinds.flatMap((kind) => [
|
|
1033
1064
|
` object ${kind.type} {`,
|
|
1034
1065
|
...kind.entries.map(
|
|
1035
1066
|
(entry) => ` const val ${field(entry.name)}: Int = ${entry.id}`,
|
|
@@ -1051,6 +1082,10 @@ function appResourceSources(app: KotlinAppProject): Record<string, string> {
|
|
|
1051
1082
|
'androidx.compose.ui.graphics.vector.ImageVector',
|
|
1052
1083
|
'androidx.compose.ui.graphics.vector.addPathNodes',
|
|
1053
1084
|
'androidx.compose.ui.unit.Dp',
|
|
1085
|
+
'android.content.Context',
|
|
1086
|
+
'android.util.AttributeSet',
|
|
1087
|
+
'android.view.LayoutNode',
|
|
1088
|
+
'android.view.View',
|
|
1054
1089
|
].map((name) => `import ${name}`),
|
|
1055
1090
|
'',
|
|
1056
1091
|
...kinds.flatMap((kind) => [
|
|
@@ -1059,16 +1094,45 @@ function appResourceSources(app: KotlinAppProject): Record<string, string> {
|
|
|
1059
1094
|
')',
|
|
1060
1095
|
'',
|
|
1061
1096
|
]),
|
|
1097
|
+
'internal val peachAppViews: Map<String, (Context, AttributeSet?) -> View> = mapOf(',
|
|
1098
|
+
...viewClasses.map(
|
|
1099
|
+
(name) =>
|
|
1100
|
+
` ${literal(name)} to { context, attrs -> ${name}(context, attrs) },`,
|
|
1101
|
+
),
|
|
1102
|
+
')',
|
|
1103
|
+
'',
|
|
1104
|
+
`internal val peachAppNames: Map<Int, String> = mapOf(`,
|
|
1105
|
+
...rKinds.flatMap((kind) =>
|
|
1106
|
+
kind.entries.map(
|
|
1107
|
+
(entry) =>
|
|
1108
|
+
` ${entry.id} to ${literal(`${app.packageName}:${kind.type}/${entry.name}`)},`,
|
|
1109
|
+
),
|
|
1110
|
+
),
|
|
1111
|
+
')',
|
|
1112
|
+
'',
|
|
1062
1113
|
'internal val compiledAppResources = PeachAppResources(',
|
|
1063
1114
|
...kinds.map(
|
|
1064
1115
|
(kind) =>
|
|
1065
1116
|
` ${kind.table.slice('peachApp'.length).toLowerCase()} = ${kind.table},`,
|
|
1066
1117
|
),
|
|
1118
|
+
' views = peachAppViews,',
|
|
1119
|
+
' names = peachAppNames,',
|
|
1067
1120
|
')',
|
|
1068
1121
|
].join('\n'),
|
|
1069
1122
|
}
|
|
1070
1123
|
}
|
|
1071
1124
|
|
|
1125
|
+
// the elements a layout inflates, without the namespace declarations and
|
|
1126
|
+
// tools attributes aapt2 strips
|
|
1127
|
+
function layoutInit(element: XmlElement): string {
|
|
1128
|
+
const attributes = Object.entries(element.attributes)
|
|
1129
|
+
.filter(([key]) => !key.startsWith('xmlns') && !key.startsWith('tools:'))
|
|
1130
|
+
.map(([key, value]) => `${literal(key)} to ${literal(value)}`)
|
|
1131
|
+
return `LayoutNode(${literal(element.name)}, mapOf(${attributes.join(', ')}), listOf(${element.children
|
|
1132
|
+
.map(layoutInit)
|
|
1133
|
+
.join(', ')}))`
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1072
1136
|
// a drawable file's table entry: a vector as the ImageVector.Builder calls
|
|
1073
1137
|
// XmlVectorParser makes, its colors chosen by the configuration's night
|
|
1074
1138
|
// mode; a bitmap as its encoded bytes
|
|
@@ -1171,11 +1235,19 @@ async function compileKotlin(
|
|
|
1171
1235
|
const sdk = await loadKotlinSdk(moduleRoot, toolchain)
|
|
1172
1236
|
const plugin = path.join(sdk.root, 'plugin.jar')
|
|
1173
1237
|
const sdkLibrary = path.join(sdk.root, `${sdkProfile}.klib`)
|
|
1174
|
-
const
|
|
1238
|
+
const javaRead = javaSpecs.length
|
|
1175
1239
|
? await readJavaModuleSpecs(javaSpecs, sdk.root, await ensureToolchain(toolchain))
|
|
1176
1240
|
: null
|
|
1241
|
+
const java =
|
|
1242
|
+
javaRead && javaRead.specs.some((spec) => !spec.application)
|
|
1243
|
+
? { ...javaRead, specs: javaRead.specs.filter((spec) => !spec.application) }
|
|
1244
|
+
: null
|
|
1245
|
+
const applicationJava = javaRead?.specs.filter((spec) => spec.application) ?? []
|
|
1177
1246
|
const generated = {
|
|
1178
1247
|
...sourceGenerated,
|
|
1248
|
+
...Object.fromEntries(
|
|
1249
|
+
applicationJava.map((spec, index) => [`JavaApplication${index}.kt`, spec.source]),
|
|
1250
|
+
),
|
|
1179
1251
|
'PeachBindingsInitialization.kt': [
|
|
1180
1252
|
'@file:OptIn(kotlin.js.ExperimentalJsExport::class)',
|
|
1181
1253
|
'package dev.peach.jni',
|
|
@@ -1209,7 +1281,7 @@ async function compileKotlin(
|
|
|
1209
1281
|
sdk: sdk.sha256,
|
|
1210
1282
|
sdkProfile,
|
|
1211
1283
|
usesSerialization,
|
|
1212
|
-
java:
|
|
1284
|
+
java: javaRead?.sha256,
|
|
1213
1285
|
}),
|
|
1214
1286
|
)
|
|
1215
1287
|
for (const { file, bytes } of inputs)
|
|
@@ -1442,6 +1514,11 @@ async function compileKotlin(
|
|
|
1442
1514
|
: ''
|
|
1443
1515
|
for (const [name, source] of Object.entries(origins))
|
|
1444
1516
|
output = output.replaceAll(path.join(generatedDir, name), source)
|
|
1517
|
+
for (const [index, spec] of applicationJava.entries())
|
|
1518
|
+
output = output.replaceAll(
|
|
1519
|
+
path.join(generatedDir, `JavaApplication${index}.kt`),
|
|
1520
|
+
spec.file,
|
|
1521
|
+
)
|
|
1445
1522
|
for (const { file, target } of compiledSources)
|
|
1446
1523
|
output = output
|
|
1447
1524
|
.replaceAll(target, file)
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
// a dependency outside Peach's pinned closure compiles from the sources jar
|
|
2
|
+
// the app's Maven repositories publish for that coordinate
|
|
3
|
+
import { createHash } from 'node:crypto'
|
|
4
|
+
import fs from 'node:fs'
|
|
5
|
+
import os from 'node:os'
|
|
6
|
+
import path from 'node:path'
|
|
7
|
+
import { inflateRawSync } from 'node:zlib'
|
|
8
|
+
|
|
9
|
+
const pending = new Map<string, Promise<string[]>>()
|
|
10
|
+
|
|
11
|
+
function braceBlock(text: string, from: number): { body: string; end: number } | null {
|
|
12
|
+
const open = text.indexOf('{', from)
|
|
13
|
+
if (open < 0) return null
|
|
14
|
+
let depth = 0
|
|
15
|
+
for (let index = open; index < text.length; index++) {
|
|
16
|
+
const char = text[index]
|
|
17
|
+
if (char === '{') depth++
|
|
18
|
+
else if (char === '}') {
|
|
19
|
+
depth--
|
|
20
|
+
if (depth === 0) return { body: text.slice(open + 1, index), end: index }
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return null
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function repositoryRoot(value: string, where: string): string {
|
|
27
|
+
if (value.startsWith('https://')) return value.replace(/\/+$/, '')
|
|
28
|
+
if (value.startsWith('/')) return value.replace(/\/+$/, '')
|
|
29
|
+
if (value.startsWith('file:')) {
|
|
30
|
+
const pathname = new URL(value).pathname
|
|
31
|
+
if (!pathname.startsWith('/'))
|
|
32
|
+
throw new Error(`${where}: Maven repository ${value} is not a local path`)
|
|
33
|
+
return decodeURIComponent(pathname).replace(/\/+$/, '')
|
|
34
|
+
}
|
|
35
|
+
throw new Error(
|
|
36
|
+
`${where}: Maven repository ${value} is not an https URL or a local path`,
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// google(), mavenCentral(), mavenLocal(), or one literal maven URL
|
|
41
|
+
function withoutComments(text: string): string {
|
|
42
|
+
let out = ''
|
|
43
|
+
for (let index = 0; index < text.length; index++) {
|
|
44
|
+
const char = text[index]
|
|
45
|
+
const next = text[index + 1]
|
|
46
|
+
if (char === '"' || char === "'") {
|
|
47
|
+
const quote = char
|
|
48
|
+
out += char
|
|
49
|
+
index++
|
|
50
|
+
while (index < text.length && text[index] !== quote) {
|
|
51
|
+
out += text[index]
|
|
52
|
+
if (text[index] === '\\') {
|
|
53
|
+
index++
|
|
54
|
+
out += text[index] ?? ''
|
|
55
|
+
}
|
|
56
|
+
index++
|
|
57
|
+
}
|
|
58
|
+
out += text[index] ?? ''
|
|
59
|
+
continue
|
|
60
|
+
}
|
|
61
|
+
if (char === '/' && next === '/') {
|
|
62
|
+
while (index < text.length && text[index] !== '\n') index++
|
|
63
|
+
out += '\n'
|
|
64
|
+
continue
|
|
65
|
+
}
|
|
66
|
+
if (char === '/' && next === '*') {
|
|
67
|
+
index += 2
|
|
68
|
+
while (index < text.length && !(text[index] === '*' && text[index + 1] === '/'))
|
|
69
|
+
index++
|
|
70
|
+
index++
|
|
71
|
+
continue
|
|
72
|
+
}
|
|
73
|
+
out += char
|
|
74
|
+
}
|
|
75
|
+
return out
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function repositories(body: string, where: string): string[] {
|
|
79
|
+
const stripped = withoutComments(body)
|
|
80
|
+
const roots: string[] = []
|
|
81
|
+
const leftover = stripped.replace(
|
|
82
|
+
/\b(google|mavenCentral|mavenLocal)\s*\(\s*\)|\bmaven\s*(?:\(\s*["']([^"']+)["']\s*\)|\{([^{}]*)\})/g,
|
|
83
|
+
(match, name: string, direct: string | undefined, block: string | undefined) => {
|
|
84
|
+
if (name === 'google') roots.push('https://dl.google.com/dl/android/maven2')
|
|
85
|
+
else if (name === 'mavenCentral') roots.push('https://repo1.maven.org/maven2')
|
|
86
|
+
else if (name === 'mavenLocal')
|
|
87
|
+
roots.push(path.join(os.homedir(), '.m2', 'repository'))
|
|
88
|
+
else {
|
|
89
|
+
const declared = direct ?? /["']([^"']+)["']/.exec(block ?? '')?.[1]
|
|
90
|
+
if (!declared)
|
|
91
|
+
throw new Error(`${where}: ${match.trim()} declares no repository URL`)
|
|
92
|
+
roots.push(repositoryRoot(declared, where))
|
|
93
|
+
}
|
|
94
|
+
return ''
|
|
95
|
+
},
|
|
96
|
+
)
|
|
97
|
+
if (leftover.replace(/[\s;]/g, ''))
|
|
98
|
+
throw new Error(`${where}: ${leftover.trim()} is not a literal Maven repository`)
|
|
99
|
+
return roots
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function mavenRepositories(projectDir: string, where: string): string[] {
|
|
103
|
+
const file = ['settings.gradle.kts', 'settings.gradle']
|
|
104
|
+
.map((name) => path.join(projectDir, name))
|
|
105
|
+
.find((candidate) => fs.existsSync(candidate))
|
|
106
|
+
if (!file) throw new Error(`${where}: ${projectDir} has no settings.gradle`)
|
|
107
|
+
const text = fs.readFileSync(file, 'utf8')
|
|
108
|
+
const management = text.search(/\bdependencyResolutionManagement\b/)
|
|
109
|
+
if (management < 0)
|
|
110
|
+
throw new Error(`${where}: ${file} declares no dependency repositories`)
|
|
111
|
+
const managed = braceBlock(text, management)
|
|
112
|
+
if (!managed)
|
|
113
|
+
throw new Error(`${where}: ${file} dependencyResolutionManagement block never closes`)
|
|
114
|
+
const repositoriesAt = managed.body.search(/\brepositories\b/)
|
|
115
|
+
if (repositoriesAt < 0)
|
|
116
|
+
throw new Error(`${where}: ${file} declares no dependency repositories`)
|
|
117
|
+
const block = braceBlock(managed.body, repositoriesAt)
|
|
118
|
+
if (!block) throw new Error(`${where}: ${file} repositories block never closes`)
|
|
119
|
+
const roots = repositories(block.body, `${where}: ${file}`)
|
|
120
|
+
if (roots.length === 0)
|
|
121
|
+
throw new Error(`${where}: ${file} declares no dependency repositories`)
|
|
122
|
+
return roots
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function zipSources(bytes: Buffer, where: string): { name: string; data: Buffer }[] {
|
|
126
|
+
let end = -1
|
|
127
|
+
const earliest = Math.max(0, bytes.length - 22 - 0xffff)
|
|
128
|
+
for (let index = bytes.length - 22; index >= earliest; index--) {
|
|
129
|
+
if (bytes.readUInt32LE(index) === 0x06054b50) {
|
|
130
|
+
end = index
|
|
131
|
+
break
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (end < 0) throw new Error(`${where}: sources jar is not a zip`)
|
|
135
|
+
const count = bytes.readUInt16LE(end + 8)
|
|
136
|
+
if (count !== bytes.readUInt16LE(end + 10))
|
|
137
|
+
throw new Error(`${where}: sources jar spans zip disks`)
|
|
138
|
+
let offset = bytes.readUInt32LE(end + 16)
|
|
139
|
+
if (offset === 0xffffffff) throw new Error(`${where}: sources jar is zip64`)
|
|
140
|
+
const entries: { name: string; data: Buffer }[] = []
|
|
141
|
+
for (let index = 0; index < count; index++) {
|
|
142
|
+
if (bytes.readUInt32LE(offset) !== 0x02014b50)
|
|
143
|
+
throw new Error(`${where}: sources jar has a broken central directory`)
|
|
144
|
+
const flags = bytes.readUInt16LE(offset + 8)
|
|
145
|
+
const method = bytes.readUInt16LE(offset + 10)
|
|
146
|
+
const compressed = bytes.readUInt32LE(offset + 20)
|
|
147
|
+
const nameLength = bytes.readUInt16LE(offset + 28)
|
|
148
|
+
const extraLength = bytes.readUInt16LE(offset + 30)
|
|
149
|
+
const commentLength = bytes.readUInt16LE(offset + 32)
|
|
150
|
+
const localOffset = bytes.readUInt32LE(offset + 42)
|
|
151
|
+
const name = bytes.toString('utf8', offset + 46, offset + 46 + nameLength)
|
|
152
|
+
offset += 46 + nameLength + extraLength + commentLength
|
|
153
|
+
if (name.endsWith('/')) continue
|
|
154
|
+
if (flags & 1) throw new Error(`${where}: ${name} is encrypted`)
|
|
155
|
+
if (name.startsWith('/') || name.split('/').includes('..'))
|
|
156
|
+
throw new Error(`${where}: ${name} escapes the sources jar`)
|
|
157
|
+
if (bytes.readUInt32LE(localOffset) !== 0x04034b50)
|
|
158
|
+
throw new Error(`${where}: ${name} has no local header`)
|
|
159
|
+
const localName = bytes.readUInt16LE(localOffset + 26)
|
|
160
|
+
const localExtra = bytes.readUInt16LE(localOffset + 28)
|
|
161
|
+
const start = localOffset + 30 + localName + localExtra
|
|
162
|
+
const payload = bytes.subarray(start, start + compressed)
|
|
163
|
+
const data =
|
|
164
|
+
method === 0 ? Buffer.from(payload) : method === 8 ? inflateRawSync(payload) : null
|
|
165
|
+
if (!data) throw new Error(`${where}: ${name} uses zip method ${method}`)
|
|
166
|
+
entries.push({ name, data })
|
|
167
|
+
}
|
|
168
|
+
return entries
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function extract(bytes: Buffer, where: string): Promise<string[]> {
|
|
172
|
+
const key = createHash('sha256').update(bytes).digest('hex')
|
|
173
|
+
const cached = pending.get(key)
|
|
174
|
+
if (cached) return cached
|
|
175
|
+
const request = (async () => {
|
|
176
|
+
const dest = path.join(os.tmpdir(), 'peach-maven-sources', key)
|
|
177
|
+
const marker = path.join(dest, '.complete')
|
|
178
|
+
if (!fs.existsSync(marker)) {
|
|
179
|
+
const entries = zipSources(bytes, where)
|
|
180
|
+
const java = entries.find((entry) => entry.name.endsWith('.java'))
|
|
181
|
+
if (java) throw new Error(`${where}: ${java.name} is a Java source`)
|
|
182
|
+
const kotlin = entries.filter((entry) => entry.name.endsWith('.kt'))
|
|
183
|
+
if (kotlin.length === 0) throw new Error(`${where}: sources jar has no Kotlin`)
|
|
184
|
+
const staging = `${dest}.partial-${process.pid}`
|
|
185
|
+
fs.rmSync(staging, { recursive: true, force: true })
|
|
186
|
+
for (const entry of kotlin) {
|
|
187
|
+
const file = path.join(staging, entry.name)
|
|
188
|
+
fs.mkdirSync(path.dirname(file), { recursive: true })
|
|
189
|
+
fs.writeFileSync(file, entry.data)
|
|
190
|
+
}
|
|
191
|
+
fs.writeFileSync(path.join(staging, '.complete'), '')
|
|
192
|
+
try {
|
|
193
|
+
fs.renameSync(staging, dest)
|
|
194
|
+
} catch (error) {
|
|
195
|
+
fs.rmSync(staging, { recursive: true, force: true })
|
|
196
|
+
if (!fs.existsSync(marker)) throw error
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return fs
|
|
200
|
+
.readdirSync(dest, { recursive: true })
|
|
201
|
+
.flatMap((entry) => {
|
|
202
|
+
const name = String(entry)
|
|
203
|
+
return name.endsWith('.kt') ? [path.join(dest, name)] : []
|
|
204
|
+
})
|
|
205
|
+
.sort()
|
|
206
|
+
})().catch((error: unknown) => {
|
|
207
|
+
pending.delete(key)
|
|
208
|
+
throw error
|
|
209
|
+
})
|
|
210
|
+
pending.set(key, request)
|
|
211
|
+
return request
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function sourcesJar(
|
|
215
|
+
root: string,
|
|
216
|
+
group: string,
|
|
217
|
+
artifact: string,
|
|
218
|
+
version: string,
|
|
219
|
+
): Promise<Buffer | null> {
|
|
220
|
+
const parts = [
|
|
221
|
+
...group.split('.'),
|
|
222
|
+
artifact,
|
|
223
|
+
version,
|
|
224
|
+
`${artifact}-${version}-sources.jar`,
|
|
225
|
+
]
|
|
226
|
+
if (root.startsWith('https://')) {
|
|
227
|
+
const url = `${root}/${parts.join('/')}`
|
|
228
|
+
const response = await fetch(url)
|
|
229
|
+
if (response.status === 404) return null
|
|
230
|
+
if (!response.ok) throw new Error(`${url} answered ${response.status}`)
|
|
231
|
+
return Buffer.from(await response.arrayBuffer())
|
|
232
|
+
}
|
|
233
|
+
const file = path.join(root, ...parts)
|
|
234
|
+
return fs.existsSync(file) ? fs.readFileSync(file) : null
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export async function mavenKotlinSources(
|
|
238
|
+
projectDir: string,
|
|
239
|
+
coordinate: { group: string; artifact: string; version: string },
|
|
240
|
+
where: string,
|
|
241
|
+
): Promise<string[] | null> {
|
|
242
|
+
const { group, artifact, version } = coordinate
|
|
243
|
+
for (const root of mavenRepositories(projectDir, where)) {
|
|
244
|
+
const jar = await sourcesJar(root, group, artifact, version)
|
|
245
|
+
if (jar) return extract(jar, `${where}: ${group}:${artifact}:${version}`)
|
|
246
|
+
}
|
|
247
|
+
return null
|
|
248
|
+
}
|
package/src/native-prebuilt.ts
CHANGED
|
@@ -58,7 +58,7 @@ export function nativeEntryKey(inputs: Record<string, string>): string {
|
|
|
58
58
|
export const nativeEntryPrefix = (kind: NativeEntryKind, name: string, key: string) =>
|
|
59
59
|
`native/${kind}/${name}/${key}`
|
|
60
60
|
|
|
61
|
-
const
|
|
61
|
+
export const nativeEntryCache = (kind: NativeEntryKind, name: string, key: string) =>
|
|
62
62
|
path.join(peachCacheDir(), 'native-prebuilt', kind, name, key)
|
|
63
63
|
|
|
64
64
|
// one GET; a missing object is null. a network failure is a miss too, since a
|
|
@@ -111,7 +111,7 @@ export function fetchNativeEntry(
|
|
|
111
111
|
name: string,
|
|
112
112
|
key: string,
|
|
113
113
|
): string | null {
|
|
114
|
-
const dir =
|
|
114
|
+
const dir = nativeEntryCache(kind, name, key)
|
|
115
115
|
const descriptorFile = path.join(dir, 'descriptor.json')
|
|
116
116
|
if (fs.existsSync(descriptorFile)) return dir
|
|
117
117
|
const base = `${NATIVE_PREBUILT_ORIGIN}/${nativeEntryPrefix(kind, name, key)}`
|