Skip to content

Commit 91a6069

Browse files
authored
Merge pull request #600 from mCodex/fix/build
Add release verification scripts and proxy shims
2 parents 7293ef4 + fc4de06 commit 91a6069

5 files changed

Lines changed: 224 additions & 2 deletions

File tree

errors/package.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"//": "Compatibility shim for bundlers that don't honor the package `exports` field (e.g. older Re.Pack/rspack/Metro setups). The canonical resolution path is the `./errors` entry in the root package.json `exports` map.",
3+
"name": "react-native-sensitive-info/errors",
4+
"types": "../lib/typescript/commonjs/src/errors.d.ts",
5+
"main": "../lib/commonjs/errors.js",
6+
"module": "../lib/module/errors.js",
7+
"react-native": "../lib/module/errors.js",
8+
"sideEffects": false
9+
}

hooks/package.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"//": "Compatibility shim for bundlers that don't honor the package `exports` field (e.g. older Re.Pack/rspack/Metro setups). The canonical resolution path is the `./hooks` entry in the root package.json `exports` map.",
3+
"name": "react-native-sensitive-info/hooks",
4+
"types": "../lib/typescript/commonjs/src/hooks/index.d.ts",
5+
"main": "../lib/commonjs/hooks/index.js",
6+
"module": "../lib/module/hooks/index.js",
7+
"react-native": "../lib/module/hooks/index.js",
8+
"sideEffects": false
9+
}

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"typecheck": "tsc --noEmit",
4949
"clean": "git clean -dfX",
5050
"release": "release-it",
51+
"release:prepare": "npm run codegen && node scripts/verify-release-artifacts.js && node scripts/smoke-test-release.js",
5152
"build": "npm run typecheck && bob build",
5253
"codegen": "nitrogen --logLevel=\"debug\" && npm run build && node post-script.js",
5354
"lint": "biome check --write .",
@@ -71,6 +72,8 @@
7172
"src",
7273
"react-native.config.js",
7374
"lib",
75+
"hooks",
76+
"errors",
7477
"nitrogen",
7578
"cpp",
7679
"nitro.json",
@@ -185,8 +188,7 @@
185188
},
186189
"hooks": {
187190
"before:init": [
188-
"npm run typecheck",
189-
"npm run build"
191+
"npm run release:prepare"
190192
]
191193
},
192194
"plugins": {

scripts/smoke-test-release.js

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
#!/usr/bin/env node
2+
/**
3+
* End-to-end pre-release smoke test.
4+
*
5+
* Packs the package with `npm pack`, installs the tarball into a throwaway
6+
* project, and verifies the consumer-facing surface:
7+
* 1. The published tarball contains nitrogen native bindings + JS subpath
8+
* proxy `package.json` files (catches "missing autolinking.rb" type bugs).
9+
* 2. Node's CJS resolver (which honors the package `exports` map) can
10+
* resolve every documented entry point.
11+
* 3. The same subpaths also resolve via the legacy `main`/`module`/
12+
* `react-native` fields in the proxy directories — this is what bundlers
13+
* that ignore `exports` (older Re.Pack/rspack/Metro setups) rely on.
14+
* 4. The podspec and the generated iOS autolinking.rb have valid Ruby
15+
* syntax, so `pod install` will not blow up at parse time.
16+
*
17+
* Designed to run inside `release-it`'s `before:init` hook so a broken
18+
* release is caught before the npm publish + git push.
19+
*/
20+
const { execSync } = require('node:child_process')
21+
const fs = require('node:fs')
22+
const os = require('node:os')
23+
const path = require('node:path')
24+
25+
const ROOT = path.resolve(__dirname, '..')
26+
const PKG = require(path.join(ROOT, 'package.json'))
27+
28+
const REQUIRED_TARBALL_ENTRIES = [
29+
'package/nitrogen/generated/ios/SensitiveInfo+autolinking.rb',
30+
'package/nitrogen/generated/android/SensitiveInfo+autolinking.gradle',
31+
'package/hooks/package.json',
32+
'package/errors/package.json',
33+
'package/lib/commonjs/index.js',
34+
'package/lib/module/index.js',
35+
'package/lib/commonjs/hooks/index.js',
36+
'package/lib/module/hooks/index.js',
37+
'package/lib/commonjs/errors.js',
38+
'package/lib/module/errors.js',
39+
]
40+
41+
const SUBPATHS = [
42+
'react-native-sensitive-info',
43+
'react-native-sensitive-info/hooks',
44+
'react-native-sensitive-info/errors',
45+
'react-native-sensitive-info/package.json',
46+
]
47+
48+
const PROXY_DIRS = ['hooks', 'errors']
49+
50+
const run = (cmd, opts = {}) =>
51+
execSync(cmd, { stdio: ['ignore', 'pipe', 'pipe'], ...opts })
52+
.toString()
53+
.trim()
54+
55+
const fail = (msg) => {
56+
console.error(`\n[smoke-test-release] ❌ ${msg}\n`)
57+
process.exit(1)
58+
}
59+
60+
const log = (msg) => console.log(`[smoke-test-release] ${msg}`)
61+
62+
// 1. Pack the tarball.
63+
log('Packing tarball with `npm pack`…')
64+
const tarballName = run('npm pack --silent', { cwd: ROOT }).split('\n').pop()
65+
const tarballPath = path.join(ROOT, tarballName)
66+
67+
try {
68+
// 2. Verify required entries are present.
69+
const entries = run(`tar -tzf ${tarballName}`, { cwd: ROOT }).split('\n')
70+
const missing = REQUIRED_TARBALL_ENTRIES.filter((e) => !entries.includes(e))
71+
if (missing.length > 0) {
72+
fail(
73+
`Tarball is missing required entries:\n - ${missing.join('\n - ')}\n\nRun \`npm run codegen\` and rebuild before releasing.`
74+
)
75+
}
76+
log(
77+
`Tarball contains all ${REQUIRED_TARBALL_ENTRIES.length} required entries.`
78+
)
79+
80+
// 3. Install the tarball into a throwaway project.
81+
const sandbox = fs.mkdtempSync(path.join(os.tmpdir(), 'rnsi-smoke-'))
82+
fs.writeFileSync(
83+
path.join(sandbox, 'package.json'),
84+
JSON.stringify({ name: 'rnsi-smoke', version: '0.0.0', private: true })
85+
)
86+
log(`Installing tarball into ${sandbox}…`)
87+
run(`npm install --silent --no-save ${tarballPath}`, { cwd: sandbox })
88+
89+
// 4. Verify Node's CJS resolver finds every documented subpath
90+
// (this exercises the `exports` map).
91+
for (const subpath of SUBPATHS) {
92+
try {
93+
run(`node -e "require.resolve('${subpath}')"`, { cwd: sandbox })
94+
log(`exports map resolves: ${subpath}`)
95+
} catch (err) {
96+
fail(`exports map cannot resolve "${subpath}":\n${err.message}`)
97+
}
98+
}
99+
100+
// 5. Verify the legacy main/module/react-native proxies still point at
101+
// real files. Bundlers that ignore `exports` rely on these.
102+
for (const sub of PROXY_DIRS) {
103+
const proxyPath = path.join(
104+
sandbox,
105+
'node_modules',
106+
PKG.name,
107+
sub,
108+
'package.json'
109+
)
110+
const proxy = JSON.parse(fs.readFileSync(proxyPath, 'utf8'))
111+
for (const field of ['main', 'module', 'react-native']) {
112+
const target = proxy[field]
113+
if (typeof target !== 'string') {
114+
fail(`Proxy ${sub}/package.json missing string "${field}" field.`)
115+
}
116+
const resolved = path.resolve(path.dirname(proxyPath), target)
117+
if (!fs.existsSync(resolved)) {
118+
fail(
119+
`Proxy ${sub}/package.json "${field}" → ${target} does not resolve to a file (${resolved}).`
120+
)
121+
}
122+
}
123+
log(`legacy field proxy resolves: ${sub}/`)
124+
}
125+
126+
// 6. Validate Ruby syntax for podspec + autolinking.rb so `pod install`
127+
// won't fail with a parse error on consumer machines.
128+
const podspecPath = path.join(ROOT, 'SensitiveInfo.podspec')
129+
const autolinkingPath = path.join(
130+
sandbox,
131+
'node_modules',
132+
PKG.name,
133+
'nitrogen/generated/ios/SensitiveInfo+autolinking.rb'
134+
)
135+
try {
136+
run(`ruby -c "${podspecPath}"`)
137+
run(`ruby -c "${autolinkingPath}"`)
138+
log('podspec + autolinking.rb pass `ruby -c`.')
139+
} catch (err) {
140+
fail(`Ruby syntax check failed:\n${err.stderr?.toString() ?? err.message}`)
141+
}
142+
143+
// 7. Cleanup sandbox.
144+
fs.rmSync(sandbox, { recursive: true, force: true })
145+
146+
console.log('\n[smoke-test-release] ✅ Release candidate looks healthy.\n')
147+
} finally {
148+
// Always remove the local tarball — release-it will pack again at publish time.
149+
if (fs.existsSync(tarballPath)) fs.unlinkSync(tarballPath)
150+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#!/usr/bin/env node
2+
/**
3+
* Verifies that all release artifacts exist before publishing.
4+
*
5+
* Run as part of `release-it`'s `before:init` hook (after `npm run codegen`)
6+
* to guarantee that the published tarball contains the native bindings and
7+
* compiled JS. If any artifact is missing the release is aborted with a
8+
* non-zero exit code.
9+
*/
10+
const fs = require('node:fs')
11+
const path = require('node:path')
12+
13+
const ROOT = path.resolve(__dirname, '..')
14+
15+
// Paths are relative to the repo root.
16+
const REQUIRED_ARTIFACTS = [
17+
// Nitro-generated native bindings (regenerated by `npm run codegen`).
18+
'nitrogen/generated/ios/SensitiveInfo+autolinking.rb',
19+
'nitrogen/generated/android/SensitiveInfo+autolinking.gradle',
20+
'nitrogen/generated/shared/c++/HybridSensitiveInfoSpec.hpp',
21+
22+
// Compiled JS entry points consumed via the package `exports` map.
23+
'lib/commonjs/index.js',
24+
'lib/module/index.js',
25+
'lib/commonjs/hooks/index.js',
26+
'lib/module/hooks/index.js',
27+
'lib/commonjs/errors.js',
28+
'lib/module/errors.js',
29+
30+
// Subpath proxy `package.json` files for bundlers without `exports` support.
31+
'hooks/package.json',
32+
'errors/package.json',
33+
]
34+
35+
const missing = REQUIRED_ARTIFACTS.filter(
36+
(rel) => !fs.existsSync(path.join(ROOT, rel))
37+
)
38+
39+
if (missing.length > 0) {
40+
console.error('\n[verify-release-artifacts] Missing required artifacts:')
41+
for (const rel of missing) {
42+
console.error(` - ${rel}`)
43+
}
44+
console.error(
45+
'\nRun `npm run codegen` and ensure the build succeeded before releasing.\n'
46+
)
47+
process.exit(1)
48+
}
49+
50+
console.log(
51+
`[verify-release-artifacts] OK — ${REQUIRED_ARTIFACTS.length} artifacts present.`
52+
)

0 commit comments

Comments
 (0)