expo-modules-macros 0.20.0 → 0.22.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.
Files changed (39) hide show
  1. package/.github/workflows/linux.yml +132 -0
  2. package/.github/workflows/publish.yml +160 -11
  3. package/.github/workflows/swift-format.yml +76 -0
  4. package/.github/workflows/windows.yml +3 -28
  5. package/AGENTS.md +19 -9
  6. package/README.md +27 -8
  7. package/apple/ExpoModulesMacros +0 -0
  8. package/apple/Package.resolved +3 -3
  9. package/apple/Package.swift +1 -2
  10. package/apple/Sources/ExpoModulesMacros/ClosureArguments.swift +39 -14
  11. package/apple/Sources/ExpoModulesMacros/DecorateModuleBuilder.swift +117 -25
  12. package/apple/Sources/ExpoModulesMacros/EventMacro.swift +29 -15
  13. package/apple/Sources/ExpoModulesMacros/ExpoModuleMacro.swift +55 -45
  14. package/apple/Sources/ExpoModulesMacros/ExpoModulesOptimizedMacro.swift +2 -1
  15. package/apple/Sources/ExpoModulesMacros/FreeFormTypes.swift +2 -61
  16. package/apple/Sources/ExpoModulesMacros/JSConstructor.swift +6 -3
  17. package/apple/Sources/ExpoModulesMacros/JSMacro.swift +180 -44
  18. package/apple/Sources/ExpoModulesMacros/JSNameCollisions.swift +84 -0
  19. package/apple/Sources/ExpoModulesMacros/MacroHelpers.swift +165 -43
  20. package/apple/Sources/ExpoModulesMacros/RecordMacro.swift +358 -234
  21. package/apple/Sources/ExpoModulesMacros/SharedObjectMacro.swift +48 -32
  22. package/apple/Sources/ExpoModulesMacros/TypeConformanceAssertion.swift +57 -27
  23. package/apple/Sources/ExpoModulesMacros/UnionMacro.swift +23 -9
  24. package/apple/Sources/ExpoModulesMacros/ViewPropsMacro.swift +10 -5
  25. package/apple/Sources/ExpoModulesScanner/CLI.swift +3 -1
  26. package/apple/Sources/ExpoModulesScanner/Core/DetectionVisitor.swift +11 -7
  27. package/apple/Sources/ExpoModulesScanner/Core/ScanBuildConfiguration.swift +2 -1
  28. package/apple/Sources/ExpoModulesScanner/Core/SourceScan.swift +10 -7
  29. package/apple/Sources/ExpoModulesScanner/Exports/Availability.swift +6 -3
  30. package/apple/Sources/ExpoModulesScanner/Exports/ExportedSurface.swift +3 -2
  31. package/apple/Sources/ExpoModulesScanner/Exports/ResolveRefs.swift +15 -5
  32. package/apple/Sources/ExpoModulesScanner/Exports/ScanExports.swift +8 -3
  33. package/apple/Sources/ExpoModulesScanner/Exports/SurfaceVisitor.swift +127 -45
  34. package/apple/Sources/ExpoModulesScanner/Exports/TypeNode.swift +2 -1
  35. package/apple/Sources/ExpoModulesScanner/Modules/ScanModules.swift +8 -2
  36. package/build/index.d.ts +4 -3
  37. package/build/index.js +33 -18
  38. package/build/types.d.ts +7 -1
  39. package/package.json +7 -3
