@hot-updater/cli-tools 1.0.8 → 1.1.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.
package/dist/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Readable } from "stream";
2
2
  import { Key as Key$1 } from "node:readline";
3
- import { AnyHotUpdaterPlugin, BundleSigningAdapter, ConfigInput, ConfiguredDatabase, EngineDatabase, HotUpdaterCoreApi, LocalSigningConfig, Migrator, Platform, PluginClientPlugin, RequiredDeep, SchemaGenerator, SigningConfig, StorageAdapter, StorageAdapterWith, StoragePutResult, ToolingDatabase } from "@hot-updater/plugin-core";
3
+ import { AnyHotUpdaterPlugin, BundleSigningAdapter, ConfigInput, ConfiguredDatabase, EngineDatabase, HotUpdaterCoreApi, LocalSigningConfig, Migrator, NativeFingerprint, NativeFingerprintOptions, Platform, PluginClientPlugin, RequiredDeep, SchemaGenerator, SigningConfig, StorageAdapter, StorageAdapterWith, StoragePutResult, ToolingDatabase } from "@hot-updater/plugin-core";
4
4
  import { Readable as Readable$1, Writable } from "node:stream";
5
5
  import { ManifestArchive } from "@hot-updater/protocol";
6
6
  //#region src/BuildLogger.d.ts
@@ -510,6 +510,99 @@ declare const assertInitInputs: ({ inputs, strict }: {
510
510
  }) => void;
511
511
  declare const getMissingInitInputs: (inputs: Readonly<Record<string, string | undefined>>) => readonly string[];
512
512
  //#endregion
513
+ //#region src/interfaceFingerprint.d.ts
514
+ /**
515
+ * The engine fingerprint.json records for the interface fingerprint. Change
516
+ * it with any change to what is hashed or how, so the new hashes are reported
517
+ * as an engine change rather than a native one.
518
+ */
519
+ declare const INTERFACE_FINGERPRINT_ENGINE = "hot-updater/interface@1";
520
+ /**
521
+ * A native package linked into the app: its directory, when it comes from a
522
+ * package manager, or its name and version, when the framework declares it
523
+ * elsewhere (a CocoaPods or Maven dependency).
524
+ */
525
+ type NativeDependency = {
526
+ name: string;
527
+ root: string;
528
+ /**
529
+ * Where autolinking finds the platform's native code when it isn't the
530
+ * package's `ios`/`android` folder: Android's `sourceDir`, or the
531
+ * folder of the iOS podspec. An Expo module can name several.
532
+ */
533
+ sourceDirs?: string[];
534
+ /** Prebuilt native files autolinking links, such as an Expo module's AARs. */
535
+ files?: string[];
536
+ } | {
537
+ name: string;
538
+ version: string;
539
+ };
540
+ /**
541
+ * The native side JavaScript runs against: the runtime, such as
542
+ * `react-native@0.85.0`, and the native packages linked into the app.
543
+ */
544
+ interface NativeInterface {
545
+ runtime: string;
546
+ dependencies: NativeDependency[];
547
+ /**
548
+ * Native settings that decide how JavaScript runs, such as the JavaScript
549
+ * engine, read from the files the framework itself reads.
550
+ */
551
+ settings?: Record<string, string>;
552
+ }
553
+ /**
554
+ * The expo-modules-autolinking an Expo app builds with, resolved from its
555
+ * `expo` package as the native templates resolve it.
556
+ */
557
+ declare const resolveExpoAutolinking: (cwd: string) => {
558
+ bin: string;
559
+ version: string;
560
+ /** Whether it lists React Native packages (`react-native-config`, 1.12+). */
561
+ linksReactNative: boolean;
562
+ };
563
+ /**
564
+ * Reads the native interface of a React Native app from the JSON its
565
+ * autolinking `config` command prints: `react-native config` for React Native
566
+ * CLI, `rock config -p <platform>` for Rock, `expo-modules-autolinking
567
+ * react-native-config` for Expo. An app that links Expo modules gets them
568
+ * from expo-modules-autolinking as well.
569
+ *
570
+ * The engine and architecture settings come from what the project declares:
571
+ * `settings` when the framework records them elsewhere, such as Expo's app
572
+ * config, else android/gradle.properties. Nothing generated by an install,
573
+ * such as Podfile.lock, is read, so every machine computes the same
574
+ * fingerprint before and after `pod install` or `expo prebuild`.
575
+ */
576
+ declare const readReactNativeInterface: ({ cwd, command, args, platform, settings: declaredSettings }: {
577
+ cwd: string;
578
+ command: string;
579
+ args: string[];
580
+ platform: Platform;
581
+ settings?: Record<string, string>;
582
+ }) => Promise<NativeInterface>;
583
+ /**
584
+ * Whether React Native codegen reads `file` as a spec for `platform`: the
585
+ * rule of its `filterJSFile`, so a fingerprint follows the specs codegen
586
+ * compiles and nothing else in `jsSrcsDir`.
587
+ */
588
+ declare const isCodegenSpecFile: (original: string, platform: Platform) => boolean;
589
+ /**
590
+ * Fingerprints the native interface JavaScript depends on, not the native
591
+ * build: the runtime and its native settings, each registry package as
592
+ * `name@version`, the codegen
593
+ * specs of the app and of local packages (a local package without specs by
594
+ * its native files), patches to those packages, `nativeVersion`, and
595
+ * `extraSources`. Version and build numbers, native implementations, and
596
+ * JavaScript-only dependencies leave it unchanged.
597
+ */
598
+ declare const createInterfaceFingerprint: (cwd: string, options: NativeFingerprintOptions, nativeInterface: NativeInterface) => Promise<NativeFingerprint>;
599
+ /**
600
+ * The fingerprint of a React Native CLI project: the native interface
601
+ * `react-native config` reports. The bare adapter's fingerprint, and the one
602
+ * a build adapter without its own gets.
603
+ */
604
+ declare const reactNativeCliFingerprint: (cwd: string, options: NativeFingerprintOptions) => Promise<NativeFingerprint>;
605
+ //#endregion
513
606
  //#region src/LogWriter.d.ts
