one
Version:
One is a React framework for web, iOS, and Android apps.
80 lines (75 loc) • 3.53 kB
text/typescript
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
import { join, resolve } from 'node:path'
import { validateNativeApp } from '../native/appManifest'
import { loadUserOneOptions } from '../vite/loadConfig'
// default react-native config: xcodebuild's bundle phase and gradle's react
// block both invoke `react-native bundle` via vxrn's community cli, which
// loads its `bundle` command override from here. without it the stock bundle
// command runs: it rejects the --config-cmd xcode passes and needs a
// metro.config.js One apps do not have. constant bytes, no per-app inputs.
const REACT_NATIVE_CONFIG = `module.exports = require('one/react-native-config')\n`
const REACT_NATIVE_CONFIG_NAMES = [
'react-native.config.js',
'react-native.config.cjs',
'react-native.config.mjs',
'react-native.config.ts',
]
export async function run(args: { platform?: string; 'no-install'?: boolean }) {
const root = process.cwd()
const packageJson = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'))
const hasExpo =
Object.hasOwn(packageJson.dependencies ?? {}, 'expo') ||
Object.hasOwn(packageJson.devDependencies ?? {}, 'expo')
if (hasExpo) {
throw new Error(
'[one] one prebuild only generates Expo-free projects. This app declares expo; run Expo prebuild and list "vxrn/expo-plugin" in the Expo config.'
)
}
const { oneOptions } = await loadUserOneOptions('build', true)
const native = oneOptions?.native
const app = typeof native === 'object' ? native.app : undefined
if (!app) {
throw new Error(
'[one] native.app is required: configure one({ native: { app } }) with name, ios.bundleId, and android.applicationId'
)
}
validateNativeApp(app, args.platform)
if ((app.ios?.backgroundTasks || app.ios?.appIntents) && args.platform !== 'android') {
const configured = oneOptions?.setupFile
const setupFile = typeof configured === 'string'
? configured
: configured && 'native' in configured
? configured.native
: configured && 'ios' in configured
? configured.ios
: undefined
if (!setupFile || !existsSync(resolve(root, setupFile))) {
throw new Error(
app.ios?.backgroundTasks
? '[one] native.app.ios.backgroundTasks requires an existing iOS native setupFile that defines every task handler before the app renders'
: '[one] native.app.ios.appIntents requires an existing iOS native setupFile that defines every action handler before the app renders'
)
}
}
// an app-owned react-native config wins; only generate ours when the app
// has none. a present config that drops One's bundle override would
// silently Release-build with the stock bundler, so fail and say the fix.
const present = REACT_NATIVE_CONFIG_NAMES.filter((name) => existsSync(join(root, name)))
if (present.length === 0) {
writeFileSync(join(root, 'react-native.config.cjs'), REACT_NATIVE_CONFIG)
} else if (
!present.some((name) =>
readFileSync(join(root, name), 'utf8').includes('one/react-native-config')
)
) {
throw new Error(
`[one] ${present.join(', ')} does not load one/react-native-config, so Release builds would run the stock React Native bundler instead of One's. Set it to \`module.exports = require('one/react-native-config')\` or spread that into your config, then re-run one prebuild.`
)
}
const { prebuild } = await import('vxrn')
await prebuild({
root: process.cwd(),
...args,
app,
})
}