@@ -0,0 +1,132 @@
1
+ name: Linux
2
+
3
+ # Builds and tests the package on Linux, and checks the release tool and its package there.
4
+
5
+ on:
6
+ push:
7
+ branches: [main]
8
+ pull_request:
9
+ branches: [main]
10
+ workflow_dispatch: {}
11
+
12
+ concurrency:
13
+ group: ci-${{ github.workflow }}-${{ github.ref }}
14
+ cancel-in-progress: true
15
+
16
+ defaults:
17
+ run:
18
+ working-directory: apple
19
+ shell: bash
20
+
21
+ jobs:
22
+ linux:
23
+ name: Build & test (Linux ${{ matrix.arch }})
24
+ runs-on: ${{ matrix.runner }}
25
+ # The release tool needs the glibc version that it's built against or a newer one. Red Hat UBI 9
26
+ # has glibc 2.34, the oldest version that the package supports. The Publish workflow builds the
27
+ # tool in the same image.
28
+ container: swift:6.4.0-rhel-ubi9@sha256:802a003f1eacbca9c20664a8ba79621d13f4538c3d329e745b7f8d53c70604a3
29
+ # The limit stops a hang, for example in the plugin server, from holding a runner for the default
30
+ # 6 hours.
31
+ timeout-minutes: 30
32
+ env:
33
+ # The newest glibc symbol version that the release tool may need. The READMEs of the Linux
34
+ # packages name it.
35
+ MAX_GLIBC_VERSION: '2.34'
36
+ strategy:
37
+ fail-fast: false
38
+ matrix:
39
+ include:
40
+ - arch: x64
41
+ runner: ubuntu-24.04
42
+ - arch: arm64
43
+ runner: ubuntu-24.04-arm
44
+ steps:
45
+ - name: Checkout
46
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
47
+
48
+ - name: Setup Node
49
+ uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
50
+ with:
51
+ node-version: 24
52
+
53
+ - name: Cache SwiftPM build
54
+ uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
55
+ with:
56
+ path: apple/.build
57
+ key: spm-${{ github.repository }}-${{ runner.os }}-${{ matrix.arch }}-${{ hashFiles('apple/Package.resolved') }}
58
+ restore-keys: |
59
+ spm-${{ github.repository }}-${{ runner.os }}-${{ matrix.arch }}-
60
+
61
+ - name: Build
62
+ run: swift build
63
+
64
+ - name: Test
65
+ run: swift test
66
+
67
+ - name: Build release tool
68
+ # Runs `npm run build` (scripts/build.js): builds the release plugin binary for this
69
+ # architecture as platforms/linux-<arch>/ExpoModulesMacros.
70
+ working-directory: ${{ github.workspace }}
71
+ run: npm run build
72
+
73
+ - name: Verify the release tool
74
+ # With no arguments it is the plugin server, which exits when stdin closes; with arguments it
75
+ # is the scanner CLI.
76
+ run: |
77
+ tool="../platforms/linux-${{ matrix.arch }}/ExpoModulesMacros"
78
+ # The Swift runtime must be linked in statically, so no Swift library may be a dependency.
79
+ needed="$(readelf --dynamic "$tool" | sed -n 's/.*(NEEDED).*\[\(.*\)\]$/\1/p')"
80
+ echo "Needed libraries:" $needed
81
+ if echo "$needed" | grep -E 'swift|Foundation|dispatch|BlocksRuntime'; then
82
+ echo "The release tool links a Swift library dynamically"
83
+ exit 1
84
+ fi
85
+ glibc="$(objdump -T "$tool" | grep -o 'GLIBC_[0-9][0-9.]*' | sed 's/^GLIBC_//' | sort -uV | tail -n 1)"
86
+ echo "Newest glibc symbol version: $glibc"
87
+ if [ "$(printf '%s\n' "$glibc" "$MAX_GLIBC_VERSION" | sort -V | tail -n 1)" != "$MAX_GLIBC_VERSION" ]; then
88
+ echo "The release tool needs glibc $glibc, newer than $MAX_GLIBC_VERSION"
89
+ exit 1
90
+ fi
91
+ "$tool" < /dev/null
92
+ "$tool" --help > /dev/null
93
+
94
+ - name: Pack the platform package
95
+ # Checks what `npm pack` would publish from `platforms/linux-<arch>`, where the release build
96
+ # put the tool: the executable with its executable bit, the README, the license and the
97
+ # manifest, with the `os`, `cpu` and `libc` fields for this machine.
98
+ working-directory: ${{ github.workspace }}
99
+ run: |
100
+ # The `./` matters: npm reads `platforms/linux-<arch>` as a GitHub repository.
101
+ npm pack "./platforms/linux-${{ matrix.arch }}" --pack-destination "$RUNNER_TEMP" --json > "$RUNNER_TEMP/pack.json"
102
+ node - "$RUNNER_TEMP/pack.json" "${{ matrix.arch }}" <<'EOF'
103
+ const fs = require('node:fs');
104
+ const [packPath, arch] = process.argv.slice(2);
105
+ const [pack] = JSON.parse(fs.readFileSync(packPath, 'utf8'));
106
+ const files = pack.files.map((file) => file.path).sort().join(',');
107
+ console.log(`${pack.filename}: ${files} (${pack.size} bytes packed)`);
108
+ if (files !== 'ExpoModulesMacros,LICENSE,README.md,package.json') {
109
+ throw new Error(`Unexpected files: ${files}`);
110
+ }
111
+ const tool = pack.files.find((file) => file.path === 'ExpoModulesMacros');
112
+ if ((tool.mode & 0o111) === 0) {
113
+ throw new Error(`ExpoModulesMacros is packed without the executable bit (mode ${tool.mode.toString(8)})`);
114
+ }
115
+ const manifest = JSON.parse(fs.readFileSync(`platforms/linux-${arch}/package.json`, 'utf8'));
116
+ const fields = [manifest.os, manifest.cpu, manifest.libc].join(' / ');
117
+ if (fields !== `linux / ${arch} / glibc`) {
118
+ throw new Error(`Unexpected os / cpu / libc: ${fields}`);
119
+ }
120
+ fs.appendFileSync(process.env.GITHUB_ENV, `PLATFORM_PACKAGE_TARBALL=${process.env.RUNNER_TEMP}/${pack.filename}\n`);
121
+ EOF
122
+
123
+ - name: Scan through the TypeScript wrapper
124
+ # Installs the packed platform package. The wrapper must find the executable in
125
+ # `@expo/modules-macros-linux-<arch>`, as it would in a project, not the local build.
126
+ working-directory: ${{ github.workspace }}
127
+ run: |
128
+ npm install
129
+ npm install --no-save "$PLATFORM_PACKAGE_TARBALL"
130
+ npm run build:ts
131
+ node scripts/check-scan.js .
132
+
@@ -70,11 +70,147 @@ jobs:
70
70
  path: platforms/win32-${{ matrix.arch }}/ExpoModulesMacros.exe
71
71
  if-no-files-found: error
72
72
 
