react-native
Version:
A framework for building native apps using React
564 lines (525 loc) • 23.3 kB
JavaScript
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
/*::
import type {SwiftpmNameOutcome} from './swiftpm-config';
export type SetupArgs = {
action: 'add' | 'update' | 'deinit' | 'sync' | 'codegen' | 'download' | 'scaffold' | null,
version: string | null,
// Local two-flavor artifact source (advanced). The directory contains
// `debug/` and `release/` cache slots, each with artifacts.json.
artifacts: string | null,
skipCodegen: boolean,
// Overrides the autolinking config command; also settable via
// RCT_SPM_AUTOLINKING_CONFIG_COMMAND.
configCommand: Array<string> | null,
// Artifact download policy: 'auto' fetches when missing, 'skip' never
// fetches, 'force' clears the cache slot and re-downloads.
downloadPolicy: 'auto' | 'skip' | 'force',
// `add` target selection: which app target (when several) and which project.
productName: string | null,
xcodeprojPath: string | null,
// `add`: run `pod deintegrate` + strip RN from the Podfile before injecting.
// Also implied on the zero-arg path when resolveAction's safe-gate detects a
// freshly-scaffolded CocoaPods project (see setup-apple-spm.js).
deintegrate: boolean,
// Skip the single remaining confirmation (the add/update dirty-pbxproj
// warning). Non-TTY auto-proceeds regardless (git is the safety net).
yes: boolean,
};
export type DownloadArgs = {
version: string | null,
flavor: string,
output: string | null,
coreTarball: string | null,
headersTarball: string | null,
depsTarball: string | null,
depsHeadersTarball: string | null,
};
export type ResolvedArtifact = {
url: string,
version: string,
};
export type ProcessResult = {
label: string,
version: string,
xcframeworkPath: string,
url: string,
};
export type ArtifactResultEntry =
| {name: string, error: void, label: string, version: string, xcframeworkPath: string, url: string}
| {name: string, error: string};
export type AutolinkingArgs = {
appRoot: string,
reactNativeRoot: string | null,
autolinkingJson: string | null,
output: string | null,
xcframeworksPath: string | null,
// Platform floor of every manifest this run writes; sanitized by parseArgs.
iosDeploymentTarget: string,
};
export type SpmTarget = {
name: string,
path: string,
exclude: Array<string>,
publicHeadersPath: string | null,
resources?: Array<string>,
// Swift target names this target depends on (already toSwiftName()'d).
// Emitted into the target's SPM `dependencies:` array so the compiler sees
// the dependent target's headers / module map.
spmTargetDependencies?: Array<string>,
// Explicit allowlist of source files (paths relative to target.path).
// Mirrors CocoaPods' `s.source_files`. When non-empty, the SPM target
// declares `sources: [...]` and only these files are compiled — test dirs,
// .js/.podspec/.md siblings, etc. can never sneak in. Null/empty means
// fall back to SPM's default scan of target.path.
sources?: ?Array<string>,
// Header search paths declared by the dep's podspec
// (pod_target_xcconfig HEADER_SEARCH_PATHS), already substituted relative
// to the dep's source dir. Path-style includes like
// `<react/renderer/components/safeareacontext/X.h>` resolve through these.
// Emitted as `.headerSearchPath(...)` directives in the target's
// cSettings / cxxSettings.
headerSearchPaths?: ?Array<string>,
};
// Routing metadata kept alongside an SpmTarget in main(). Lives in a wrapper
// (not on SpmTarget) so the target type stays describable from the outside.
export type TargetEntry = {
target: SpmTarget,
origin: 'npm' | 'spmModule',
// The dep's npm package name (origin 'npm' only). Used in the
// missing-manifest error so the message names the package the developer
// installed, not its derived Swift target name.
npmName?: string,
// The dep's checked-in package root (origin 'npm' only) — the directory that
// holds its root `Package.swift` and `.react-native/` metadata. Threaded from
// the autolinking model (`dep.root`) so the watch file can flag manifest edits
// without walking up from the (possibly nested) target source dir.
root?: string,
// Filled in for npm-origin entries during the mirror step; consumed by the
// synth-package emission step further down.
mirrorReady?: ?{
synthPkgDir: string,
mirroredResources: ?Array<string>,
},
};
// ---------------------------------------------------------------------------
// autolinking.json shape (output of @react-native-community/cli config).
// All fields are optional because the JSON is user-influenced; the consumer
// checks at runtime.
// ---------------------------------------------------------------------------
export type AutolinkingIosPlatform = {
sourceDir?: ?string,
podspecPath?: ?string,
...
};
// As parsed from autolinking.json — all fields optional because the JSON is
// user-influenced. main() validates and narrows to AutolinkedDep before use.
export type AutolinkingDepJson = {
root?: ?string,
platforms?: ?{ios?: ?AutolinkingIosPlatform, ...},
...
};
export type RawAutolinkingJson = {
dependencies?: ?{[string]: AutolinkingDepJson},
...
};
// Validated/normalized dep — name and ios platform are guaranteed present.
// Produced from AutolinkingDepJson in main() and passed through
// expandSpmDependencies to autolinkingDepToSpmTarget.
export type AutolinkedDep = {
name: string,
root: string,
platforms: {ios: AutolinkingIosPlatform, ...},
// Resolved Swift target / module / headers-subdir name — the name the library
// declares in `swiftpmConfig`, else its podspec's (see resolveSwiftName).
// Populated by expandSpmDependencies — always present after expansion;
// optional in the type so caller-side construction stays simple.
swiftName?: string,
// Where swiftName came from. Only a podspec-derived name is safe for the
// scaffolder to record in the library's package.json.
swiftNameSource?: 'config' | 'podspec' | 'npm',
// Which podspec field named it ('podspec' source only), so `spm scaffold` can
// report the choice it made between `header_dir` and `module_name`.
swiftNamePodspecKey?: 'header_dir' | 'module_name' | 'name',
// Populated by expandSpmDependencies from each dep's declared
// `dependencies` array.
spmDependencies?: Array<string>,
...
};
// CLI `config` output minimally typed for the bits we read in
// generate-spm-autolinking-config.js.
export type CliConfigJson = {
root?: ?string,
reactNativePath?: ?string,
project?: ?{ios?: ?{sourceDir?: ?string, ...}, ...},
...
};
// Entry shape for an spmModule declared in react-native.config.js.
export type SpmModuleConfig = {
name: string,
path: string,
exclude?: Array<string>,
// Optional CocoaPods-style glob allowlist (analog of s.source_files).
// When set, replaces auto source discovery for the module — only files
// matching one of these patterns are passed to SPM via `sources:`.
sources?: Array<string>,
};
// ---------------------------------------------------------------------------
// Inputs to the Swift emitters in generate-spm-autolinking.js.
// ---------------------------------------------------------------------------
export type NpmDepRef = {
swiftName: string,
// Path passed to .package(path:). Relative to autolinked/ (the aggregator's
// dir). For in-place synth this is the dep's real source dir.
packagePath?: string,
// The npm package name (e.g. react-native-safe-area-context). Used by the
// aggregator's eval-time missing-manifest guard to name the library a
// developer installed (not its Swift target name).
npmName?: string,
};
export type AggregatorInput = {
npmDeps?: ReadonlyArray<NpmDepRef>,
inlineTargets?: ReadonlyArray<SpmTarget>,
hasReactDep?: boolean,
// Relative path from the aggregator's dir (autolinking/) to
// build/xcframeworks. Used for the inline-target ReactNative dep.
xcframeworksRelPath?: ?string,
// Autolinking-plugin contributions (Expo & other frameworks). Merged into
// the aggregator's package deps + the AutolinkedAggregate target deps.
pluginPackageDeps?: ReadonlyArray<PluginPackageDep>,
pluginProductDeps?: ReadonlyArray<PluginProductDep>,
iosDeploymentTarget?: ?string,
};
// --- Autolinking plugins (PREVIEW / unstable contract) ---
// A framework's plugin package dependency: local (path) or remote (url+version).
export type PluginPackageDep = {
name: string,
path?: string,
url?: string,
version?: string,
};
// A product the AutolinkedAggregate target should link.
export type PluginProductDep = {name: string, package: string};
// A generated source file (e.g. Expo's ExpoModulesProvider.swift) to register
// in the codegen package so it compiles.
export type PluginGeneratedSource = {path: string};
// A build-time shell phase for the app target — SwiftPM's missing analog of
// CocoaPods' `script_phase`. `id` is the stable ledger key and deterministic
// UUID seed (charset /^[@A-Za-z0-9_./-]+$/, so a scoped npm name works; `:` is
// excluded because the seed is `plugin:<id>`); `position` is normalized to 'end'
// by invokePlugins so consumers never re-derive the default.
export type PluginScriptPhase = {
id: string,
name: string,
script: string,
position: 'beforeCompile' | 'end',
inputPaths?: Array<string>,
outputPaths?: Array<string>,
alwaysOutOfDate?: boolean,
};
// A plugin-declared dynamic XCFramework pair. RN validates both paths, stages
// immutable app-local slots, and links/embeds the selected framework outside
// SwiftPM.
export type PluginFlavoredFramework = {
id: string,
frameworkName: string,
linkage: 'dynamic',
flavors: {debug: string, release: string},
};
export type XcframeworkSlice = {
sdk: string,
platform: string,
variant: ?string,
architectures: Array<string>,
libraryIdentifier: string,
libraryPath: string,
binaryPath: string,
};
export type ParsedXcframework = {
path: string,
frameworkName: string,
executableName: string,
slices: Array<XcframeworkSlice>,
};
export type FlavoredFrameworkManifestEntry = {
id: string,
frameworkName: string,
executableName: string,
linkage: 'dynamic',
artifactRelativePath: string,
slices: Array<XcframeworkSlice>,
};
export type FlavoredFrameworksManifest = {
version: 1,
frameworks: Array<FlavoredFrameworkManifestEntry>,
};
// How a plugin depends on React — the single source of truth so a plugin's
// own Package.swift doesn't re-derive RN's package path/identity/products.
// Local vs remote is distinguished by which keys are present:
// - local: {name, path (absolute), relPath (relative to outputDir)}
// - remote: {name, url, version}
// `path` is absolute so it's correct no matter which subdir of outputDir the
// plugin writes its manifest into (the manifests are gitignored + regenerated).
export type ReactPackageRef =
| {name: string, path: string, relPath?: string}
| {name: string, url: string, version: string};
export type ReactDescriptor = {
packageRef: ReactPackageRef,
// The products a React-consuming target may depend on — the same set RN wires
// into its own autolinked targets (parity), filtered to those resolvable this
// run. Every listed product is safe to reference without guarding. Note
// ReactAppHeaders lives in the separate, per-app React-GeneratedCode package.
products: Array<{name: string, package: string}>,
};
// Context handed to every plugin. Includes the JS root (projectRoot) and the
// parsed autolinking data so a framework can scan the discovered deps.
export type PluginContext = {
appRoot: string,
projectRoot: string,
reactNativeRoot: string,
autolinking: {readonly [string]: unknown},
outputDir: string,
// How to depend on React (package ref + product set); null only when there is
// no resolvable React dependency at all.
react: ?ReactDescriptor,
};
export type PluginResult = {
packageDependencies: Array<PluginPackageDep>,
productDependencies: Array<PluginProductDep>,
generatedSources: Array<PluginGeneratedSource>,
flavoredFrameworks: Array<PluginFlavoredFramework>,
// Absolute paths (dirs or files) the Xcode auto-sync build phase should watch
// for staleness, e.g. the plugin dep's own `Package.swift` and per-module
// manifests. Folded into `.spm-sync-watch-paths` by main().
watchPaths: Array<string>,
// Build-time shell phases for the app target, recorded to
// `.spm-plugin-script-phases.json` by main() and injected by `spm add`/
// `update`.
scriptPhases: Array<PluginScriptPhase>,
};
export type SpmAutolinkingPlugin = (context: PluginContext) => ?{
packageDependencies?: Array<PluginPackageDep>,
productDependencies?: Array<PluginProductDep>,
generatedSources?: Array<PluginGeneratedSource>,
flavoredFrameworks?: Array<PluginFlavoredFramework>,
watchPaths?: Array<string>,
// `position` may be omitted here; invokePlugins normalizes it to 'end'.
scriptPhases?: Array<{
...PluginScriptPhase,
position?: 'beforeCompile' | 'end',
}>,
};
export type DiscoveredPlugin = {
depName: string,
pluginPath: string,
plugin: SpmAutolinkingPlugin,
};
export type SynthPackageSpec = {
swiftName: string,
exclude?: Array<string>,
publicHeadersPath?: ?string,
// Explicit allowlist of source paths (relative to `targetPath`). When
// present and non-empty, the synth Package.swift emits `sources: [...]`
// — SPM will only compile these files.
sources?: ?Array<string>,
spmDependencies?: Array<{swiftName: string}>,
hasReactDep?: boolean,
// Relative path (posix, from the synth dir <outputDir>/packages/<Name>) to
// the app's React xcframeworks package and codegen package. Computed by the
// caller at generation time — the synth holds no runtime discovery.
reactNativePackagePath?: string,
codegenPackagePath?: string,
resources?: ?Array<string>,
isDynamic?: boolean,
targetPath?: string,
// Fallback relative base for sibling synth packages when the caller does not
// supply absolute paths (tests). Production uses siblingSynthAbsolutePaths.
siblingPackageBaseRelative?: string,
siblingSynthAbsolutePaths?: {[swiftName: string]: string},
// Header search paths from the dep's podspec `pod_target_xcconfig`
// HEADER_SEARCH_PATHS, with `$(PODS_TARGET_SRCROOT)` substituted and the
// synth wrapper's `root/` prefix applied. Emitted as `.headerSearchPath()`
// directives on cSettings / cxxSettings so path-style includes like
// `<react/renderer/components/safeareacontext/X.h>` resolve through the
// dep's own `common/cpp/` subtree.
headerSearchPaths?: ?Array<string>,
iosDeploymentTarget?: ?string,
};
export type GeneratePackageArgs = {
appRoot: string,
reactNativeRoot: string | null,
version: string | null,
debugArtifactsDir: string | null,
releaseArtifactsDir: string | null,
appName: string | null,
targetName: string | null,
sourcePath: string | null,
iosVersion: string,
};
// A preprocessor define lifted from a podspec's pod_target_xcconfig
// (OTHER_CFLAGS `-D...` tokens + GCC_PREPROCESSOR_DEFINITIONS entries). `value`
// is null for a bare `-DNAME` (define with no value); `config` scopes the
// define to a build configuration (from `[config=*Debug*]`/`[config=*Release*]`
// xcconfig keys), null = unconditional. Emitted as SPM `.define(...)`.
export type PreprocessorDefine = {
name: string,
value: ?string,
config: ?('debug' | 'release'),
};
// ---------------------------------------------------------------------------
// Scaffold types — for the `npx react-native spm scaffold` command that
// generates a `Package.swift` into `node_modules/<dep>/` for community RN
// libraries that don't ship SPM support. Inputs come from the dep's podspec
// (read via `pod ipc spec --format=json` when CocoaPods is available, or a
// regex fallback). The output Package.swift is treated as "self-managed" by
// the autolinker — see isSelfManagedPackage in generate-spm-autolinking.js.
// ---------------------------------------------------------------------------
// Flattened, subspec-merged view of a podspec — what the translation layer
// consumes. HEADER_SEARCH_PATHS / source globs are kept as raw strings;
// substitution of `$(PODS_TARGET_SRCROOT)` etc. happens during translation
// so the raw model stays portable.
export type PodspecModel = {
name: string,
version: string,
sourceFiles: Array<string>,
publicHeaderFiles: Array<string>,
privateHeaderFiles: Array<string>,
excludeFiles: Array<string>,
headerMappingsDir: ?string,
// Every subspec's header_mappings_dir (union). dirname() of each is added as
// a header search path so `<namespace/...>` includes resolve from the
// physical source tree (SPM has no header_mappings_dir copy step).
headerMappingsDirs: Array<string>,
headerDir: ?string,
// What CocoaPods compiles the module as, and so what Swift and `@import`
// consumers spell (`s.module_name`). Defaults to the pod name in CocoaPods;
// null here when the podspec does not declare it.
moduleName: ?string,
frameworks: Array<string>,
weakFrameworks: Array<string>,
libraries: Array<string>,
// Merged dependency name list. With pod-ipc, includes the deps materialized
// by `install_modules_dependencies(s)` (React-Core, React-Fabric, ...).
// Version constraints stripped — we only need the name to bucket.
dependencies: Array<string>,
compilerFlags: Array<string>,
// Raw header-search-path entries from `pod_target_xcconfig['HEADER_SEARCH_PATHS']`.
// May contain Xcode build setting placeholders like `$(PODS_TARGET_SRCROOT)`.
headerSearchPaths: Array<string>,
// Preprocessor defines lifted from pod_target_xcconfig OTHER_CFLAGS (`-D`
// tokens) + GCC_PREPROCESSOR_DEFINITIONS (incl. per-config variants). Already
// resolved by `pod ipc` (e.g. `-DWORKLETS_VERSION=#{package['version']}` →
// `WORKLETS_VERSION=0.9.2`). Emitted as `.define(...)` on the SPM target.
preprocessorDefines: Array<PreprocessorDefine>,
// File paths or glob patterns the dep declares as bundled resources.
resources: Array<string>,
requiresArc: boolean,
// Warnings collected during parsing — surfaced in the scaffold summary so
// a user can spot fields that didn't translate cleanly (unknown env vars,
// unrecognized $(...) tokens, etc.). Never throws on parse errors.
warnings: Array<string>,
// True when produced by the regex fallback rather than pod-ipc. Used to
// emit a louder banner in the scaffold summary explaining that dependency
// wiring may be incomplete.
partial: boolean,
// True when the podspec source calls `install_modules_dependencies(s)` —
// RN's Podfile-side helper that injects the React-Core family. We strip that
// helper before evaluating the spec (and the regex parser can't expand it),
// so React-Core never surfaces in `dependencies`. The scaffolder treats this
// flag as an implicit React-core dependency (a plain ObjC module using the
// helper has no `codegenConfig` to key off).
usesInstallModulesDependencies: boolean,
};
// Intermediate translation result — concrete data the Swift emitter consumes.
// Decouples podspec reading from SPM-specific shaping so each side can be
// tested in isolation.
export type SpmScaffoldSpec = {
// Swift target / module name, as resolved by expandSpmDependencies.
swiftName: string,
// Source file paths relative to the dep root, ready for `sources: [...]`
// emission after the `root/` wrapper-dir prefix is applied at emit time.
sources: Array<string>,
// Header search paths resolved to dep-root-relative form. Each entry
// becomes `.headerSearchPath("<path>")` in cSettings + cxxSettings.
headerSearchPaths: Array<string>,
// Preprocessor defines (resolved by pod ipc) — emitted as `.define(...)` in
// cSettings + cxxSettings, honoring any per-config scope.
preprocessorDefines: Array<PreprocessorDefine>,
// True when the target has ObjC(++) sources (.m/.mm). Drives emitting +
// `-include`ing a prefix header that ambient-imports Foundation/UIKit (which
// CocoaPods provides via a generated prefix.pch and SPM does not).
needsObjCPrefix: boolean,
// Bucketed dependency references — pre-computed by the translation layer.
// `coreReactNative` is true when ANY React-* / RCT* / RCT-Folly / glog
// dep is present (so we add React's invariant header products).
// `siblingNames` are npm names that match other autolinked deps.
// `siblingSwiftNames` carries each one's resolved Swift name (honoring the
// sibling's `spm.name`); a sibling absent from it falls back to
// toSwiftName(npmName) at emit time.
coreReactNative: boolean,
siblingNames: Array<string>,
siblingSwiftNames?: {[npmName: string]: string},
// Extra frameworks beyond the autolinker's default UIKit/Foundation/CoreGraphics
// set. Merged with the defaults at emit time.
extraFrameworks: Array<string>,
weakFrameworks: Array<string>,
// Compiler flags lifted from `s.compiler_flags`. Tokenized, ready for
// `cxxSettings: [.unsafeFlags([...])]`.
compilerFlags: Array<string>,
// Public-headers strategy. `mappingsDir` (when set) drives publicHeadersPath
// so the autolinker's centralized headers tree exposes `#import <SwiftName/...>`.
publicHeadersPath: ?string,
// Root-level headers (podspecs like `s.source_files = "*.{h,m,mm}"`) that the
// scaffold writer mirrors into a generated include/<SwiftName>/ shim dir so
// the CocoaPods header-map spelling `#import <SwiftName/Header.h>` resolves
// for dependents (publicHeadersPath is then "include").
namespacedShimHeaders: Array<string>,
// File paths (relative to dep root) the emitter should declare as `.copy(...)`
// resources on the target.
resources: Array<string>,
// Carried through from the PodspecModel; surfaced in the scaffold summary.
warnings: Array<string>,
};
// Per-dep outcome from one scaffold run. The orchestrator returns an array
// of these so the CLI summary can print a structured table.
export type ScaffoldResult =
| {
depName: string,
status: 'written',
packageSwiftPath: string,
warnings: Array<string>,
// True when the dep's Package.swift already existed (a regen — slot
// changed, --force, etc.); false on first-time scaffolds. The CLI
// orchestrator prompts only for first-time scaffolds.
previouslyExisted: boolean,
// What became of `swiftpmConfig.name` in the library's package.json.
// Absent when the name was not podspec-derived, so nothing was attempted.
swiftpmName?: SwiftpmNameOutcome,
}
| {
depName: string,
status:
| 'skipped-self-managed'
| 'skipped-autogen'
| 'skipped-scaffolder-marker'
| 'skipped-no-ios'
| 'skipped-no-podspec'
| 'skipped-opt-out'
| 'skipped-mixed-language'
| 'skipped-is-react-native',
reason: string,
}
| {
depName: string,
status: 'error',
reason: string,
};
*/
module.exports = {};