514
607
  type HotUpdaterLogWriter = {
515
608
  logFilePath: string | null;
@@ -1925,4 +2018,4 @@ type TransformTemplateArgs<T extends string> = { [Key in ExtractPlaceholders<T>]
1925
2018
  */
1926
2019
  declare function transformTemplate<T extends string>(templateString: T, values: TransformTemplateArgs<T>): string;
1927
2020
  //#endregion
1928
- export { AppClientCredential, AppClientPlugin, AssembleServerOptions, AssembledServer, BuildLogger, BuildLoggerConfig, BuildType, BundleSigningSession, ClientAuthSpec, ClientCredentialSpec, ConfigBuilder, ConfigBuilderScaffold, ConfigResponse, CreateHotUpdaterConfigScaffoldFromBuilderOptions, CreateHotUpdaterConfigScaffoldOptions, HOT_UPDATER_SERVER_PACKAGE_VERSION_ENV, HotUpdateDirUtil, HotUpdaterConfigOptions, HotUpdaterConfigScaffold, HotUpdaterInitEnv, HotUpdaterLogWriter, ImportInfo, InitEnvFileError, InitError, InitProviderDefinition, InitProviderInputDefinition, InitProviderInputPersistence, LegacyInfrastructureError, ManagedHelperStatement, ManagedHelperStrategy, MissingInitInputsError, PromptProgress, PromptSpinner, ProviderConfig, ProvisionedClientCredential, ReactNativeMetadata, ReadPackageUpResult, RunInitOptions, ServerClientEndpoint, ServerDefinition, ServerDefinitionError, WriteHotUpdaterConfigResult, assembleServer, assertInfrastructureGenerationAtUrl, assertInfrastructureGenerationPayload, assertInitInputs, assertInitProviderInputs, banner, clientAuthOf, typedColors as colors, confirmInitInputPersistence, copyDirToTmp, createHotUpdaterConfigScaffold, createHotUpdaterConfigScaffoldFromBuilder, createLogWriter, createMigrator, createTarBrTargetFiles, createZip, decryptJson, defineInitProvider, encryptJson, ensureInstallPackages, generateClientCredential, generateSchema, generatesSchema, getAndroidSdkPath, getBundleSigningPublicKey, getCwd, getHotUpdaterEnvValue, getHotUpdaterInitInputEnv, getInitProviderEnvVars, getInitProviderTextPromptValues, getMissingInitInputs, getMissingInitProviderInputs, getPackageManager, getReactNativeMetadatas, getStorageFileByteSize, isServerDefinition, link, loadConfig, loadPlatformConfigs, log, makeEnv, moduleSpecifiersOf, p, prepareBundleSigning, printAppSetup, printBanner, provisionClientCredential, putStorageFile, readBundleSigningPublicKeyFile, readHotUpdaterEnv, readHotUpdaterInitEnv, readPackageUp, renderAppImports, renderAppSetup, renderImportStatements, resolveHotUpdaterServerVersion, resolveInitProviderInput, resolveInitProviderInputs, resolvePackageVersion, serverDefinitionOf, shouldAutoSelectOnlyInitResource, stripAnsi, transformEnv, transformTemplate, writeHotUpdaterConfig, writeHotUpdaterFiles };
2021
+ export { AppClientCredential, AppClientPlugin, AssembleServerOptions, AssembledServer, BuildLogger, BuildLoggerConfig, BuildType, BundleSigningSession, ClientAuthSpec, ClientCredentialSpec, ConfigBuilder, ConfigBuilderScaffold, ConfigResponse, CreateHotUpdaterConfigScaffoldFromBuilderOptions, CreateHotUpdaterConfigScaffoldOptions, HOT_UPDATER_SERVER_PACKAGE_VERSION_ENV, HotUpdateDirUtil, HotUpdaterConfigOptions, HotUpdaterConfigScaffold, HotUpdaterInitEnv, HotUpdaterLogWriter, INTERFACE_FINGERPRINT_ENGINE, ImportInfo, InitEnvFileError, InitError, InitProviderDefinition, InitProviderInputDefinition, InitProviderInputPersistence, LegacyInfrastructureError, ManagedHelperStatement, ManagedHelperStrategy, MissingInitInputsError, NativeDependency, NativeInterface, PromptProgress, PromptSpinner, ProviderConfig, ProvisionedClientCredential, ReactNativeMetadata, ReadPackageUpResult, RunInitOptions, ServerClientEndpoint, ServerDefinition, ServerDefinitionError, WriteHotUpdaterConfigResult, assembleServer, assertInfrastructureGenerationAtUrl, assertInfrastructureGenerationPayload, assertInitInputs, assertInitProviderInputs, banner, clientAuthOf, typedColors as colors, confirmInitInputPersistence, copyDirToTmp, createHotUpdaterConfigScaffold, createHotUpdaterConfigScaffoldFromBuilder, createInterfaceFingerprint, createLogWriter, createMigrator, createTarBrTargetFiles, createZip, decryptJson, defineInitProvider, encryptJson, ensureInstallPackages, generateClientCredential, generateSchema, generatesSchema, getAndroidSdkPath, getBundleSigningPublicKey, getCwd, getHotUpdaterEnvValue, getHotUpdaterInitInputEnv, getInitProviderEnvVars, getInitProviderTextPromptValues, getMissingInitInputs, getMissingInitProviderInputs, getPackageManager, getReactNativeMetadatas, getStorageFileByteSize, isCodegenSpecFile, isServerDefinition, link, loadConfig, loadPlatformConfigs, log, makeEnv, moduleSpecifiersOf, p, prepareBundleSigning, printAppSetup, printBanner, provisionClientCredential, putStorageFile, reactNativeCliFingerprint, readBundleSigningPublicKeyFile, readHotUpdaterEnv, readHotUpdaterInitEnv, readPackageUp, readReactNativeInterface, renderAppImports, renderAppSetup, renderImportStatements, resolveExpoAutolinking, resolveHotUpdaterServerVersion, resolveInitProviderInput, resolveInitProviderInputs, resolvePackageVersion, serverDefinitionOf, shouldAutoSelectOnlyInitResource, stripAnsi, transformEnv, transformTemplate, writeHotUpdaterConfig, writeHotUpdaterFiles };
package/dist/index.mjs CHANGED
@@ -44983,6 +44983,474 @@ const getInitProviderEnvVars = ({ includeConsentInputs, inputs, provider }) => {
44983
44983
  return env;
44984
44984
  };
44985
44985
  //#endregion
44986
+ //#region src/interfaceFingerprint.ts
44987
+ var import_out = /* @__PURE__ */ __toESM(require_out(), 1);
44988
+ var import_readYaml = require_readYaml();
44989
+ const require$2 = createRequire(import.meta.url);
44990
+ /**
44991
+ * The engine fingerprint.json records for the interface fingerprint. Change
44992
+ * it with any change to what is hashed or how, so the new hashes are reported
44993
+ * as an engine change rather than a native one.
44994
+ */
44995
+ const INTERFACE_FINGERPRINT_ENGINE = "hot-updater/interface@1";
44996
+ const GENERATED_DIRS = [
44997
+ "**/node_modules/**",
44998
+ "**/build/**",
44999
+ "**/Pods/**",
45000
+ "**/.gradle/**",
45001
+ "**/.cxx/**",
45002
+ "**/DerivedData/**"
45003
+ ];
45004
+ const TEST_DIRS = [
45005
+ "**/{test,tests,Tests,androidTest,__tests__}/**",
45006
+ "**/*Tests.swift",
45007
+ "**/*Test.{kt,java}"
45008
+ ];
45009
+ const NATIVE_FILES = {
45010
+ ios: [
45011
+ "*.podspec",
45012
+ "ios/**/*.{h,hpp,hxx,m,mm,c,cc,cpp,cxx,swift}",
45013
+ "apple/**/*.{h,hpp,hxx,m,mm,c,cc,cpp,cxx,swift}"
45014
+ ],
45015
+ android: ["android/**/*.{java,kt,c,cc,cpp,cxx,h,hpp,hxx,gradle,kts}", "android/**/CMakeLists.txt"]
45016
+ };
45017
+ /**
45018
+ * The folders under `dir` that hold a package of their own, such as an example
45019
+ * app beside a library's podspec: their files belong to that package.
45020
+ */
45021
+ const nestedPackages = async (dir) => (await (0, import_out.default)("**/package.json", {
45022
+ cwd: dir,
45023
+ ignore: [...GENERATED_DIRS]
45024
+ })).map((file) => path$1.posix.dirname(file)).filter((folder) => folder !== ".").map((folder) => `${folder}/**`);
45025
+ /** A platform's native files in a folder autolinking points at. */
45026
+ const NATIVE_SOURCE_FILES = {
45027
+ ios: ["*.podspec", "**/*.{h,hpp,hxx,m,mm,c,cc,cpp,cxx,swift}"],
45028
+ android: ["**/*.{java,kt,c,cc,cpp,cxx,h,hpp,hxx,gradle,kts}", "**/CMakeLists.txt"]
45029
+ };
45030
+ /**
45031
+ * The folder autolinking reads a dependency's native code from, when the
45032
+ * config names one: Android's `sourceDir`, or the iOS podspec's folder.
45033
+ */
45034
+ const nativeSourceDir = (platform, config) => {
45035
+ if (!config || typeof config !== "object") return void 0;
45036
+ const value = platform === "ios" ? config.podspecPath : config.sourceDir;
45037
+ if (typeof value !== "string") return void 0;
45038
+ return platform === "ios" ? path$1.dirname(value) : value;
45039
+ };
45040
+ const readManifest = async (dir) => {
45041
+ try {
45042
+ return JSON.parse(await fs$2.readFile(path$1.join(dir, "package.json"), "utf8"));
45043
+ } catch {
45044
+ return null;
45045
+ }
45046
+ };
45047
+ const parseJsonOutput = (stdout, command) => {
45048
+ const start = stdout.indexOf("{");
45049
+ try {
45050
+ if (start >= 0) return JSON.parse(stdout.slice(start));
45051
+ } catch {}
45052
+ throw new Error(`\`${command}\` printed no JSON configuration. Check that it runs in this project.`);
45053
+ };
45054
+ const findPackageRoot = async (from, name) => {
45055
+ let dir = from;
45056
+ while (true) {
45057
+ if ((await readManifest(dir))?.name === name) return dir;
45058
+ const parent = path$1.dirname(dir);
45059
+ if (parent === dir) return null;
45060
+ dir = parent;
45061
+ }
45062
+ };
45063
+ /**
45064
+ * A local Expo module's registration for `platform`, from its
45065
+ * expo-module.config.json (or legacy unimodule.json): whether it lists the platform, the platform's
45066
+ * own section, and the native features it requires. Other platforms' and
45067
+ * non-native sections, such as `web` and `devtools`, leave it alone.
45068
+ */
45069
+ const readExpoModuleConfig = async (root, platform) => {
45070
+ let file = null;
45071
+ for (const name of ["expo-module.config.json", "unimodule.json"]) {
45072
+ file = await fs$2.readFile(path$1.join(root, name), "utf8").catch(() => null);
45073
+ if (file !== null) break;
45074
+ }
45075
+ if (file === null) return null;
45076
+ const config = JSON.parse(file);
45077
+ const names = platform === "ios" ? ["apple", "ios"] : ["android"];
45078
+ const section = platform === "ios" ? config["apple"] ?? config["ios"] : config["android"];
45079
+ return stableStringify({
45080
+ listed: Array.isArray(config["platforms"]) ? names.some((name) => config["platforms"].includes(name)) : null,
45081
+ coreFeatures: config["coreFeatures"] ?? null,
45082
+ section: section ?? null
45083
+ });
45084
+ };
45085
+ const atLeast = (version, minimum) => {
45086
+ const parts = version.split(/[.+-]/u).map(Number);
45087
+ for (let index = 0; index < 3; index++) {
45088
+ const part = parts[index] ?? 0;
45089
+ if (part !== minimum[index]) return part > minimum[index];
45090
+ }
45091
+ return true;
45092
+ };
45093
+ /**
45094
+ * The expo-modules-autolinking an Expo app builds with, resolved from its
45095
+ * `expo` package as the native templates resolve it.
45096
+ */
45097
+ const resolveExpoAutolinking = (cwd) => {
45098
+ const expo = path$1.dirname(require$2.resolve("expo/package.json", { paths: [cwd] }));
45099
+ const manifest = require$2.resolve("expo-modules-autolinking/package.json", { paths: [expo] });
45100
+ const { version = "0.0.0" } = JSON.parse(readFileSync(manifest, "utf8"));
45101
+ return {
45102
+ bin: path$1.join(path$1.dirname(manifest), "bin/expo-modules-autolinking.js"),
45103
+ version,
45104
+ linksReactNative: atLeast(version, [
45105
+ 1,
45106
+ 12,
45107
+ 0
45108
+ ])
45109
+ };
45110
+ };
45111
+ /**
45112
+ * The Expo modules linked into a React Native app through
45113
+ * `use_expo_modules!` / `useExpoModules()`. React Native CLI's config lists
45114
+ * only the `expo` package for them, so they are read from
45115
+ * expo-modules-autolinking.
45116
+ */
45117
+ const readExpoModules = async (cwd, platform) => {
45118
+ const { bin, version } = resolveExpoAutolinking(cwd);
45119
+ const name = platform === "android" ? "android" : atLeast(version, [
45120
+ 1,
45121
+ 10,
45122
+ 0
45123
+ ]) ? "apple" : "ios";
45124
+ const result = await execa(process.execPath, [
45125
+ bin,
45126
+ "resolve",
45127
+ "--platform",
45128
+ name,
45129
+ "--json"
45130
+ ], {
45131
+ cwd,
45132
+ reject: false
45133
+ });
45134
+ if (result.exitCode !== 0) throw new Error("`expo-modules-autolinking resolve` failed.");
45135
+ const { modules = [] } = parseJsonOutput(result.stdout, "expo-modules-autolinking resolve");
45136
+ return readExpoModuleDependencies(modules);
45137
+ };
45138
+ const readExpoModuleDependencies = (modules) => Promise.all(modules.flatMap((module) => {
45139
+ const name = module.packageName;
45140
+ if (!name) return [];
45141
+ const sourceDirs = [
45142
+ ...(module.projects ?? []).map(({ sourceDir }) => sourceDir),
45143
+ ...(module.plugins ?? []).map(({ sourceDir }) => sourceDir),
45144
+ ...(module.pods ?? []).map(({ podspecDir }) => podspecDir)
45145
+ ].filter((dir) => Boolean(dir));
45146
+ const files = [...(module.projects ?? []).flatMap(({ aarProjects }) => aarProjects ?? []), ...module.aarProjects ?? []].map(({ aarFilePath }) => aarFilePath).filter((file) => Boolean(file));
45147
+ return [(async () => {
45148
+ const anchor = sourceDirs[0] ?? files[0];
45149
+ const root = anchor ? await findPackageRoot(anchor, name) : null;
45150
+ return root ? {
45151
+ name,
45152
+ root,
45153
+ sourceDirs,
45154
+ ...files.length > 0 ? { files } : {}
45155
+ } : {
45156
+ name,
45157
+ version: module.packageVersion ?? "unknown"
45158
+ };
45159
+ })()];
45160
+ }));
45161
+ const GRADLE_FLAGS = [
45162
+ "hermesEnabled",
45163
+ "hermesV1Enabled",
45164
+ "react.hermesV1Enabled",
45165
+ "newArchEnabled"
45166
+ ];
45167
+ /**
45168
+ * The engine and architecture flags android/gradle.properties declares. An
45169
+ * absent flag takes React Native's default, which follows its version.
45170
+ */
45171
+ const readGradleFlags = async (androidDir) => {
45172
+ const properties = await fs$2.readFile(path$1.join(androidDir, "gradle.properties"), "utf8").catch(() => "");
45173
+ const flags = {};
45174
+ const lines = [];
45175
+ let pending = "";
45176
+ for (const line of properties.split(/\r?\n/)) {
45177
+ const joined = pending + (pending ? line.trimStart() : line);
45178
+ if (/\\*$/.exec(joined)[0].length % 2 === 1) {
45179
+ pending = joined.slice(0, -1);
45180
+ continue;
45181
+ }
45182
+ lines.push(joined);
45183
+ pending = "";
45184
+ }
45185
+ if (pending) lines.push(pending);
45186
+ for (const line of lines) {
45187
+ const trimmed = line.trim();
45188
+ if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("!")) continue;
45189
+ const separator = trimmed.search(/[=:\s]/);
45190
+ const end = separator < 0 ? trimmed.length : separator;
45191
+ const key = trimmed.slice(0, end);
45192
+ if (GRADLE_FLAGS.includes(key)) flags[`android:${key}`] = trimmed.slice(end).replace(/^\s*[=:]?/, "").trim();
45193
+ }
45194
+ return flags;
45195
+ };
45196
+ /**
45197
+ * Reads the native interface of a React Native app from the JSON its
45198
+ * autolinking `config` command prints: `react-native config` for React Native
45199
+ * CLI, `rock config -p <platform>` for Rock, `expo-modules-autolinking
45200
+ * react-native-config` for Expo. An app that links Expo modules gets them
45201
+ * from expo-modules-autolinking as well.
45202
+ *
45203
+ * The engine and architecture settings come from what the project declares:
45204
+ * `settings` when the framework records them elsewhere, such as Expo's app
45205
+ * config, else android/gradle.properties. Nothing generated by an install,
45206
+ * such as Podfile.lock, is read, so every machine computes the same
45207
+ * fingerprint before and after `pod install` or `expo prebuild`.
45208
+ */
45209
+ const readReactNativeInterface = async ({ cwd, command, args, platform, settings: declaredSettings }) => {
45210
+ const { stdout } = await execa(command, args, { cwd });
45211
+ const config = parseJsonOutput(stdout, [path$1.basename(command), ...args].join(" "));
45212
+ const reactNative = config.reactNativePath ? await readManifest(config.reactNativePath) : null;
45213
+ const dependencies = Object.values(config.dependencies ?? {}).flatMap((dependency) => dependency.name && dependency.root && dependency.platforms?.[platform] ? [{
45214
+ name: dependency.name,
45215
+ root: dependency.root,
45216
+ sourceDir: nativeSourceDir(platform, dependency.platforms[platform])
45217
+ }].map(({ sourceDir, ...rest }) => sourceDir ? {
45218
+ ...rest,
45219
+ sourceDirs: [sourceDir]
45220
+ } : rest) : []);
45221
+ const expoModules = dependencies.some(({ name }) => name === "expo") ? await readExpoModules(cwd, platform) : [];
45222
+ const names = new Set(dependencies.map(({ name }) => name));
45223
+ const settings = declaredSettings ?? (platform === "android" ? await readGradleFlags(config.project?.android?.sourceDir ?? path$1.join(cwd, "android")) : {});
45224
+ return {
45225
+ runtime: `react-native@${reactNative?.version ?? "unknown"}`,
45226
+ dependencies: [...dependencies, ...expoModules.filter(({ name }) => !names.has(name))],
45227
+ settings
45228
+ };
45229
+ };
45230
+ const sha256 = (data) => createHash("sha256").update(data).digest("hex");
45231
+ const toPosix = (filePath) => filePath.split(path$1.sep).join("/");
45232
+ /** JSON with object keys sorted, so key order in package.json doesn't count. */
45233
+ const stableStringify = (value) => JSON.stringify(value, (_key, entry) => entry && typeof entry === "object" && !Array.isArray(entry) ? Object.fromEntries(Object.entries(entry).sort(([a], [b]) => a < b ? -1 : 1)) : entry);
45234
+ /**
45235
+ * Whether React Native codegen reads `file` as a spec for `platform`: the
45236
+ * rule of its `filterJSFile`, so a fingerprint follows the specs codegen
45237
+ * compiles and nothing else in `jsSrcsDir`.
45238
+ */
45239
+ const isCodegenSpecFile = (original, platform) => {
45240
+ const file = original.replace(/\.fb(\.|$)/, "$1");
45241
+ const name = path$1.basename(file);
45242
+ if (!/^(Native.+|.+NativeComponent)/.test(name) || name.endsWith(".d.ts") || file.endsWith("NativeUIManager.js") || file.includes("__tests")) return false;
45243
+ const parts = name.split(".");
45244
+ return parts.length === 2 || parts[1] === platform;
45245
+ };
45246
+ const findCodegenSpecs = async (packageRoot, platform) => {
45247
+ const config = (await readManifest(packageRoot))?.codegenConfig;
45248
+ const jsSrcsDirs = (config?.libraries ?? (config ? [config] : [])).map(({ jsSrcsDir }) => jsSrcsDir).filter((dir) => Boolean(dir));
45249
+ if (jsSrcsDirs.length === 0) return null;
45250
+ const files = [];
45251
+ for (const jsSrcsDir of jsSrcsDirs) files.push(...await (0, import_out.default)("**/*.{js,jsx,ts,tsx}", {
45252
+ cwd: path$1.resolve(packageRoot, jsSrcsDir),
45253
+ absolute: true,
45254
+ ignore: ["**/node_modules/**"]
45255
+ }));
45256
+ return files.filter((file, index) => files.indexOf(file) === index).filter((file) => isCodegenSpecFile(file, platform));
45257
+ };
45258
+ const isPatchFor = (file, name) => {
45259
+ const plus = name.replace("/", "+");
45260
+ const underscore = name.replace("/", "__");
45261
+ return file.startsWith(`${plus}+`) || file.startsWith(`${underscore}@`) || file.startsWith(`${underscore}+`) || file === `${underscore}.patch`;
45262
+ };
45263
+ const isWorkspaceRoot = async (dir) => {
45264
+ if (await fs$2.access(path$1.join(dir, "pnpm-workspace.yaml")).then(() => true).catch(() => false)) return true;
45265
+ return (await readManifest(dir))?.workspaces !== void 0;
45266
+ };
45267
+ /**
45268
+ * The folders whose patches apply to the app: its own and, in a monorepo,
45269
+ * the workspace root, where pnpm and patch-package keep them.
45270
+ */
45271
+ const findPatchRoots = async (cwd) => {
45272
+ let dir = cwd;
45273
+ while (!await isWorkspaceRoot(dir)) {
45274
+ const parent = path$1.dirname(dir);
45275
+ if (parent === dir) return [cwd];
45276
+ dir = parent;
45277
+ }
45278
+ return dir === cwd ? [cwd] : [cwd, dir];
45279
+ };
45280
+ /** `name` from a `name@version` or `name@npm:version` key. */
45281
+ const packageNameOf = (key) => {
45282
+ const at = key.lastIndexOf("@");
45283
+ return at > 0 ? key.slice(0, at) : key;
45284
+ };
45285
+ /**
45286
+ * The patch files a package manager records for each package in `root`:
45287
+ * pnpm's and Bun's `patchedDependencies` (in package.json or
45288
+ * pnpm-workspace.yaml) and Yarn's `patch:` resolutions.
45289
+ */
45290
+ const findConfiguredPatches = async (root) => {
45291
+ const manifest = await readManifest(root);
45292
+ const workspace = await fs$2.readFile(path$1.join(root, "pnpm-workspace.yaml"), "utf8").then((contents) => (0, import_readYaml.parseYaml)(contents)).catch(() => null);
45293
+ const patches = Object.entries({
45294
+ ...manifest?.patchedDependencies,
45295
+ ...manifest?.pnpm?.patchedDependencies,
45296
+ ...workspace?.patchedDependencies
45297
+ }).map(([key, file]) => ({
45298
+ name: packageNameOf(key),
45299
+ file: path$1.resolve(root, file)
45300
+ }));
45301
+ for (const [key, value] of [
45302
+ ...Object.entries(manifest?.resolutions ?? {}),
45303
+ ...Object.entries(manifest?.dependencies ?? {}),
45304
+ ...Object.entries(manifest?.devDependencies ?? {}),
45305
+ ...Object.entries(manifest?.optionalDependencies ?? {})
45306
+ ]) {
45307
+ const hash = value.lastIndexOf("#");
45308
+ if (!value.startsWith("patch:") || hash < 0) continue;
45309
+ const file = value.slice(hash + 1).split("::")[0];
45310
+ patches.push({
45311
+ name: packageNameOf(key),
45312
+ file: path$1.resolve(root, file.replace(/^~\//, ""))
45313
+ });
45314
+ }
45315
+ return patches;
45316
+ };
45317
+ /**
45318
+ * Fingerprints the native interface JavaScript depends on, not the native
45319
+ * build: the runtime and its native settings, each registry package as
45320
+ * `name@version`, the codegen
45321
+ * specs of the app and of local packages (a local package without specs by
45322
+ * its native files), patches to those packages, `nativeVersion`, and
45323
+ * `extraSources`. Version and build numbers, native implementations, and
45324
+ * JavaScript-only dependencies leave it unchanged.
45325
+ */
45326
+ const createInterfaceFingerprint = async (cwd, options, nativeInterface) => {
45327
+ const { platform } = options;
45328
+ const sources = /* @__PURE__ */ new Map();
45329
+ const addContents = (id, contents, reason) => {
45330
+ sources.set(`contents:${id}`, {
45331
+ type: "contents",
45332
+ id,
45333
+ contents,
45334
+ reasons: [reason],
45335
+ hash: sha256(contents)
45336
+ });
45337
+ };
45338
+ const addFile = async (absolutePath, reason) => {
45339
+ const filePath = toPosix(path$1.relative(cwd, absolutePath));
45340
+ sources.set(`file:${filePath}`, {
45341
+ type: "file",
45342
+ filePath,
45343
+ reasons: [reason],
45344
+ hash: sha256(await fs$2.readFile(absolutePath))
45345
+ });
45346
+ };
45347
+ addContents("runtime", nativeInterface.runtime, "runtime");
45348
+ for (const [key, value] of Object.entries(nativeInterface.settings ?? {})) addContents(`setting:${key}`, value, "native-setting");
45349
+ const addCodegenConfig = async (root) => {
45350
+ const config = (await readManifest(root))?.codegenConfig;
45351
+ if (config) {
45352
+ const other = platform === "ios" ? "android" : "ios";
45353
+ const ownSection = (section) => {
45354
+ const { [other]: _other, ...own } = section;
45355
+ return own;
45356
+ };
45357
+ const own = ownSection(config);
45358
+ if (Array.isArray(own.libraries)) own.libraries = own.libraries.map((library) => library && typeof library === "object" ? ownSection(library) : library);
45359
+ addContents(`codegen-config:${toPosix(path$1.relative(cwd, root)) || "."}`, stableStringify(own), "codegen-config");
45360
+ }
45361
+ };
45362
+ await addCodegenConfig(cwd);
45363
+ for (const specFile of await findCodegenSpecs(cwd, platform) ?? []) await addFile(specFile, "app-codegen-spec");
45364
+ for (const dependency of nativeInterface.dependencies) {
45365
+ if ("version" in dependency) {
45366
+ addContents(`dependency:${dependency.name}`, `${dependency.name}@${dependency.version}`, "native-dependency");
45367
+ continue;
45368
+ }
45369
+ const root = await fs$2.realpath(dependency.root);
45370
+ if (root.split(path$1.sep).includes("node_modules")) {
45371
+ const version = (await readManifest(root))?.version ?? "unknown";
45372
+ addContents(`dependency:${dependency.name}`, `${dependency.name}@${version}`, "native-dependency");
45373
+ continue;
45374
+ }
45375
+ await addCodegenConfig(root);
45376
+ const found = await findCodegenSpecs(root, platform);
45377
+ const specs = found && found.length > 0 ? found : null;
45378
+ const files = specs ?? [...await (0, import_out.default)([...NATIVE_FILES[platform]], {
45379
+ cwd: root,
45380
+ absolute: true,
45381
+ ignore: [...GENERATED_DIRS, ...TEST_DIRS]
45382
+ })];
45383
+ const expoModuleConfig = await readExpoModuleConfig(root, platform);
45384
+ if (expoModuleConfig !== null) addContents(`expo-module-config:${dependency.name}`, expoModuleConfig, "local-dependency-expo-module");
45385
+ for (const file of dependency.files ?? []) {
45386
+ const real = await fs$2.realpath(file).catch(() => void 0);
45387
+ if (real) await addFile(real, "local-dependency-native");
45388
+ }
45389
+ if (!specs) for (const dir of dependency.sourceDirs ?? []) {
45390
+ const sourceDir = await fs$2.realpath(dir).catch(() => void 0);
45391
+ if (!sourceDir) continue;
45392
+ files.push(...await (0, import_out.default)([...NATIVE_SOURCE_FILES[platform]], {
45393
+ cwd: sourceDir,
45394
+ absolute: true,
45395
+ ignore: [
45396
+ ...GENERATED_DIRS,
45397
+ ...TEST_DIRS,
45398
+ ...await nestedPackages(sourceDir)
45399
+ ]
45400
+ }));
45401
+ }
45402
+ for (const file of new Set(files)) await addFile(file, specs ? "local-dependency-codegen-spec" : "local-dependency-native");
45403
+ }
45404
+ const patchedNames = ["react-native", ...nativeInterface.dependencies.map(({ name }) => name)];
45405
+ for (const root of await findPatchRoots(cwd)) {
45406
+ const dir = path$1.join(root, "patches");
45407
+ const patches = await fs$2.readdir(dir).catch(() => []);
45408
+ for (const patch of patches) if (patchedNames.some((name) => isPatchFor(patch, name))) await addFile(path$1.join(dir, patch), "native-dependency-patch");
45409
+ for (const { name, file } of await findConfiguredPatches(root)) if (await fs$2.access(file).then(() => true, () => false) && patchedNames.includes(name)) await addFile(file, "native-dependency-patch");
45410
+ }
45411
+ if (options.nativeVersion !== void 0) addContents("nativeVersion", String(options.nativeVersion), "config");
45412
+ const extraSources = Array.isArray(options.extraSources) ? options.extraSources : options.extraSources?.[platform] ?? [];
45413
+ if (extraSources.length > 0) {
45414
+ const files = await (0, import_out.default)(extraSources, {
45415
+ cwd,
45416
+ absolute: true,
45417
+ dot: true,
45418
+ ignore: [...GENERATED_DIRS, ...options.ignorePaths ?? []],
45419
+ onlyFiles: false,
45420
+ markDirectories: true
45421
+ });
45422
+ for (const match of files) {
45423
+ const prefix = toPosix(path$1.relative(cwd, match)).replace(/\/$/, "");
45424
+ const matches = match.endsWith("/") ? await (0, import_out.default)(prefix ? `${prefix}/**/*` : "**/*", {
45425
+ cwd,
45426
+ absolute: true,
45427
+ dot: true,
45428
+ ignore: [...GENERATED_DIRS, ...options.ignorePaths ?? []]
45429
+ }) : [match];
45430
+ for (const file of matches) await addFile(file, "extra-source");
45431
+ }
45432
+ }
45433
+ const sorted = [...sources.entries()].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([, source]) => source);
45434
+ const digest = createHash("sha256").update(`${INTERFACE_FINGERPRINT_ENGINE}\0`);
45435
+ for (const source of sorted) digest.update(`${source.type}\0${source.type === "contents" ? source.id : source.filePath}\0${source.hash}\0`);
45436
+ return {
45437
+ engine: INTERFACE_FINGERPRINT_ENGINE,
45438
+ hash: digest.digest("hex"),
45439
+ sources: sorted
45440
+ };
45441
+ };
45442
+ /**
45443
+ * The fingerprint of a React Native CLI project: the native interface
45444
+ * `react-native config` reports. The bare adapter's fingerprint, and the one
45445
+ * a build adapter without its own gets.
45446
+ */
45447
+ const reactNativeCliFingerprint = async (cwd, options) => createInterfaceFingerprint(cwd, options, await readReactNativeInterface({
45448
+ cwd,
45449
+ command: process.execPath,
45450
+ args: [path$1.join(path$1.dirname(require$2.resolve("react-native/package.json", { paths: [cwd] })), "cli.js"), "config"],
45451
+ platform: options.platform
45452
+ }));
45453
+ //#endregion
44986
45454
  //#region ../../node_modules/.pnpm/es-toolkit@1.47.0/node_modules/es-toolkit/dist/predicate/isPlainObject.mjs
44987
45455
  /**
44988
45456
  * Checks if a given value is a plain object.
@@ -45116,7 +45584,6 @@ function mergeWith(target, source, merge) {
45116
45584
  }
45117
45585
  //#endregion
45118
45586
  //#region src/loadConfig.ts
45119
- var import_out = /* @__PURE__ */ __toESM(require_out(), 1);
45120
45587
  const getDefaultPlatformConfig = () => {
45121
45588
  let infoPlistPaths = [];
45122
45589
  try {
@@ -45564,4 +46031,4 @@ function transformTemplate(templateString, values) {
45564
46031
  return result;
45565
46032
  }
45566
46033
  //#endregion
45567
- export { BuildLogger, ConfigBuilder, HOT_UPDATER_SERVER_PACKAGE_VERSION_ENV, HotUpdateDirUtil, InitEnvFileError, InitError, LegacyInfrastructureError, MissingInitInputsError, ServerDefinitionError, assembleServer, assertInfrastructureGenerationAtUrl, assertInfrastructureGenerationPayload, assertInitInputs, assertInitProviderInputs, banner, clientAuthOf, typedColors as colors, confirmInitInputPersistence, copyDirToTmp, createHotUpdaterConfigScaffold, createHotUpdaterConfigScaffoldFromBuilder, createLogWriter, createMigrator, createTarBrTargetFiles, createZip, decryptJson, defineInitProvider, encryptJson, ensureInstallPackages, generateClientCredential, generateSchema, generatesSchema, getAndroidSdkPath, getBundleSigningPublicKey, getCwd, getHotUpdaterEnvValue, getHotUpdaterInitInputEnv, getInitProviderEnvVars, getInitProviderTextPromptValues, getMissingInitInputs, getMissingInitProviderInputs, getPackageManager, getReactNativeMetadatas, getStorageFileByteSize, isServerDefinition, link, loadConfig, loadPlatformConfigs, log, makeEnv, moduleSpecifiersOf, p, prepareBundleSigning, printAppSetup, printBanner, provisionClientCredential, putStorageFile, readBundleSigningPublicKeyFile, readHotUpdaterEnv, readHotUpdaterInitEnv, readPackageUp, renderAppImports, renderAppSetup, renderImportStatements, resolveHotUpdaterServerVersion, resolveInitProviderInput, resolveInitProviderInputs, resolvePackageVersion, serverDefinitionOf, shouldAutoSelectOnlyInitResource, stripAnsi, transformEnv, transformTemplate, writeHotUpdaterConfig, writeHotUpdaterFiles };
46034
+ export { BuildLogger, ConfigBuilder, HOT_UPDATER_SERVER_PACKAGE_VERSION_ENV, HotUpdateDirUtil, INTERFACE_FINGERPRINT_ENGINE, InitEnvFileError, InitError, LegacyInfrastructureError, MissingInitInputsError, ServerDefinitionError, assembleServer, assertInfrastructureGenerationAtUrl, assertInfrastructureGenerationPayload, assertInitInputs, assertInitProviderInputs, banner, clientAuthOf, typedColors as colors, confirmInitInputPersistence, copyDirToTmp, createHotUpdaterConfigScaffold, createHotUpdaterConfigScaffoldFromBuilder, createInterfaceFingerprint, createLogWriter, createMigrator, createTarBrTargetFiles, createZip, decryptJson, defineInitProvider, encryptJson, ensureInstallPackages, generateClientCredential, generateSchema, generatesSchema, getAndroidSdkPath, getBundleSigningPublicKey, getCwd, getHotUpdaterEnvValue, getHotUpdaterInitInputEnv, getInitProviderEnvVars, getInitProviderTextPromptValues, getMissingInitInputs, getMissingInitProviderInputs, getPackageManager, getReactNativeMetadatas, getStorageFileByteSize, isCodegenSpecFile, isServerDefinition, link, loadConfig, loadPlatformConfigs, log, makeEnv, moduleSpecifiersOf, p, prepareBundleSigning, printAppSetup, printBanner, provisionClientCredential, putStorageFile, reactNativeCliFingerprint, readBundleSigningPublicKeyFile, readHotUpdaterEnv, readHotUpdaterInitEnv, readPackageUp, readReactNativeInterface, renderAppImports, renderAppSetup, renderImportStatements, resolveExpoAutolinking, resolveHotUpdaterServerVersion, resolveInitProviderInput, resolveInitProviderInputs, resolvePackageVersion, serverDefinitionOf, shouldAutoSelectOnlyInitResource, stripAnsi, transformEnv, transformTemplate, writeHotUpdaterConfig, writeHotUpdaterFiles };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hot-updater/cli-tools",
3
- "version": "1.0.8",
3
+ "version": "1.1.0",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=20.19.0"
@@ -49,9 +49,9 @@
49
49
  "oxc-parser": "0.141.0",
50
50
  "oxc-transform": "0.141.0",
51
51
  "unconfig": "7.5.0",
52
- "@hot-updater/protocol": "1.0.8",
53
- "@hot-updater/plugin-core": "1.0.8",
54
- "@hot-updater/server": "1.0.8"
52
+ "@hot-updater/protocol": "1.1.0",
53
+ "@hot-updater/plugin-core": "1.1.0",
54
+ "@hot-updater/server": "1.1.0"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@clack/prompts": "1.7.0",
@@ -65,7 +65,7 @@
65
65
  "tar": "^7.5.16",
66
66
  "verkit": "0.4.0",
67
67
  "workspace-tools": "^0.41.7",
68
- "@hot-updater/test-utils": "1.0.8"
68
+ "@hot-updater/test-utils": "1.1.0"
69
69
  },
70
70
  "inlinedDependencies": {
71
71
  "@babel/code-frame": "7.29.0",