73
+ build-linux:
74
+ name: Build Linux tool (${{ matrix.arch }})
75
+ # SwiftPM builds macro tools only for the host, so each architecture builds on a runner of its own.
76
+ runs-on: ${{ matrix.runner }}
77
+ # The tool needs the glibc version that it's built against or a newer one. Red Hat UBI 9 has
78
+ # glibc 2.34, the oldest version that the package supports. The Linux workflow uses the same image.
79
+ container: swift:6.4.0-rhel-ubi9@sha256:802a003f1eacbca9c20664a8ba79621d13f4538c3d329e745b7f8d53c70604a3
80
+ timeout-minutes: 30
81
+ strategy:
82
+ matrix:
83
+ include:
84
+ - arch: x64
85
+ runner: ubuntu-24.04
86
+ - arch: arm64
87
+ runner: ubuntu-24.04-arm
88
+ steps:
89
+ - name: Checkout
90
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
91
+
92
+ - name: Setup Node
93
+ uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
94
+ with:
95
+ node-version: 24
96
+
97
+ - name: Build release tool
98
+ # Runs scripts/build.js: builds the release plugin binary for this architecture as
99
+ # platforms/linux-<arch>/ExpoModulesMacros.
100
+ run: npm run build
101
+
102
+ - name: Upload tool
103
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
104
+ with:
105
+ # Named after the package directory, like the Windows artifacts.
106
+ name: linux-${{ matrix.arch }}
107
+ path: platforms/linux-${{ matrix.arch }}/ExpoModulesMacros
108
+ if-no-files-found: error
109
+
110
+ verify-windows:
111
+ name: Verify Windows tool (${{ matrix.arch }})
112
+ needs: build-windows
113
+ # Installs the release tool from its packed package and scans through the TypeScript wrapper,
114
+ # before anything is published. The runner has no Swift toolchain.
115
+ runs-on: ${{ matrix.runner }}
116
+ timeout-minutes: 15
117
+ strategy:
118
+ matrix:
119
+ include:
120
+ - arch: x64
121
+ runner: windows-latest
122
+ - arch: arm64
123
+ runner: windows-11-arm
124
+ steps:
125
+ - name: Checkout
126
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
127
+
128
+ - name: Setup Node
129
+ uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
130
+ with:
131
+ node-version: 24
132
+
133
+ - name: Download tool
134
+ uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
135
+ with:
136
+ name: win32-${{ matrix.arch }}
137
+ path: platforms/win32-${{ matrix.arch }}
138
+
139
+ - name: Install the packed tool and scan
140
+ shell: pwsh
141
+ run: |
142
+ # The `./` matters: npm reads `platforms/win32-<arch>` as a GitHub repository.
143
+ $pack = npm pack "./platforms/win32-${{ matrix.arch }}" --pack-destination $env:RUNNER_TEMP --json | ConvertFrom-Json
144
+ if ($LASTEXITCODE -ne 0) { throw "npm pack failed" }
145
+ npm install
146
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
147
+ npm install --no-save (Join-Path $env:RUNNER_TEMP $pack[0].filename)
148
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
149
+ npm run build:ts
150
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
151
+ node scripts/check-scan.js .
152
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
153
+
154
+ verify-linux:
155
+ name: Verify Linux tool (${{ matrix.arch }})
156
+ needs: build-linux
157
+ # Installs the release tool from its packed package, then scans through the TypeScript wrapper in
158
+ # images of the oldest distribution releases that the package supports, before anything is
159
+ # published. The images have no Swift toolchain, and the runner's Node runs in them.
160
+ runs-on: ${{ matrix.runner }}
161
+ timeout-minutes: 15
162
+ defaults:
163
+ run:
164
+ shell: bash
165
+ strategy:
166
+ matrix:
167
+ include:
168
+ - arch: x64
169
+ runner: ubuntu-24.04
170
+ - arch: arm64
171
+ runner: ubuntu-24.04-arm
172
+ steps:
173
+ - name: Checkout
174
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
175
+
176
+ - name: Setup Node
177
+ uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
178
+ with:
179
+ node-version: 24
180
+
181
+ - name: Download tool
182
+ uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
183
+ with:
184
+ name: linux-${{ matrix.arch }}
185
+ path: platforms/linux-${{ matrix.arch }}
186
+
187
+ - name: Install the packed tool
188
+ run: |
189
+ # Artifacts don't keep file modes, so the executable gets its executable bit back first.
190
+ chmod +x platforms/linux-${{ matrix.arch }}/ExpoModulesMacros
191
+ # The `./` matters: npm reads `platforms/linux-<arch>` as a GitHub repository.
192
+ npm pack "./platforms/linux-${{ matrix.arch }}" --pack-destination "$RUNNER_TEMP" --json > "$RUNNER_TEMP/pack.json"
193
+ tarball="$(node -p 'JSON.parse(require("node:fs").readFileSync(process.argv[1], "utf8"))[0].filename' "$RUNNER_TEMP/pack.json")"
194
+ npm install
195
+ npm install --no-save "$RUNNER_TEMP/$tarball"
196
+ npm run build:ts
197
+
198
+ - name: Scan on each distribution
199
+ run: |
200
+ node_dir="$(dirname "$(dirname "$(command -v node)")")"
201
+ for image in ubuntu:22.04 debian:12 amazonlinux:2023; do
202
+ echo "::group::$image"
203
+ run=(docker run --rm -v "$PWD:/work:ro" -v "$node_dir:/node:ro" -w /work "$image")
204
+ "${run[@]}" getconf GNU_LIBC_VERSION
205
+ "${run[@]}" /node/bin/node scripts/check-scan.js .
206
+ echo "::endgroup::"
207
+ done
208
+
73
209
  publish:
74
210
  name: Build and publish to npm
75
- needs: build-windows
76
- # Builds the native macOS tool binary, so it must run on macOS. The Windows binaries come from the
77
- # build-windows jobs.
211
+ needs: [build-windows, build-linux, verify-windows, verify-linux]
212
+ # Builds the native macOS tool binary, so it must run on macOS. The Windows and Linux binaries come
213
+ # from the build-windows and build-linux jobs.
78
214
  # The package declares swift-tools-version 6.2, which requires Xcode 26+.
79
215
  runs-on: macos-26
80
216
  permissions:
@@ -151,25 +287,38 @@ jobs:
151
287
  # copies it to apple/ExpoModulesMacros, and strips it.
152
288
  run: npm run build
153
289
 
154
- - name: Download Windows tools
155
- # Each artifact goes into the directory of its name: `platforms/win32-<arch>`.
290
+ - name: Verify the macOS tool
291
+ # Scans through the TypeScript wrapper, which must find the binary in `apple/`, before
292
+ # anything is published. The verify-windows and verify-linux jobs check the other binaries.
293
+ run: |
294
+ npm run build:ts
295
+ node scripts/check-scan.js .
296
+
297
+ - name: Download Windows and Linux tools
298
+ # Each artifact goes into the directory of its name: `platforms/<platform>-<arch>`.
156
299
  uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
157
300
  with:
158
- pattern: win32-*
301
+ pattern: '{win32,linux}-*'
159
302
  path: platforms
160
303
 
161
- - name: Verify Windows tools
162
- run: ls -l platforms/win32-x64/ExpoModulesMacros.exe platforms/win32-arm64/ExpoModulesMacros.exe
304
+ - name: Verify Windows and Linux tools
305
+ # Artifacts don't keep file modes, so the Linux executables get their executable bit back
306
+ # here. `npm publish` keeps it in the package.
307
+ run: |
308
+ chmod +x platforms/linux-x64/ExpoModulesMacros platforms/linux-arm64/ExpoModulesMacros
309
+ ls -l \
310
+ platforms/win32-x64/ExpoModulesMacros.exe platforms/win32-arm64/ExpoModulesMacros.exe \
311
+ platforms/linux-x64/ExpoModulesMacros platforms/linux-arm64/ExpoModulesMacros
163
312
 
164
- - name: Publish the Windows packages
313
+ - name: Publish the Windows and Linux packages
165
314
  # Published before `expo-modules-macros`, whose optional dependencies point at them, so that
166
315
  # package is never on npm without them. Each package needs trusted publishing (OIDC)
167
316
  # configured on npmjs.com for this workflow.
168
317
  env:
169
318
  DRY_RUN: ${{ inputs.dry_run && '--dry-run' || '' }}
170
319
  run: |
171
- for arch in x64 arm64; do
172
- npm publish --access public --tag "${{ steps.bump.outputs.tag }}" $DRY_RUN "./platforms/win32-$arch"
320
+ for package in win32-x64 win32-arm64 linux-x64 linux-arm64; do
321
+ npm publish --access public --tag "${{ steps.bump.outputs.tag }}" $DRY_RUN "./platforms/$package"
173
322
  done
174
323
 
175
324
  - name: Publish
@@ -0,0 +1,76 @@
1
+ name: Swift Format
2
+
3
+ # Checks that the Swift sources are formatted with the pinned swift-format version.
4
+
5
+ on:
6
+ push:
7
+ branches: [main]
8
+ pull_request:
9
+ branches: [main]
10
+ workflow_dispatch: {}
11
+
12
+ concurrency:
13
+ group: ci-${{ github.workflow }}-${{ github.ref }}
14
+ cancel-in-progress: true
15
+
16
+ defaults:
17
+ run:
18
+ shell: bash
19
+
20
+ env:
21
+ # Keep in sync with the version in the Development section of README.md.
22
+ SWIFT_FORMAT_VERSION: '604.0.0'
23
+
24
+ jobs:
25
+ lint:
26
+ name: Lint
27
+ runs-on: ubuntu-24.04
28
+ # The same image as the Linux workflow. swift-format links the Swift runtime from it.
29
+ container: swift:6.4.0-rhel-ubi9@sha256:802a003f1eacbca9c20664a8ba79621d13f4538c3d329e745b7f8d53c70604a3
30
+ timeout-minutes: 30
31
+ steps:
32
+ - name: Checkout
33
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
34
+
35
+ - name: Setup Node
36
+ uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
37
+ with:
38
+ node-version: 24
39
+
40
+ - name: Restore swift-format
41
+ id: swift-format-cache
42
+ uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
43
+ with:
44
+ path: ~/.local/bin/swift-format
45
+ # The binary links the Swift runtime of the container image, so the key changes with this
46
+ # file, which sets the image.
47
+ key: swift-format-${{ runner.os }}-${{ env.SWIFT_FORMAT_VERSION }}-${{ hashFiles('.github/workflows/swift-format.yml') }}
48
+
49
+ - name: Build swift-format
50
+ if: steps.swift-format-cache.outputs.cache-hit != 'true'
51
+ run: |
52
+ set -euo pipefail
53
+ src="$(mktemp -d)"
54
+ git clone --depth 1 --branch "$SWIFT_FORMAT_VERSION" https://github.com/swiftlang/swift-format.git "$src"
55
+ cd "$src"
56
+ swift build -c release --product swift-format
57
+ mkdir -p ~/.local/bin
58
+ cp "$(swift build -c release --show-bin-path)/swift-format" ~/.local/bin/swift-format
59
+
60
+ - name: Save swift-format
61
+ # Saves the binary before the lint, so that a run with findings doesn't build it again.
62
+ if: steps.swift-format-cache.outputs.cache-hit != 'true'
63
+ uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
64
+ with:
65
+ path: ~/.local/bin/swift-format
66
+ key: ${{ steps.swift-format-cache.outputs.cache-primary-key }}
67
+
68
+ - name: Add swift-format to PATH
69
+ run: echo "$HOME/.local/bin" >> "$GITHUB_PATH"
70
+
71
+ - name: Lint
72
+ run: |
73
+ # The script lists the files with `git ls-files`. The checkout belongs to another user than
74
+ # the container's root user, so Git needs it marked as safe.
75
+ git config --global --add safe.directory "$GITHUB_WORKSPACE"
76
+ npm run swift:lint
@@ -1,7 +1,6 @@
1
1
  name: Windows
2
2
 
3
- # Builds and tests the package on Windows, and checks the release tool there. The Windows binaries
4
- # aren't published yet, so this runs apart from the macOS workflow.
3
+ # Builds and tests the package on Windows, and checks the release tool and its package there.
5
4
 
6
5
  on:
7
6
  push:
@@ -116,29 +115,5 @@ jobs:
116
115
  if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
117
116
  npm run build:ts
118
117
  if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
119
- $fixture = Join-Path $env:RUNNER_TEMP "scan-fixture"
120
- New-Item -ItemType Directory -Force $fixture | Out-Null
121
- Set-Content (Join-Path $fixture "Module.swift") "@ExpoModule`npublic final class WindowsModule {}"
122
- @'
123
- const path = require('node:path');
124
- const { getScannerBinaryPath, scanModules } = require(path.resolve('build'));
125
- const binaryPath = getScannerBinaryPath();
126
- const expected = path.join('node_modules', '@expo', `modules-macros-win32-${process.arch}`, 'ExpoModulesMacros.exe');
127
- if (!binaryPath.endsWith(expected)) {
128
- console.error(`getScannerBinaryPath() returned ${binaryPath}, not a path ending in ${expected}`);
129
- process.exit(1);
130
- }
131
- console.log(`getScannerBinaryPath() returned ${binaryPath}`);
132
- scanModules([process.argv[2]]).then((result) => {
133
- const module = result.modules.find((candidate) => candidate.name === 'WindowsModule');
134
- if (!module) {
135
- console.error(JSON.stringify(result, null, 2));
136
- process.exit(1);
137
- }
138
- console.log(`scanModules reported ${module.name} in ${module.file}`);
139
- }, (error) => {
140
- console.error(error);
141
- process.exit(1);
142
- });
143
- '@ | Set-Content (Join-Path $env:RUNNER_TEMP "scan-check.js")
144
- node (Join-Path $env:RUNNER_TEMP "scan-check.js") $fixture
118
+ node scripts/check-scan.js .
119
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
package/AGENTS.md CHANGED
@@ -9,10 +9,11 @@
9
9
  - `apple/Sources/ExpoModulesScanner`: the scanner library. `Modules/` implements `scan-modules` (autolinking), `Exports/` implements `scan-exports` (type generation), `Core/` holds the shared parsing and `#if` evaluation, and `CLI.swift` is the command-line front end.
10
10
  - `apple/Sources/ExpoModulesOptimized`: declarations for the `@OptimizedFunction` macro.
11
11
  - `apple/Tests`: `ExpoModulesMacrosTests` (expansion tests) and `ExpoModulesScannerTests`.
12
- - `platforms/`: the Windows packages `@expo/modules-macros-win32-x64` and `-arm64` (a `package.json` with `os` and `cpu`, a README and a copy of `LICENSE`). The Publish workflow runs `scripts/set-platform-versions.js` to give them the version of this package. Their `ExpoModulesMacros.exe` is written there by `npm run build` on Windows and isn't committed.
12
+ - `platforms/`: the Windows packages `@expo/modules-macros-win32-x64` and `-arm64`, and the Linux packages `@expo/modules-macros-linux-x64` and `-arm64` (a `package.json` with `os` and `cpu`, plus `libc` on Linux, a README and a copy of `LICENSE`). The Publish workflow runs `scripts/set-platform-versions.js` to give them the version of this package. Their `ExpoModulesMacros.exe` (Windows) or `ExpoModulesMacros` (Linux) is written there by `npm run build` on that platform and isn't committed.
13
13
  - `apple/PodTests`: a stub test so that `expo/expo` native tests can install this package as a pod. Do not add real tests here.
14
14
  - `src/`: the TypeScript wrapper around the scanner CLI. `types.ts` mirrors the Swift `Codable` output types by hand.
15
- - `scripts/build.js` (`npm run build`): builds the release binaries: the universal macOS `apple/ExpoModulesMacros` (committed and published), and on Windows `platforms/win32-<arch>/ExpoModulesMacros.exe` for the host architecture (not committed; published in that package).
15
+ - `scripts/build.js` (`npm run build`): builds the release binaries: the universal macOS `apple/ExpoModulesMacros` (committed and published), and on Windows and Linux `platforms/win32-<arch>/ExpoModulesMacros.exe` or `platforms/linux-<arch>/ExpoModulesMacros` for the host architecture (not committed; published in that package).
16
+ - `scripts/swift-format.js` (`npm run swift:format`, `npm run swift:lint`): runs swift-format on the `.swift` files that Git tracks or doesn't ignore, with the configuration in `.swift-format`.
16
17
 
17
18
  ## Commands
18
19
 
@@ -23,20 +24,29 @@ cd apple && swift build # debug build
23
24
  cd apple && swift test # all Swift tests
24
25
  npm run typecheck # TypeScript wrapper
25
26
  npm run build # release binary (slow; on macOS uses Rosetta for x86_64)
27
+ npm run swift:format # format the Swift sources in place
28
+ npm run swift:lint # check the formatting, as CI does
26
29
  ```
27
30
 
28
- CI runs on macOS (`.github/workflows/swift.yml`) and on Windows x64 and arm64 (`.github/workflows/windows.yml`). On macOS it runs the release build, checks both binary slices, then runs `swift test` and `npm run typecheck`. On Windows it builds and tests the package, builds the release binary, checks that it starts without the Swift runtime, packs it into its `platforms/` package (checking the files and the `os`/`cpu` fields), installs that package, and scans through the TypeScript wrapper, which must find the executable there. Code in `Sources` and `Tests` must not assume Apple platforms or POSIX paths.
31
+ CI runs on macOS (`.github/workflows/swift.yml`), on Windows x64 and arm64 (`.github/workflows/windows.yml`), and on Linux x64 and arm64 (`.github/workflows/linux.yml`). On macOS it runs the release build, checks both binary slices, then runs `swift test` and `npm run typecheck`. On Windows it builds and tests the package, builds the release binary, checks that it starts without the Swift runtime, packs it into its `platforms/` package (checking the files and the `os`/`cpu` fields), installs that package, and scans through the TypeScript wrapper with `scripts/check-scan.js`, which must find the executable there. On Linux it does the same in the `swift:6.4.0-rhel-ubi9` image, and also checks that no Swift library is linked dynamically, the newest glibc symbol version that the binary needs, and that the package keeps the executable bit. The Swift Format workflow (`.github/workflows/swift-format.yml`) builds the pinned swift-format version and runs `npm run swift:lint`. Code in `Sources` and `Tests` must not assume Apple platforms or POSIX paths.
29
32
 
30
33
  ## Windows
31
34
 
32
35
  - Outside Apple platforms, `Sources` import `FoundationEssentials` instead of `Foundation` (`#if canImport(FoundationEssentials)`). Full Foundation brings in ICU, which would add tens of megabytes to the statically linked Windows binary. Don't use APIs that only `Foundation` has (`FileHandle`, `NSRegularExpression`, `CharacterSet`, `FileManager.enumerator`, `NSString` path methods); `Core/StandardStreams.swift` writes to stdout and stderr.
33
36
  - The Windows binaries link the Swift runtime statically (`-static-stdlib`), because Swift has no stable ABI on Windows and the scanner must also run without a Swift toolchain. SwiftPM's `--static-swift-stdlib` has no effect there.
34
37
 
38
+ ## Linux
39
+
40
+ - The `FoundationEssentials` rule above applies on Linux too.
41
+ - The Linux binaries link the Swift runtime statically (`-static-stdlib`; SwiftPM's `--static-swift-stdlib` doesn't apply to the macro tool here either) but link glibc and the GNU C++ runtime dynamically, so they need the glibc version they were built against or a newer one. CI and the Publish workflow build them in `swift:6.4.0-rhel-ubi9` (glibc 2.34), and the Linux workflow fails when the binary needs a newer glibc symbol than `MAX_GLIBC_VERSION`. A change to the build image must keep these and the glibc version in the READMEs in sync.
42
+
35
43
  ## Conventions
36
44
 
37
- - Swift uses 2-space indentation. Write new doc comments as `///` line comments, not `/** */` blocks.
45
+ - swift-format formats the Swift sources. After you change Swift code, run `npm run swift:format` and then `npm run swift:lint`, which also reports the rules that the formatter doesn't fix. `.swift-format` is a copy of the configuration in `expo/expo`, so keep the two the same. Each repository pins its own swift-format version.
46
+ - The scripts run the swift-format on `PATH`, or on macOS the one in Xcode when there is none. CI builds the version in `SWIFT_FORMAT_VERSION` (`.github/workflows/swift-format.yml`, also named in the README), which can format differently from the one in Xcode. When CI reports a finding that the local lint doesn't, fix it as CI reports it.
47
+ - Write new doc comments as `///` line comments, not `/** */` blocks.
38
48
  - Tests use Swift Testing (`import Testing`, `@Test`) with backtick-quoted descriptive names, for example ``func `Prunes .build, Pods, .git, and node_modules directories`()``.
39
- - Macro tests compare full expansions with `assertMacroExpansion` from `SwiftSyntaxMacrosGenericTestSupport`, using `indentationWidth: .spaces(2)`. Each test file wraps it in a local `assertExpansion` helper.
49
+ - Macro tests compare full expansions with `assertMacroExpansion` from `SwiftSyntaxMacrosGenericTestSupport`, using `indentationWidth: .spaces(2)`. Each test file wraps it in a local `assertExpansion` helper that passes `recordIssue` (`IssueRecording.swift`) as the failure handler.
40
50
  - Scanner tests reach internal API with `@testable import ExpoModulesScanner`. Do not make scanner types `public` only for tests.
41
51
  - `@JS` applies to functions, properties and initializers. Do not write code or comments that assume it marks only functions.
42
52
  - Code comments describe the code as it is. Do not refer to open PRs, branches or planned work.
@@ -44,7 +54,7 @@ CI runs on macOS (`.github/workflows/swift.yml`) and on Windows x64 and arm64 (`
44
54
  ## Changing macros
45
55
 
46
56
  - The scanner re-implements the macros' naming and member rules on the syntax tree; it does not call the macro code. When a macro changes which members it binds or how it names them (the `@JS` name override, the `on` prefix `@Event` strips, which properties `@Record` treats as fields), change `scan-exports` to match, or generated TypeScript types drift from the runtime.
47
- - The scanner only parses files that match the pre-filter built by `macroAttributeRegex` in `Core/SourceScan.swift`. A new attribute or conformance the scanner must detect has to be added there, or files that use it are skipped without an error.
57
+ - The scanner only parses files that pass the pre-filter, `MacroPrefilter` in `Core/SourceScan.swift`. Each command builds it from the `macros` and `conformances` arguments of its `scanFiles` call. A new attribute or conformance the scanner must detect has to be added to those arguments (an attribute also needs a `DetectedMacro` case), or files that use it are skipped without an error.
48
58
  - A new macro needs: the implementation in `apple/Sources/ExpoModulesMacros`, an entry in `providingMacros` in `Plugin.swift`, an expansion test file, the `#externalMacro` declaration in `expo-modules-core`, and an entry in the macro list in `README.md`.
49
59
 
50
60
  ## Contracts with other packages
@@ -52,14 +62,14 @@ CI runs on macOS (`.github/workflows/swift.yml`) and on Windows x64 and arm64 (`
52
62
  - **Macro declarations live in `expo-modules-core`** (`ios/Core/ExpoModulesMacros.swift` in `expo/expo`), as `#externalMacro(module: "ExpoModulesMacros", type: ...)`. Adding a macro, renaming a macro type or changing a macro's signature needs a matching edit there, and the type must be listed in `providingMacros` in `Plugin.swift`.
53
63
  - **Generated code calls `expo-modules-core` API.** An expansion that uses a new core symbol only works with a core version that has it.
54
64
  - **Scanner output is versioned.** `scanModulesSchemaVersion` (`Modules/ScanModules.swift`) and `scanExportsSchemaVersion` (`Exports/ExportedSurface.swift`) change independently. When the output shape of a command changes, bump its version and update the matching `SUPPORTED_SCAN_*_SCHEMA_VERSION` constant and mirror types in `src/types.ts`. `expo-modules-autolinking` also checks the `scan-modules` version.
55
- - **The binary path is part of the contract.** `expo-modules-autolinking` resolves this package from `expo-modules-core` and passes `-load-plugin-executable <package>/apple/<binary>#ExpoModulesMacros` to the compiler. The scanner wrapper in autolinking uses the same path, and `expo-modules-cli` uses `getScannerBinaryPath()`. On Windows the binary is `ExpoModulesMacros.exe` in `@expo/modules-macros-win32-<arch>`. Renaming or moving a binary needs a matching change in `expo/expo`.
65
+ - **The binary path is part of the contract.** `expo-modules-autolinking` resolves this package from `expo-modules-core` and passes `-load-plugin-executable <package>/apple/<binary>#ExpoModulesMacros` to the compiler. The scanner wrapper in autolinking uses the same path, and `expo-modules-cli` uses `getScannerBinaryPath()`. On Windows the binary is `ExpoModulesMacros.exe` in `@expo/modules-macros-win32-<arch>`, and on Linux `ExpoModulesMacros` in `@expo/modules-macros-linux-<arch>`. Renaming or moving a binary needs a matching change in `expo/expo`.
56
66
 
57
67
  ## Releases
58
68
 
59
- - Do not rebuild or commit the binary under `apple/` in a feature change. Only the **Publish** workflow (`.github/workflows/publish.yml`, manual) rebuilds it, and it commits the result as `Release vX.Y.Z` after the npm publish succeeds. It also builds the Windows binaries and publishes them in the `platforms/` packages, with the same version.
69
+ - Do not rebuild or commit the binary under `apple/` in a feature change. Only the **Publish** workflow (`.github/workflows/publish.yml`, manual) rebuilds it, and it commits the result as `Release vX.Y.Z` after the npm publish succeeds. It also builds the Windows and Linux binaries and publishes them in the `platforms/` packages, with the same version. Before it publishes anything, it installs each binary from its packed package and scans through the TypeScript wrapper with `scripts/check-scan.js`.
60
70
  - Do not edit the `version` in `package.json` by hand. The Publish workflow bumps it.
61
71
  - Commit and PR titles are short and imperative, with code identifiers in backticks, for example ``Report `@Union` types in `scan-exports` ``.
62
72
 
63
73
  ## Installed copies
64
74
 
65
- In `node_modules`, the compiler runs the prebuilt binary `apple/ExpoModulesMacros` (on Windows, `ExpoModulesMacros.exe` from `@expo/modules-macros-win32-<arch>`); editing the Swift sources there has no effect. Macro and scanner fixes belong in this repository and ship in a new release. The TypeScript wrapper ships compiled in `build/`, since `src/` is not published.
75
+ In `node_modules`, the compiler runs the prebuilt binary `apple/ExpoModulesMacros` (on Windows and Linux, the binary from `@expo/modules-macros-<platform>-<arch>`); editing the Swift sources there has no effect. Macro and scanner fixes belong in this repository and ship in a new release. The TypeScript wrapper ships compiled in `build/`, since `src/` is not published.
package/README.md CHANGED
@@ -15,15 +15,20 @@ The same executable doubles as a source scanner CLI.
15
15
 
16
16
  This package is not meant to be installed directly. It is a dependency of `expo-modules-core`, so every Expo project already has it. The published package contains a prebuilt universal (arm64 + x86_64) macOS binary at `apple/ExpoModulesMacros`, so consumers never build the plugin themselves.
17
17
 
18
- The Windows binaries ship in their own packages, `@expo/modules-macros-win32-x64` and `@expo/modules-macros-win32-arm64`, each with an `ExpoModulesMacros.exe`. This package lists them as optional dependencies, and their `os` and `cpu` fields make npm, pnpm and Yarn install only the one that matches a Windows machine, so other machines don't download them. The Swift runtime is linked in statically, so the executables run without a Swift toolchain; they need only Windows system libraries and the Microsoft Visual C++ runtime.
18
+ The Windows and Linux binaries ship in their own packages, one per platform and architecture:
19
+
20
+ - `@expo/modules-macros-win32-x64` and `@expo/modules-macros-win32-arm64`, each with an `ExpoModulesMacros.exe`. They need only Windows system libraries and the Microsoft Visual C++ runtime.
21
+ - `@expo/modules-macros-linux-x64` and `@expo/modules-macros-linux-arm64`, each with an `ExpoModulesMacros`. They need glibc 2.34 or newer and the GNU C++ runtime (`libstdc++` and `libgcc_s`), for example on Ubuntu 22.04, Debian 12, Red Hat Enterprise Linux 9 or Amazon Linux 2023. They don't run on musl distributions such as Alpine Linux.
22
+
23
+ This package lists them as optional dependencies, and their `os`, `cpu` and (on Linux) `libc` fields make npm, pnpm and Yarn install only the one that matches the machine, so other machines don't download them. The Swift runtime is linked in statically, so the executables run without a Swift toolchain.
19
24
 
20
25
  # Macros
21
26
 
22
27
  - **`@ExpoModule(_ name: String? = nil, classes: [Any.Type] = [])`** on a class. Turns the class into a module: binds its `@JS` members into the module's JavaScript object and resolves the module name from the argument, falling back to the class name. It also synthesizes everything inheriting from `Module` used to provide, so a module class can carry any superclass, or none.
23
- - **`@JS(_ jsName: String? = nil, _ options: JSOptions...)`** on a member of a module or shared object. Marks a function, property or initializer for export. `@ExpoModule` and `@SharedObject` bind each marked member straight into the JavaScript object, with the argument decoding, the call and the result encoding inlined, so there is no dynamic per-call path. The JS name defaults to the Swift name; pass a string to override it. The macro also checks that every type crossing the boundary is convertible in the direction it travels, so a bad type is reported on the author's own declaration rather than on the enclosing type. The `.concurrent` option moves an `async` body off the JavaScript thread while still decoding and encoding on it. A function or initializer can take a closure argument, such as `(Point) throws -> Size` or `(String) async throws -> Data`. JavaScript passes a function for it, and native code can store the closure and call it from any thread. A closure that throws or returns a value blocks the caller until JavaScript returns, and an `async` closure suspends instead and awaits a promise that the function returns. A closure that returns a value must be `throws`, so that an error from JavaScript can reach the caller. A non-throwing `Void` closure returns before JavaScript reads its arguments, so don't change a class instance after passing it to one.
28
+ - **`@JS(_ jsName: String? = nil, _ options: JSOptions...)`** on a member of a module or shared object. Marks a function, property or initializer for export. `@ExpoModule` and `@SharedObject` bind each marked member straight into the JavaScript object, with the argument decoding, the call and the result encoding inlined, so there is no dynamic per-call path. The JS name defaults to the Swift name; pass a string to override it. Two `@JS` members with the same JS name on one JavaScript object are a compile-time error, since JavaScript could reach only one of them. A shared object's static and instance members bind onto different objects, so they can share a name. The macro also checks that every type crossing the boundary is convertible in the direction it travels, so a bad type is reported on the author's own declaration rather than on the enclosing type. The `.concurrent` option moves an `async` body off the JavaScript thread while still decoding and encoding on it. The `.withAsync("name")` option binds a synchronous function a second time under that name, as a function that returns a promise and runs the body on the concurrent thread pool. That function isn't isolated to the JavaScript thread, so its body must be safe to run on any thread. The macro reports a function isolated to a global actor through its own attribute or its type's. When the isolation comes from elsewhere, such as a protocol or the module's default isolation, the error points into the generated code, so mark the function `nonisolated`. A function or initializer can take a closure argument, such as `(Point) throws -> Size` or `(String) async throws -> Data`. JavaScript passes a function for it, and native code can store the closure and call it from any thread. A closure that throws or returns a value blocks the caller until JavaScript returns, and an `async` closure suspends instead and awaits a promise that the function returns. A closure that returns a value must be `throws`, so that an error from JavaScript can reach the caller. A non-throwing `Void` closure returns before JavaScript reads its arguments, so don't change a class instance after passing it to one.
24
29
  - **`@Event(_ name: String? = nil, sync: Bool = false)`** on a function-typed `var`. Expands the property into a closure that emits the event, so calling the property sends it to JavaScript with the closure's parameter as the payload. The JS name defaults to the property name with a leading `on` stripped. `sync: true` dispatches inline instead of asynchronously.
25
30
  - **`@SharedObject(_ name: String? = nil)`** on a `SharedObject` subclass. Collects the class's `@JS` members into a class definition that a module exposes through `@ExpoModule(classes:)`.
26
- - **`@Record()`** on a record type. Treats every non-static, non-private, non-computed stored property as a field, with no per-field wrapper, and synthesizes the memberwise initializer, the conversions in both directions, and the `Record` conformance. Requiredness is inferred: a default value makes a field optional, an optional type makes it nullable and optional.
31
+ - **`@Record()`** on a struct, or on a final class with no inheritance clause (declare a class's protocol conformances in an extension). Treats every non-static, non-private, non-computed stored property as a field, with no per-field wrapper, and synthesizes the memberwise initializer, the conversions in both directions, and the `Record` conformance. Requiredness is inferred: a default value makes a field optional, an optional type makes it nullable and optional.
27
32
  - **`@Union()`** on an enum whose cases each carry one associated value. Models a TypeScript union. Synthesizes the conversions in both directions, a typed accessor per payload type, and the `JavaScriptDecodable` and `JavaScriptEncodable` conformances. Decoding is ordered: the first case whose payload decodes wins, so the more specific case goes first.
28
33
 
29
34
  How that looks in a module:
@@ -97,13 +102,13 @@ The binary is a compiled executable, so each call still spawns a process. What t
97
102
 
98
103
  to `OTHER_SWIFT_FLAGS` for `ExpoModulesCore`, every pod that depends on it, and their test specs. Expo's SPM prebuilds pass the same flag when they generate `Package.swift`, so both build systems load the same binary.
99
104
 
100
- On Windows, the compiler loads `ExpoModulesMacros.exe` from `@expo/modules-macros-win32-<arch>` with the same flag. `getScannerBinaryPath()` in the TypeScript wrapper returns the binary for the current platform and architecture. On Windows it falls back to the local `npm run build` output in `platforms/win32-<arch>` when the platform package isn't installed, for example in this repository.
105
+ On Windows and Linux, the compiler loads `ExpoModulesMacros.exe` from `@expo/modules-macros-win32-<arch>` or `ExpoModulesMacros` from `@expo/modules-macros-linux-<arch>` with the same flag. `getScannerBinaryPath()` in the TypeScript wrapper returns the binary for the current platform and architecture. On Windows and Linux it falls back to the local `npm run build` output in `platforms/<platform>-<arch>` when the platform package isn't installed, for example in this repository.
101
106
 
102
107
  The module and type names in `#externalMacro` must stay in sync with `apple/Sources/ExpoModulesMacros/Plugin.swift`.
103
108
 
104
109
  # Development
105
110
 
106
- Requires a toolchain with Swift 6.2 or newer: on macOS 13 or newer that means Xcode 26 or newer, and on Windows the Swift toolchain from swift.org (CI uses 6.4).
111
+ Requires a toolchain with Swift 6.2 or newer: on macOS 13 or newer that means Xcode 26 or newer, and on Windows and Linux the Swift toolchain from swift.org (CI uses 6.4).
107
112
 
108
113
  ```sh
109
114
  cd apple
@@ -111,20 +116,34 @@ swift build
111
116
  swift test
112
117
  ```
113
118
 
119
+ The Swift sources are formatted with [swift-format](https://github.com/swiftlang/swift-format), using the same configuration (`.swift-format`) as the `expo/expo` repository. `npm run swift:format` formats the `.swift` files that Git tracks or doesn't ignore in place, and `npm run swift:lint` checks them without changes, as CI does.
120
+
121
+ The scripts run the `swift-format` on `PATH`, or on macOS the one in the selected Xcode when there is none on `PATH`. CI uses swift-format `604.0.0`, which can format differently from the one in Xcode. To get the version that CI uses, build it from source:
122
+
123
+ ```sh
124
+ git clone --depth 1 --branch 604.0.0 https://github.com/swiftlang/swift-format.git /tmp/swift-format
125
+ (cd /tmp/swift-format && swift build -c release --product swift-format)
126
+ mkdir -p ~/.local/bin
127
+ cp "$(cd /tmp/swift-format && swift build -c release --show-bin-path)/swift-format" ~/.local/bin/
128
+ ```
129
+
130
+ Then add `~/.local/bin` to `PATH`.
131
+
114
132
  `npm run build` runs `scripts/build.js`, which builds the release binary with SwiftPM's native build system (Swift Build, the default since Swift 6.4, doesn't build a macro tool that no target in the package uses).
115
133
 
116
134
  - On macOS, it builds for arm64 and x86_64, merges the slices into `apple/ExpoModulesMacros` with `lipo`, strips it, and verifies both slices are present. SwiftPM only builds macro tools for the host architecture, so the x86_64 slice is produced by running the toolchain under Rosetta; the script installs Rosetta if it is missing.
117
135
  - On Windows, it builds for the host architecture only, with the Swift runtime linked in statically and without debug info, and writes `platforms/win32-<arch>/ExpoModulesMacros.exe` (`x64` or `arm64`, as Node's `process.arch` names them), the file that the package for that architecture publishes. It strips the executable with `llvm-strip` from the Swift toolchain and checks the architecture in its header.
136
+ - On Linux, it also builds for the host architecture only, with the Swift runtime linked in statically, and writes `platforms/linux-<arch>/ExpoModulesMacros`. It strips the executable with `strip` and checks the architecture in its ELF header. The executable needs the glibc version of the machine it's built on or a newer one, so CI and the Publish workflow build it in the `swift:6.4.0-rhel-ubi9` image (Red Hat UBI 9, glibc 2.34).
118
137
 
119
- The macOS binary is committed to the repository. The Windows binaries aren't: the Publish workflow builds them and puts them into the packages in `platforms/`.
138
+ The macOS binary is committed to the repository. The Windows and Linux binaries aren't: the Publish workflow builds them and puts them into the packages in `platforms/`.
120
139
 
121
140
  # Releasing
122
141
 
123
142
  The **Publish** workflow is manual (`workflow_dispatch`) and takes a release type. It bumps the version, builds the universal binary, and publishes to npm through OIDC trusted publishing. The commit, tag and GitHub release are created only after the publish succeeds, so a failed build leaves the branch untouched.
124
143
 
125
- Windows jobs (x64 and arm64) build the `.exe` files first. After the version bump, `scripts/set-platform-versions.js` gives the packages in `platforms/` the same version and lists them in this package's `optionalDependencies` with that exact version. The workflow publishes the two Windows packages before `expo-modules-macros`, so this package is never on npm without them. Each package needs trusted publishing configured on npmjs.com for the Publish workflow.
144
+ Windows and Linux jobs (x64 and arm64 for each) build their binaries first. Verify jobs then install each binary from its packed package and scan through the TypeScript wrapper with `scripts/check-scan.js`, on Windows runners without Swift and, for Linux, in the Ubuntu 22.04, Debian 12 and Amazon Linux 2023 images. The publish job does the same for the macOS binary, so a failed check stops the release before anything is published. After the version bump, `scripts/set-platform-versions.js` gives the packages in `platforms/` the same version and lists them in this package's `optionalDependencies` with that exact version. The workflow publishes the four platform packages before `expo-modules-macros`, so this package is never on npm without them. Each package needs trusted publishing configured on npmjs.com for the Publish workflow.
126
145
 
127
- Prerelease versions (`prepatch`, `preminor`, `premajor`, `prerelease`) are published with the `next` dist-tag, and the others with `latest`. With **Dry run** checked, the workflow builds everything and runs `npm publish --dry-run` for the three packages, but doesn't publish, commit, tag or create a release. Use it to check a change to the workflow.
146
+ Prerelease versions (`prepatch`, `preminor`, `premajor`, `prerelease`) are published with the `next` dist-tag, and the others with `latest`. With **Dry run** checked, the workflow builds everything and runs `npm publish --dry-run` for the five packages, but doesn't publish, commit, tag or create a release. Use it to check a change to the workflow.
128
147
 
129
148
  # Contributing
130
149
 
Binary file