@hot-updater/cli-tools 1.2.0-rc.4 → 1.2.0-rc.5
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 +96 -39
- package/dist/index.mjs +229 -120
- package/package.json +5 -5
package/dist/index.d.mts
CHANGED
|
@@ -411,19 +411,14 @@ interface CredentialResolver {
|
|
|
411
411
|
*/
|
|
412
412
|
dispose(): Promise<void>;
|
|
413
413
|
}
|
|
414
|
-
/**
|
|
415
|
-
* The command that stores a credential of `kind` you have as `name` through
|
|
416
|
-
* the config's `secrets` adapter, from its files.
|
|
417
|
-
*/
|
|
418
|
-
declare const credentialPutCommand: (kind: CredentialKind, name?: string) => string;
|
|
419
414
|
/** The credentials Hot Updater issues itself after a sign-in. */
|
|
420
415
|
type IssuedCredentialKind = Exclude<CredentialKind, "android-keystore">;
|
|
421
416
|
/**
|
|
422
|
-
* The command that
|
|
423
|
-
*
|
|
424
|
-
*
|
|
417
|
+
* The command that stores the key Hot Updater issues as `name`: an App
|
|
418
|
+
* Store Connect API key after an Apple Account sign-in, a Play service
|
|
419
|
+
* account after a Google one, and an upload keystore it creates.
|
|
425
420
|
*/
|
|
426
|
-
declare const credentialIssueCommand: (kind:
|
|
421
|
+
declare const credentialIssueCommand: (kind: CredentialKind, name?: string) => string;
|
|
427
422
|
/** The command group of each issued kind's shortcut, `<group> login`. */
|
|
428
423
|
declare const CREDENTIAL_LOGIN_GROUPS: {
|
|
429
424
|
readonly "asc-api-key": "apple";
|
|
@@ -438,7 +433,8 @@ declare const CREDENTIAL_LOGIN_GROUPS: {
|
|
|
438
433
|
declare const credentialLoginCommand: (kind: IssuedCredentialKind, name?: string) => string;
|
|
439
434
|
/**
|
|
440
435
|
* The command that stores a missing credential of `kind` as `name`: the
|
|
441
|
-
* keystore
|
|
436
|
+
* upload keystore Hot Updater creates, or the key it issues after a
|
|
437
|
+
* sign-in.
|
|
442
438
|
*/
|
|
443
439
|
declare const credentialStoreCommand: (kind: CredentialKind, name?: string) => string;
|
|
444
440
|
declare class CredentialUnavailableError extends Error {
|
|
@@ -569,6 +565,15 @@ type WriteHotUpdaterConfigResult = {
|
|
|
569
565
|
path: string;
|
|
570
566
|
reason?: string;
|
|
571
567
|
};
|
|
568
|
+
/**
|
|
569
|
+
* `const <name> = secretEnv([...names])`, a helper a managed config reads
|
|
570
|
+
* credentials with: from the environment and `.env.hotupdater` first, then
|
|
571
|
+
* from the config's `secrets` adapter. `storageSecrets` and
|
|
572
|
+
* `databaseSecrets` read each adapter's own, so a command that reaches only
|
|
573
|
+
* one needs only its credentials; `deploySecrets` reads what both share.
|
|
574
|
+
* Its builder imports `secretEnv` from `hot-updater`.
|
|
575
|
+
*/
|
|
576
|
+
declare const secretEnvHelper: (name: "databaseSecrets" | "deploySecrets" | "storageSecrets", names: readonly string[]) => ManagedHelperStatement;
|
|
572
577
|
declare const createHotUpdaterConfigScaffold: ({ build, storage, database, plugins, extraImports, helperStatements, updateStrategy }: CreateHotUpdaterConfigScaffoldOptions) => HotUpdaterConfigScaffold;
|
|
573
578
|
declare const createHotUpdaterConfigScaffoldFromBuilder: (builder: ConfigBuilder, { helperStatements, updateStrategy }?: CreateHotUpdaterConfigScaffoldFromBuilderOptions) => HotUpdaterConfigScaffold;
|
|
574
579
|
/**
|
|
@@ -596,7 +601,12 @@ type HotUpdaterInitEnv = {
|
|
|
596
601
|
readonly managedEnv: Readonly<Record<string, string>>;
|
|
597
602
|
};
|
|
598
603
|
declare const getHotUpdaterInitInputEnv: ({ env, managedEnv }: HotUpdaterInitEnv, nonInteractive: boolean) => Readonly<Record<string, string>>;
|
|
599
|
-
|
|
604
|
+
/**
|
|
605
|
+
* Init's inputs: `.env.hotupdater` (`managedEnv`), with `envFile`'s over
|
|
606
|
+
* it (`env`). `secretsEnv`, the credentials init read from a team secrets
|
|
607
|
+
* store, sits under both, as values the file would hold.
|
|
608
|
+
*/
|
|
609
|
+
declare const readHotUpdaterInitEnv: (cwd: string, envFile?: string, secretsEnv?: Readonly<Record<string, string>>) => Promise<HotUpdaterInitEnv>;
|
|
600
610
|
declare const getHotUpdaterEnvValue: (env: Readonly<Record<string, string>>, key: string) => string | undefined;
|
|
601
611
|
/**
|
|
602
612
|
* An env file's variables as `process.loadEnvFile` sets them, the way
|
|
@@ -668,6 +678,13 @@ type InitProviderInputsDefinition = Readonly<Record<string, InitProviderInputDef
|
|
|
668
678
|
type InitProviderDefinition<TInputs extends InitProviderInputsDefinition = InitProviderInputsDefinition> = {
|
|
669
679
|
readonly inputs: TInputs;
|
|
670
680
|
readonly label: string;
|
|
681
|
+
/**
|
|
682
|
+
* The variables of the credentials the config init writes reads with
|
|
683
|
+
* `secretEnv`, such as an API token. With a team `secrets` adapter, init
|
|
684
|
+
* stores them there with the app's API key, and reads one the
|
|
685
|
+
* environment and the env file lack from it.
|
|
686
|
+
*/
|
|
687
|
+
readonly secrets?: readonly string[];
|
|
671
688
|
};
|
|
672
689
|
declare const defineInitProvider: <const TInputs extends InitProviderInputsDefinition>(provider: InitProviderDefinition<TInputs>) => InitProviderDefinition<TInputs>;
|
|
673
690
|
declare const shouldAutoSelectOnlyInitResource: ({ availableResourceCount, savedIdentifier }: {
|
|
@@ -706,6 +723,12 @@ declare const getInitProviderEnvVars: <TInputs extends InitProviderInputsDefinit
|
|
|
706
723
|
type RunInitOptions = {
|
|
707
724
|
readonly build: BuildType;
|
|
708
725
|
readonly envFile?: string;
|
|
726
|
+
/**
|
|
727
|
+
* Credentials the environment and the env files lack, read from the
|
|
728
|
+
* config's team `secrets` adapter (`InitProviderDefinition.secrets`):
|
|
729
|
+
* init reads them as if `.env.hotupdater` held them.
|
|
730
|
+
*/
|
|
731
|
+
readonly secretsEnv?: Readonly<Record<string, string>>;
|
|
709
732
|
};
|
|
710
733
|
declare class InitError extends Error {
|
|
711
734
|
readonly name: string;
|
|
@@ -909,8 +932,8 @@ type ConfigResponse = RequiredDeep<Omit<ConfigInput, "database" | "storage" | "p
|
|
|
909
932
|
signing?: ReturnType<typeof normalizeSigningConfig>;
|
|
910
933
|
/**
|
|
911
934
|
* Where `secret:` references and issued keys are read and stored: the
|
|
912
|
-
* config's adapter
|
|
913
|
-
*
|
|
935
|
+
* config's adapter alone, or `localSecrets()` when it sets none
|
|
936
|
+
* (`resolveSecretsAdapter`).
|
|
914
937
|
*/
|
|
915
938
|
secrets: SecretsAdapter;
|
|
916
939
|
};
|
|
@@ -927,6 +950,11 @@ declare const loadPlatformConfigs: <TPlatform extends Platform>(platforms: reado
|
|
|
927
950
|
readonly platform: TPlatform;
|
|
928
951
|
readonly config: ConfigResponse;
|
|
929
952
|
}[]>;
|
|
953
|
+
/**
|
|
954
|
+
* The names every `secretEnv` the config created while it loaded reads,
|
|
955
|
+
* such as `HOT_UPDATER_CLOUDFLARE_API_TOKEN`, in the order it created them.
|
|
956
|
+
*/
|
|
957
|
+
declare const getSecretEnvNames: (config: ConfigResponse) => readonly string[];
|
|
930
958
|
//#endregion
|
|
931
959
|
//#region src/serverDefinition.d.ts
|
|
932
960
|
/** A plugin's endpoint on a server's `handlers.client`. */
|
|
@@ -2410,9 +2438,9 @@ declare const LOCAL_SECRETS_NAME = "localSecrets";
|
|
|
2410
2438
|
* `~/.hot-updater/secrets/<project-key>/<name>.json`; `set` writes that
|
|
2411
2439
|
* file, 0600 in a 0700 directory, outside the project, so it survives
|
|
2412
2440
|
* `node_modules` and is never committed. `HOT_UPDATER_HOME` moves
|
|
2413
|
-
* `~/.hot-updater`. The project key
|
|
2414
|
-
*
|
|
2415
|
-
*
|
|
2441
|
+
* `~/.hot-updater`. The project key hashes the checkout's real path
|
|
2442
|
+
* (`getProjectKey`), so each clone of an app has its own directory, and
|
|
2443
|
+
* another clone or machine signs in and issues a key of its own.
|
|
2416
2444
|
*
|
|
2417
2445
|
* @example
|
|
2418
2446
|
* export default defineConfig({ secrets: localSecrets(), ... });
|
|
@@ -2424,36 +2452,31 @@ interface LocalSecretsOptions {
|
|
|
2424
2452
|
* each, such as `~/.hot-updater/secrets/my-app`: `~` is the home
|
|
2425
2453
|
* directory, and a relative path is the project's. A directory in a git
|
|
2426
2454
|
* work tree must be one git ignores. Defaults to
|
|
2427
|
-
* `~/.hot-updater/secrets/<project-key>`, whose key
|
|
2428
|
-
*
|
|
2455
|
+
* `~/.hot-updater/secrets/<project-key>`, whose key hashes the
|
|
2456
|
+
* checkout's real path, one per clone (`getProjectKey`).
|
|
2429
2457
|
*/
|
|
2430
2458
|
readonly path?: string;
|
|
2431
2459
|
}
|
|
2432
2460
|
/**
|
|
2433
|
-
* The
|
|
2434
|
-
*
|
|
2435
|
-
*
|
|
2436
|
-
* when the adapter stores nothing. Without an adapter, it is
|
|
2437
|
-
* `localSecrets()` itself. Keys Hot Updater issues, and credentials
|
|
2438
|
-
* `secrets put` imports, are reused by the next build on this machine with
|
|
2439
|
-
* no step in between.
|
|
2461
|
+
* The `secrets` adapter a config load uses: the config's own adapter as it
|
|
2462
|
+
* is, with no store on this machine under it, or `localSecrets()` when the
|
|
2463
|
+
* config sets none.
|
|
2440
2464
|
*/
|
|
2441
|
-
declare const
|
|
2465
|
+
declare const resolveSecretsAdapter: (configured?: SecretsAdapter) => SecretsAdapter;
|
|
2442
2466
|
/**
|
|
2443
|
-
*
|
|
2444
|
-
*
|
|
2445
|
-
* default one.
|
|
2467
|
+
* `localSecrets()`'s store, for `localSecrets()` itself; undefined for any
|
|
2468
|
+
* other adapter, which keeps nothing on this machine.
|
|
2446
2469
|
*/
|
|
2447
|
-
declare const getLocalSecretsStore: (secrets: SecretsAdapter) => LocalSecretsStore;
|
|
2470
|
+
declare const getLocalSecretsStore: (secrets: SecretsAdapter) => LocalSecretsStore | undefined;
|
|
2448
2471
|
/**
|
|
2449
|
-
* The config's own `secrets` adapter
|
|
2450
|
-
*
|
|
2451
|
-
*
|
|
2472
|
+
* The config's own `secrets` adapter: undefined for `localSecrets()`,
|
|
2473
|
+
* which is the adapter when the config sets none. `secretEnv` reads deploy
|
|
2474
|
+
* credentials from it.
|
|
2452
2475
|
*/
|
|
2453
2476
|
declare const getConfiguredAdapter: (secrets: SecretsAdapter) => SecretsAdapter | undefined;
|
|
2454
2477
|
/**
|
|
2455
|
-
* Where `secrets.set` stores `name`, for messages: the config's adapter
|
|
2456
|
-
*
|
|
2478
|
+
* Where `secrets.set` stores `name`, for messages: the config's adapter,
|
|
2479
|
+
* else `localSecrets()`'s file.
|
|
2457
2480
|
*/
|
|
2458
2481
|
declare const describeStoredSecret: (secrets: SecretsAdapter, name: string) => string;
|
|
2459
2482
|
/**
|
|
@@ -2473,10 +2496,12 @@ declare const readSecretWithSource: (secrets: SecretsAdapter, name: string, cwd:
|
|
|
2473
2496
|
declare const keepsKeysOnThisMachine: (secrets: SecretsAdapter) => boolean;
|
|
2474
2497
|
/**
|
|
2475
2498
|
* The `secrets` adapter of one config load, filled once the config it
|
|
2476
|
-
* evaluates resolves: each `secretEnv` the evaluation creates reads from it
|
|
2499
|
+
* evaluates resolves: each `secretEnv` the evaluation creates reads from it,
|
|
2500
|
+
* and adds the names it reads to `names`.
|
|
2477
2501
|
*/
|
|
2478
2502
|
interface SecretsScope {
|
|
2479
2503
|
secrets?: SecretsAdapter;
|
|
2504
|
+
names?: Set<string>;
|
|
2480
2505
|
}
|
|
2481
2506
|
/**
|
|
2482
2507
|
* Evaluates a config, the file or a config function, with `scope` as the
|
|
@@ -2493,8 +2518,8 @@ declare const withSecretsScope: <T>(scope: SecretsScope, evaluate: () => Promise
|
|
|
2493
2518
|
*/
|
|
2494
2519
|
declare const setConfiguredSecrets: (secrets: SecretsAdapter) => void;
|
|
2495
2520
|
/**
|
|
2496
|
-
* The config's `secrets` adapter
|
|
2497
|
-
*
|
|
2521
|
+
* The config's `secrets` adapter (`resolveSecretsAdapter`), `localSecrets()`
|
|
2522
|
+
* until a config loads.
|
|
2498
2523
|
*/
|
|
2499
2524
|
declare const getConfiguredSecrets: () => SecretsAdapter;
|
|
2500
2525
|
/** Where an adapter keeps `name`, for messages. */
|
|
@@ -2524,6 +2549,38 @@ declare const describeSecret: (secrets: SecretsAdapter, name: string) => string;
|
|
|
2524
2549
|
*/
|
|
2525
2550
|
declare const secretEnv: <const TName extends string>(names: readonly TName[]) => (() => Promise<Readonly<Record<TName, string>>>);
|
|
2526
2551
|
//#endregion
|
|
2552
|
+
//#region src/teamSecrets.d.ts
|
|
2553
|
+
/**
|
|
2554
|
+
* The client API key the managed servers' `apiKeys()` gives an app, which
|
|
2555
|
+
* init saves (`API_KEY_ENV` in `@hot-updater/plugin-api-keys`). The config
|
|
2556
|
+
* never reads it; a team store shares it with the deploy credentials, so a
|
|
2557
|
+
* teammate's init registers the same key again rather than a new one.
|
|
2558
|
+
*/
|
|
2559
|
+
declare const MANAGED_CLIENT_CREDENTIAL_ENV = "HOT_UPDATER_API_KEY";
|
|
2560
|
+
/**
|
|
2561
|
+
* The config's own `secrets` adapter, the team store: undefined when the
|
|
2562
|
+
* config sets none, which is `localSecrets()`. Loading the config runs its
|
|
2563
|
+
* `process.loadEnvFile(".env.hotupdater")`, which init must not see, so
|
|
2564
|
+
* `process.env` is restored after it.
|
|
2565
|
+
*/
|
|
2566
|
+
declare const loadTeamSecrets: () => Promise<SecretsAdapter | undefined>;
|
|
2567
|
+
/**
|
|
2568
|
+
* `names` neither the environment nor `env`, init's env files, sets, read
|
|
2569
|
+
* from `secrets` in one call: the values a teammate's init, or a run after
|
|
2570
|
+
* the file's lines were deleted, replays with.
|
|
2571
|
+
*/
|
|
2572
|
+
declare const readMissingFromTeamSecrets: (secrets: SecretsAdapter, names: readonly string[], { env, cwd }: {
|
|
2573
|
+
readonly env: Readonly<Record<string, string>>;
|
|
2574
|
+
readonly cwd: string;
|
|
2575
|
+
}) => Promise<Readonly<Record<string, string>>>;
|
|
2576
|
+
/**
|
|
2577
|
+
* Stores each of `names` that `.env.hotupdater` holds through `secrets`,
|
|
2578
|
+
* then reads them back in one call. The file is left as it is. Returns the
|
|
2579
|
+
* names stored, in the order of `names`; throws when the adapter reads
|
|
2580
|
+
* back another value.
|
|
2581
|
+
*/
|
|
2582
|
+
declare const storeEnvFileSecrets: (secrets: SecretsAdapter, names: readonly string[], cwd: string) => Promise<readonly string[]>;
|
|
2583
|
+
//#endregion
|
|
2527
2584
|
//#region src/storageFiles.d.ts
|
|
2528
2585
|
declare const getStorageFileByteSize: (filePath: string) => Promise<number>;
|
|
2529
2586
|
declare const putStorageFile: (storage: StorageAdapterWith<"put">, key: string, filePath: string) => Promise<StoragePutResult & {
|
|
@@ -2547,4 +2604,4 @@ type TransformTemplateArgs<T extends string> = { [Key in ExtractPlaceholders<T>]
|
|
|
2547
2604
|
*/
|
|
2548
2605
|
declare function transformTemplate<T extends string>(templateString: T, values: TransformTemplateArgs<T>): string;
|
|
2549
2606
|
//#endregion
|
|
2550
|
-
export { AppClientCredential, AppClientPlugin, AssembleServerOptions, AssembledServer, BuildFailedError, BuildFailureSummaryConfig, BuildLogger, BuildLoggerConfig, BuildType, BundleSigningSession, CREDENTIAL_ENV_NAMES, CREDENTIAL_LOGIN_GROUPS, ClientAuthSpec, ClientCredentialSpec, ConfigBuilder, ConfigBuilderScaffold, ConfigResponse, CreateCredentialResolverOptions, CreateHotUpdaterConfigScaffoldFromBuilderOptions, CreateHotUpdaterConfigScaffoldOptions, type CredentialFiles, CredentialKind, CredentialRequest, CredentialResolver, CredentialUnavailableError, Credentials, DEFAULT_SIGNING_KEY_SECRET, HOT_UPDATER_HOME_ENV, HOT_UPDATER_SERVER_PACKAGE_VERSION_ENV, HotUpdateDirUtil, HotUpdaterConfigOptions, HotUpdaterConfigScaffold, HotUpdaterInitEnv, HotUpdaterLogWriter, INTERFACE_FINGERPRINT_ENGINE, ImportInfo, InitEnvFileError, InitError, InitProviderDefinition, InitProviderInputDefinition, InitProviderInputPersistence, IssuedCredentialKind, LOCAL_SECRETS_NAME, LegacyInfrastructureError, LocalSecretsOptions, LocalSecretsStore, ManagedHelperStatement, ManagedHelperStrategy, MissingInitInputsError, NativeDependency, NativeInterface, PromptProgress, PromptSpinner, ProviderConfig, ProvisionedClientCredential, ReactNativeMetadata, ReadPackageUpResult, RunInitOptions, SecretsScope, ServerClientEndpoint, ServerDefinition, ServerDefinitionError, WriteHotUpdaterConfigResult, assembleServer, assertInfrastructureGenerationAtUrl, assertInfrastructureGenerationPayload, assertInitInputs, assertInitProviderInputs, assertUnreservedEnv, autolinkingConfigCommand, banner, clientAuthOf, typedColors as colors, confirmInitInputPersistence, copyDirToTmp, createCredentialResolver, createHotUpdaterConfigScaffold, createHotUpdaterConfigScaffoldFromBuilder, createInterfaceFingerprint, createLocalSecretsStore, createLogWriter, createMigrator, createMinBundleId, createTarBrTargetFiles, createZip, credentialIssueCommand, credentialLoginCommand,
|
|
2607
|
+
export { AppClientCredential, AppClientPlugin, AssembleServerOptions, AssembledServer, BuildFailedError, BuildFailureSummaryConfig, BuildLogger, BuildLoggerConfig, BuildType, BundleSigningSession, CREDENTIAL_ENV_NAMES, CREDENTIAL_LOGIN_GROUPS, ClientAuthSpec, ClientCredentialSpec, ConfigBuilder, ConfigBuilderScaffold, ConfigResponse, CreateCredentialResolverOptions, CreateHotUpdaterConfigScaffoldFromBuilderOptions, CreateHotUpdaterConfigScaffoldOptions, type CredentialFiles, CredentialKind, CredentialRequest, CredentialResolver, CredentialUnavailableError, Credentials, DEFAULT_SIGNING_KEY_SECRET, HOT_UPDATER_HOME_ENV, HOT_UPDATER_SERVER_PACKAGE_VERSION_ENV, HotUpdateDirUtil, HotUpdaterConfigOptions, HotUpdaterConfigScaffold, HotUpdaterInitEnv, HotUpdaterLogWriter, INTERFACE_FINGERPRINT_ENGINE, ImportInfo, InitEnvFileError, InitError, InitProviderDefinition, InitProviderInputDefinition, InitProviderInputPersistence, IssuedCredentialKind, LOCAL_SECRETS_NAME, LegacyInfrastructureError, LocalSecretsOptions, LocalSecretsStore, MANAGED_CLIENT_CREDENTIAL_ENV, ManagedHelperStatement, ManagedHelperStrategy, MissingInitInputsError, NativeDependency, NativeInterface, PromptProgress, PromptSpinner, ProviderConfig, ProvisionedClientCredential, ReactNativeMetadata, ReadPackageUpResult, RunInitOptions, SecretsScope, ServerClientEndpoint, ServerDefinition, ServerDefinitionError, WriteHotUpdaterConfigResult, assembleServer, assertInfrastructureGenerationAtUrl, assertInfrastructureGenerationPayload, assertInitInputs, assertInitProviderInputs, assertUnreservedEnv, autolinkingConfigCommand, banner, clientAuthOf, typedColors as colors, confirmInitInputPersistence, copyDirToTmp, createCredentialResolver, createHotUpdaterConfigScaffold, createHotUpdaterConfigScaffoldFromBuilder, createInterfaceFingerprint, createLocalSecretsStore, createLogWriter, createMigrator, createMinBundleId, createTarBrTargetFiles, createZip, credentialIssueCommand, credentialLoginCommand, credentialStoreCommand, decryptJson, defineInitProvider, describeSecret, describeStoredSecret, encryptJson, ensureInstallPackages, generateClientCredential, generateSchema, generatesSchema, getAndroidSdkPath, getBundleSigningPublicKey, getConfiguredAdapter, getConfiguredSecrets, getCwd, getHotUpdaterEnvValue, getHotUpdaterHome, getHotUpdaterInitInputEnv, getInitProviderEnvVars, getInitProviderTextPromptValues, getLocalKeyFile, getLocalKeyStoreDir, getLocalSecretsStore, getMissingInitInputs, getMissingInitProviderInputs, getPackageManager, getProjectKey, getReactNativeMetadatas, getSecretEnvNames, getStorageFileByteSize, isCI, isCodegenSpecFile, isReservedVariableName, isSecretName, isServerDefinition, keepsKeysOnThisMachine, link, loadConfig, loadPlatformConfigs, loadTeamSecrets, localSecrets, log, makeEnv, maskSecretValues, moduleSpecifiersOf, nonInteractiveReason, p, parseLoadedEnv, parseSecretReference, prepareBundleSigning, printAppSetup, printBanner, provisionClientCredential, putStorageFile, reactNativeCliFingerprint, reactNativeConfigCommand, readBundleSigningPublicKeyFile, readHotUpdaterInitEnv, readLoadedHotUpdaterEnv, readLocalKey, readMissingFromTeamSecrets, readPackageUp, readReactNativeInterface, readSecretVariables, readSecretWithSource, registerCredentialValue, registerSecretValue, renderAppImports, renderAppSetup, renderImportStatements, resolveExpoAutolinking, resolveHotUpdaterServerVersion, resolveInitProviderInput, resolveInitProviderInputs, resolveLocalSecretsPath, resolvePackageVersion, resolveSecretsAdapter, secretEnv, secretEnvHelper, secretVariableName, serverDefinitionOf, setConfiguredSecrets, shouldAutoSelectOnlyInitResource, storeEnvFileSecrets, stripAnsi, transformEnv, transformTemplate, withCredentialResolver, withSecretsScope, writeHotUpdaterConfig, writeHotUpdaterFiles, writeLocalKey };
|
package/dist/index.mjs
CHANGED
|
@@ -25247,9 +25247,17 @@ const readEnvFile = async (filePath, allowMissing) => {
|
|
|
25247
25247
|
return parseEnv$1(content);
|
|
25248
25248
|
};
|
|
25249
25249
|
const getHotUpdaterInitInputEnv = ({ env, managedEnv }, nonInteractive) => nonInteractive ? env : managedEnv;
|
|
25250
|
-
|
|
25250
|
+
/**
|
|
25251
|
+
* Init's inputs: `.env.hotupdater` (`managedEnv`), with `envFile`'s over
|
|
25252
|
+
* it (`env`). `secretsEnv`, the credentials init read from a team secrets
|
|
25253
|
+
* store, sits under both, as values the file would hold.
|
|
25254
|
+
*/
|
|
25255
|
+
const readHotUpdaterInitEnv = async (cwd, envFile, secretsEnv = {}) => {
|
|
25251
25256
|
const savedEnvPath = path.resolve(cwd, HOT_UPDATER_ENV_PATH);
|
|
25252
|
-
const savedEnv =
|
|
25257
|
+
const savedEnv = {
|
|
25258
|
+
...secretsEnv,
|
|
25259
|
+
...await readEnvFile(savedEnvPath, true)
|
|
25260
|
+
};
|
|
25253
25261
|
if (!envFile) return {
|
|
25254
25262
|
env: {},
|
|
25255
25263
|
managedEnv: savedEnv
|
|
@@ -25511,8 +25519,7 @@ const RESERVED_PREFIXES = [
|
|
|
25511
25519
|
"LD_",
|
|
25512
25520
|
"DYLD_",
|
|
25513
25521
|
"GIT_",
|
|
25514
|
-
"NPM_CONFIG_"
|
|
25515
|
-
"HOT_UPDATER_GOOGLE_OAUTH_CLIENT_"
|
|
25522
|
+
"NPM_CONFIG_"
|
|
25516
25523
|
];
|
|
25517
25524
|
/**
|
|
25518
25525
|
* Whether `name` is a variable no secret may set: one a loader or
|
|
@@ -25572,8 +25579,6 @@ const readSecretVariables = async (names, cwd) => {
|
|
|
25572
25579
|
/** `localSecrets()`'s name, the `secrets` adapter when the config sets none. */
|
|
25573
25580
|
const LOCAL_SECRETS_NAME = "localSecrets";
|
|
25574
25581
|
const LOCAL_KEY_STORE = Symbol.for("@hot-updater/cli-tools/local-key-store");
|
|
25575
|
-
/** `localSecrets()`'s store without a `path`, the one after any adapter. */
|
|
25576
|
-
const defaultLocalSecretsStore = createLocalSecretsStore();
|
|
25577
25582
|
const isLocalKeyStoreSecrets = (secrets) => LOCAL_KEY_STORE in secrets;
|
|
25578
25583
|
/**
|
|
25579
25584
|
* Secrets in JSON files on this machine, the `secrets` adapter when the
|
|
@@ -25582,77 +25587,52 @@ const isLocalKeyStoreSecrets = (secrets) => LOCAL_KEY_STORE in secrets;
|
|
|
25582
25587
|
* `~/.hot-updater/secrets/<project-key>/<name>.json`; `set` writes that
|
|
25583
25588
|
* file, 0600 in a 0700 directory, outside the project, so it survives
|
|
25584
25589
|
* `node_modules` and is never committed. `HOT_UPDATER_HOME` moves
|
|
25585
|
-
* `~/.hot-updater`. The project key
|
|
25586
|
-
*
|
|
25587
|
-
*
|
|
25590
|
+
* `~/.hot-updater`. The project key hashes the checkout's real path
|
|
25591
|
+
* (`getProjectKey`), so each clone of an app has its own directory, and
|
|
25592
|
+
* another clone or machine signs in and issues a key of its own.
|
|
25588
25593
|
*
|
|
25589
25594
|
* @example
|
|
25590
25595
|
* export default defineConfig({ secrets: localSecrets(), ... });
|
|
25591
25596
|
*/
|
|
25592
|
-
const localSecrets = (options = {}) =>
|
|
25597
|
+
const localSecrets = (options = {}) => createLocalSecrets(createLocalSecretsStore(options));
|
|
25593
25598
|
/**
|
|
25594
|
-
* The
|
|
25595
|
-
*
|
|
25596
|
-
*
|
|
25597
|
-
* when the adapter stores nothing. Without an adapter, it is
|
|
25598
|
-
* `localSecrets()` itself. Keys Hot Updater issues, and credentials
|
|
25599
|
-
* `secrets put` imports, are reused by the next build on this machine with
|
|
25600
|
-
* no step in between.
|
|
25599
|
+
* The `secrets` adapter a config load uses: the config's own adapter as it
|
|
25600
|
+
* is, with no store on this machine under it, or `localSecrets()` when the
|
|
25601
|
+
* config sets none.
|
|
25601
25602
|
*/
|
|
25602
|
-
const
|
|
25603
|
-
const
|
|
25604
|
-
const describeFile = (name) => store.file(name, getCwd());
|
|
25603
|
+
const resolveSecretsAdapter = (configured) => configured ?? localSecrets();
|
|
25604
|
+
const createLocalSecrets = (store) => {
|
|
25605
25605
|
return {
|
|
25606
|
-
name:
|
|
25607
|
-
[LOCAL_KEY_STORE]:
|
|
25608
|
-
configured,
|
|
25609
|
-
store
|
|
25610
|
-
} : { store },
|
|
25606
|
+
name: LOCAL_SECRETS_NAME,
|
|
25607
|
+
[LOCAL_KEY_STORE]: { store },
|
|
25611
25608
|
get: async (names, context) => {
|
|
25612
|
-
const values =
|
|
25609
|
+
const values = await readSecretVariables(names, context.cwd);
|
|
25613
25610
|
const read = Object.create(null);
|
|
25614
|
-
for (const name of names)
|
|
25615
|
-
const value = Object.hasOwn(values, name) ? values[name] : void 0;
|
|
25616
|
-
read[name] = typeof value === "string" && value ? value : await store.read(name, context.cwd);
|
|
25617
|
-
}
|
|
25611
|
+
for (const name of names) read[name] = values[name] || await store.read(name, context.cwd);
|
|
25618
25612
|
return read;
|
|
25619
25613
|
},
|
|
25620
25614
|
set: async (name, value, context) => {
|
|
25621
|
-
if (configured?.set) {
|
|
25622
|
-
await configured.set(name, value, context);
|
|
25623
|
-
if (await store.read(name, context.cwd) === void 0) return;
|
|
25624
|
-
try {
|
|
25625
|
-
await store.write(name, value, context.cwd);
|
|
25626
|
-
} catch (error) {
|
|
25627
|
-
p.log.warn(`The ${configured.name} secrets adapter stored ${name}, and the copy on this machine at ${store.file(name, context.cwd)} still holds the value it replaced: ${error instanceof Error ? error.message : String(error)}. Delete that file.`);
|
|
25628
|
-
}
|
|
25629
|
-
return;
|
|
25630
|
-
}
|
|
25631
25615
|
await store.write(name, value, context.cwd);
|
|
25632
25616
|
},
|
|
25633
|
-
describe: (name) =>
|
|
25617
|
+
describe: (name) => `${secretVariableName(name)} in the environment, or ${store.file(name, getCwd())} on this machine`
|
|
25634
25618
|
};
|
|
25635
25619
|
};
|
|
25636
25620
|
/**
|
|
25637
|
-
*
|
|
25638
|
-
*
|
|
25639
|
-
* default one.
|
|
25621
|
+
* `localSecrets()`'s store, for `localSecrets()` itself; undefined for any
|
|
25622
|
+
* other adapter, which keeps nothing on this machine.
|
|
25640
25623
|
*/
|
|
25641
|
-
const getLocalSecretsStore = (secrets) => isLocalKeyStoreSecrets(secrets) ? secrets[LOCAL_KEY_STORE].store :
|
|
25624
|
+
const getLocalSecretsStore = (secrets) => isLocalKeyStoreSecrets(secrets) ? secrets[LOCAL_KEY_STORE].store : void 0;
|
|
25642
25625
|
/**
|
|
25643
|
-
* The config's own `secrets` adapter
|
|
25644
|
-
*
|
|
25645
|
-
*
|
|
25626
|
+
* The config's own `secrets` adapter: undefined for `localSecrets()`,
|
|
25627
|
+
* which is the adapter when the config sets none. `secretEnv` reads deploy
|
|
25628
|
+
* credentials from it.
|
|
25646
25629
|
*/
|
|
25647
|
-
const getConfiguredAdapter = (secrets) => isLocalKeyStoreSecrets(secrets) ?
|
|
25630
|
+
const getConfiguredAdapter = (secrets) => isLocalKeyStoreSecrets(secrets) ? void 0 : secrets;
|
|
25648
25631
|
/**
|
|
25649
|
-
* Where `secrets.set` stores `name`, for messages: the config's adapter
|
|
25650
|
-
*
|
|
25632
|
+
* Where `secrets.set` stores `name`, for messages: the config's adapter,
|
|
25633
|
+
* else `localSecrets()`'s file.
|
|
25651
25634
|
*/
|
|
25652
|
-
const describeStoredSecret = (secrets, name) =>
|
|
25653
|
-
const configured = getConfiguredAdapter(secrets);
|
|
25654
|
-
return configured?.set ? describeSecret(configured, name) : getLocalSecretsStore(secrets).file(name, getCwd());
|
|
25655
|
-
};
|
|
25635
|
+
const describeStoredSecret = (secrets, name) => getLocalSecretsStore(secrets)?.file(name, getCwd()) ?? describeSecret(secrets, name);
|
|
25656
25636
|
/**
|
|
25657
25637
|
* `name`'s value, read in the order `secrets.get` reads it, with the one
|
|
25658
25638
|
* place it came from, for messages: the config's adapter
|
|
@@ -25661,27 +25641,19 @@ const describeStoredSecret = (secrets, name) => {
|
|
|
25661
25641
|
*/
|
|
25662
25642
|
const readSecretWithSource = async (secrets, name, cwd) => {
|
|
25663
25643
|
const held = (value) => typeof value === "string" && value.length > 0;
|
|
25664
|
-
|
|
25644
|
+
const store = getLocalSecretsStore(secrets);
|
|
25645
|
+
if (!store) {
|
|
25665
25646
|
const value = (await secrets.get([name], { cwd }))[name];
|
|
25666
25647
|
return held(value) ? {
|
|
25667
25648
|
value,
|
|
25668
25649
|
source: describeSecret(secrets, name)
|
|
25669
25650
|
} : void 0;
|
|
25670
25651
|
}
|
|
25671
|
-
const
|
|
25672
|
-
if (
|
|
25673
|
-
|
|
25674
|
-
|
|
25675
|
-
|
|
25676
|
-
source: describeSecret(configured, name)
|
|
25677
|
-
};
|
|
25678
|
-
} else {
|
|
25679
|
-
const value = (await readSecretVariables([name], cwd))[name];
|
|
25680
|
-
if (held(value)) return {
|
|
25681
|
-
value,
|
|
25682
|
-
source: `${secretVariableName(name)} (environment)`
|
|
25683
|
-
};
|
|
25684
|
-
}
|
|
25652
|
+
const variable = (await readSecretVariables([name], cwd))[name];
|
|
25653
|
+
if (held(variable)) return {
|
|
25654
|
+
value: variable,
|
|
25655
|
+
source: `${secretVariableName(name)} (environment)`
|
|
25656
|
+
};
|
|
25685
25657
|
const value = await store.read(name, cwd);
|
|
25686
25658
|
return held(value) ? {
|
|
25687
25659
|
value,
|
|
@@ -25721,13 +25693,13 @@ const setConfiguredSecrets = (secrets) => {
|
|
|
25721
25693
|
globalThis[CONFIGURED_SECRETS] = secrets;
|
|
25722
25694
|
};
|
|
25723
25695
|
/**
|
|
25724
|
-
* The config's `secrets` adapter
|
|
25725
|
-
*
|
|
25696
|
+
* The config's `secrets` adapter (`resolveSecretsAdapter`), `localSecrets()`
|
|
25697
|
+
* until a config loads.
|
|
25726
25698
|
*/
|
|
25727
25699
|
const getConfiguredSecrets = () => globalThis[CONFIGURED_SECRETS] ?? localSecrets();
|
|
25728
25700
|
/** Where an adapter keeps `name`, for messages. */
|
|
25729
25701
|
const describeSecret = (secrets, name) => secrets.describe?.(name) ?? `${name} in ${secrets.name}`;
|
|
25730
|
-
const list = (names) => names.length <= 2 ? names.join(" and ") : `${names.slice(0, -1).join(", ")}, and ${names.at(-1)}`;
|
|
25702
|
+
const list$1 = (names) => names.length <= 2 ? names.join(" and ") : `${names.slice(0, -1).join(", ")}, and ${names.at(-1)}`;
|
|
25731
25703
|
/**
|
|
25732
25704
|
* Environment variables from the `secrets` adapter of the config that
|
|
25733
25705
|
* creates it, as a function a config calls when an adapter needs them, so loading
|
|
@@ -25756,35 +25728,44 @@ const secretEnv = (names) => {
|
|
|
25756
25728
|
const invalid = wanted.filter((name) => !ENV_NAME_PATTERN.test(name));
|
|
25757
25729
|
if (invalid.length > 0) throw new Error(`secretEnv takes environment variable names: letters, digits, and underscores, not starting with a digit, and ${invalid.map((name) => JSON.stringify(name)).join(", ")} ${invalid.length === 1 ? "is" : "are"} not.`);
|
|
25758
25730
|
const reserved = wanted.filter(isReservedVariableName);
|
|
25759
|
-
if (reserved.length > 0) throw new Error(`secretEnv refuses ${list(reserved)}: ${reserved.length === 1 ? "it changes" : "they change"} how a process loads code or which credentials Hot Updater uses.`);
|
|
25731
|
+
if (reserved.length > 0) throw new Error(`secretEnv refuses ${list$1(reserved)}: ${reserved.length === 1 ? "it changes" : "they change"} how a process loads code or which credentials Hot Updater uses.`);
|
|
25760
25732
|
const scope = globalThis[LOADING_SCOPE];
|
|
25761
|
-
|
|
25762
|
-
|
|
25763
|
-
const
|
|
25764
|
-
|
|
25765
|
-
|
|
25766
|
-
|
|
25767
|
-
|
|
25768
|
-
|
|
25769
|
-
|
|
25770
|
-
|
|
25771
|
-
|
|
25772
|
-
|
|
25773
|
-
|
|
25774
|
-
|
|
25775
|
-
|
|
25776
|
-
|
|
25777
|
-
|
|
25778
|
-
|
|
25779
|
-
|
|
25780
|
-
|
|
25781
|
-
|
|
25782
|
-
|
|
25783
|
-
|
|
25733
|
+
if (scope) {
|
|
25734
|
+
scope.names ??= /* @__PURE__ */ new Set();
|
|
25735
|
+
for (const name of wanted) scope.names.add(name);
|
|
25736
|
+
}
|
|
25737
|
+
let read;
|
|
25738
|
+
return () => read ??= readSecretEnv(wanted, scope).catch((error) => {
|
|
25739
|
+
read = void 0;
|
|
25740
|
+
throw error;
|
|
25741
|
+
});
|
|
25742
|
+
};
|
|
25743
|
+
const readSecretEnv = async (wanted, scope) => {
|
|
25744
|
+
const cwd = getCwd();
|
|
25745
|
+
const fileEnv = await readLoadedHotUpdaterEnv(cwd);
|
|
25746
|
+
const variables = Object.create(null);
|
|
25747
|
+
const fromStore = [];
|
|
25748
|
+
for (const name of wanted) {
|
|
25749
|
+
const value = getHotUpdaterEnvValue(fileEnv, name);
|
|
25750
|
+
if (value === void 0) fromStore.push(name);
|
|
25751
|
+
else variables[name] = value;
|
|
25752
|
+
}
|
|
25753
|
+
if (fromStore.length > 0) {
|
|
25754
|
+
const secrets = getConfiguredAdapter(scope?.secrets ?? getConfiguredSecrets());
|
|
25755
|
+
const read = secrets ? await secrets.get(fromStore, { cwd }) : {};
|
|
25756
|
+
const missing = fromStore.filter((name) => {
|
|
25757
|
+
const value = Object.hasOwn(read, name) ? read[name] : void 0;
|
|
25758
|
+
if (typeof value !== "string" || value === "") return true;
|
|
25759
|
+
variables[name] = value;
|
|
25760
|
+
return false;
|
|
25761
|
+
});
|
|
25762
|
+
if (missing.length > 0) {
|
|
25763
|
+
const them = missing.length === 1 ? "it" : "them";
|
|
25764
|
+
throw new Error(secrets === void 0 ? `secretEnv found no value for ${list$1(missing)}: set ${them} in the environment or .env.hotupdater, or read ${them} from a secrets store with the config's secrets.` : `secretEnv found no value for ${list$1(missing)}: ${missing.length === 1 ? "it is" : "they are"} not set in the environment or .env.hotupdater, and the ${secrets.name} secrets adapter does not hold ${them} (${missing.map((name) => describeSecret(secrets, name)).join("; ")}).`);
|
|
25784
25765
|
}
|
|
25785
|
-
|
|
25786
|
-
|
|
25787
|
-
|
|
25766
|
+
}
|
|
25767
|
+
for (const value of Object.values(variables)) registerSecretValue(value);
|
|
25768
|
+
return Object.freeze(variables);
|
|
25788
25769
|
};
|
|
25789
25770
|
//#endregion
|
|
25790
25771
|
//#region src/localBundleSigning.ts
|
|
@@ -38744,21 +38725,33 @@ const formatCommand = (command) => {
|
|
|
38744
38725
|
return lines.join("\n");
|
|
38745
38726
|
};
|
|
38746
38727
|
/**
|
|
38747
|
-
*
|
|
38748
|
-
* the
|
|
38728
|
+
* `lead` and the command it names on one line when they fit 77 columns,
|
|
38729
|
+
* else the command on the lines after it.
|
|
38749
38730
|
*/
|
|
38750
|
-
const
|
|
38751
|
-
|
|
38752
|
-
|
|
38753
|
-
|
|
38754
|
-
|
|
38755
|
-
|
|
38756
|
-
|
|
38731
|
+
const withLead = (lead, command) => lead.length + 1 + command.length <= 77 ? `${lead} ${command}` : `${lead}\n${formatCommand(command)}`;
|
|
38732
|
+
/**
|
|
38733
|
+
* Why a build has no upload keystore, in three lines, steering away from a
|
|
38734
|
+
* second key: Play rejects builds signed with a key it does not have.
|
|
38735
|
+
* Under a team's adapter the keystore is someone's already, so it is
|
|
38736
|
+
* imported, never created; only `localSecrets()` alone offers a new one.
|
|
38737
|
+
*/
|
|
38738
|
+
const describeMissingKeystore = (store, item, name) => {
|
|
38739
|
+
const configured = getConfiguredAdapter(store);
|
|
38740
|
+
if (configured) return [
|
|
38741
|
+
`No ${item} in ${describeSecret(configured, name)}.`,
|
|
38742
|
+
"Ask whoever set it up to store it there, or import it with:",
|
|
38743
|
+
"npx hot-updater google-play login"
|
|
38744
|
+
].join("\n");
|
|
38745
|
+
return [
|
|
38746
|
+
`No ${item} in the ${store.name} secrets adapter.`,
|
|
38747
|
+
"Play rejects a new key if the app has one: import it with google-play login.",
|
|
38748
|
+
withLead("Otherwise:", credentialStoreCommand("android-keystore", name))
|
|
38749
|
+
].join("\n");
|
|
38757
38750
|
};
|
|
38758
38751
|
/**
|
|
38759
|
-
* The command that
|
|
38760
|
-
*
|
|
38761
|
-
*
|
|
38752
|
+
* The command that stores the key Hot Updater issues as `name`: an App
|
|
38753
|
+
* Store Connect API key after an Apple Account sign-in, a Play service
|
|
38754
|
+
* account after a Google one, and an upload keystore it creates.
|
|
38762
38755
|
*/
|
|
38763
38756
|
const credentialIssueCommand = (kind, name = kind) => `npx hot-updater secrets issue ${kind}${name === kind ? "" : ` ${name}`}`;
|
|
38764
38757
|
/** The command group of each issued kind's shortcut, `<group> login`. */
|
|
@@ -38775,9 +38768,10 @@ const CREDENTIAL_LOGIN_GROUPS = {
|
|
|
38775
38768
|
const credentialLoginCommand = (kind, name = kind) => name === kind ? `npx hot-updater ${CREDENTIAL_LOGIN_GROUPS[kind]} login` : credentialIssueCommand(kind, name);
|
|
38776
38769
|
/**
|
|
38777
38770
|
* The command that stores a missing credential of `kind` as `name`: the
|
|
38778
|
-
* keystore
|
|
38771
|
+
* upload keystore Hot Updater creates, or the key it issues after a
|
|
38772
|
+
* sign-in.
|
|
38779
38773
|
*/
|
|
38780
|
-
const credentialStoreCommand = (kind, name = kind) => kind === "android-keystore" ?
|
|
38774
|
+
const credentialStoreCommand = (kind, name = kind) => kind === "android-keystore" ? credentialIssueCommand(kind, name) : credentialLoginCommand(kind, name);
|
|
38781
38775
|
var CredentialUnavailableError = class extends Error {
|
|
38782
38776
|
kind;
|
|
38783
38777
|
/** The environment variables that can provide this credential. */
|
|
@@ -39026,7 +39020,7 @@ const createCredentialResolver = ({ env = {}, secrets, cwd } = {}) => {
|
|
|
39026
39020
|
const reason = error instanceof Error ? error.message : String(error);
|
|
39027
39021
|
throw new CredentialUnavailableError(kind, `Couldn't read ${item} from the ${store.name} secrets adapter.\n${reason}`, { cause: error });
|
|
39028
39022
|
}
|
|
39029
|
-
if (value === void 0) throw new CredentialUnavailableError(kind, `No ${item} in the ${store.name} secrets adapter.\nStore it once with:\n${formatCommand(credentialStoreCommand(kind, name))}`);
|
|
39023
|
+
if (value === void 0) throw new CredentialUnavailableError(kind, kind === "android-keystore" ? describeMissingKeystore(store, item, name) : `No ${item} in the ${store.name} secrets adapter.\nStore it once with:\n${formatCommand(credentialStoreCommand(kind, name))}`);
|
|
39030
39024
|
registerSecretValue(value);
|
|
39031
39025
|
return fromSecretValue[kind](value, {
|
|
39032
39026
|
files,
|
|
@@ -45532,8 +45526,24 @@ const MANAGED_IMPORT_PACKAGES = /* @__PURE__ */ new Set([
|
|
|
45532
45526
|
const MANAGED_HELPER_NAMES = /* @__PURE__ */ new Set([
|
|
45533
45527
|
"awsOptions",
|
|
45534
45528
|
"commonOptions",
|
|
45535
|
-
"credential"
|
|
45529
|
+
"credential",
|
|
45530
|
+
"databaseSecrets",
|
|
45531
|
+
"deploySecrets",
|
|
45532
|
+
"storageSecrets"
|
|
45536
45533
|
]);
|
|
45534
|
+
/**
|
|
45535
|
+
* `const <name> = secretEnv([...names])`, a helper a managed config reads
|
|
45536
|
+
* credentials with: from the environment and `.env.hotupdater` first, then
|
|
45537
|
+
* from the config's `secrets` adapter. `storageSecrets` and
|
|
45538
|
+
* `databaseSecrets` read each adapter's own, so a command that reaches only
|
|
45539
|
+
* one needs only its credentials; `deploySecrets` reads what both share.
|
|
45540
|
+
* Its builder imports `secretEnv` from `hot-updater`.
|
|
45541
|
+
*/
|
|
45542
|
+
const secretEnvHelper = (name, names) => ({
|
|
45543
|
+
name,
|
|
45544
|
+
strategy: "replace",
|
|
45545
|
+
code: `const ${name} = secretEnv([\n${names.map((variable) => ` ${JSON.stringify(variable)},`).join("\n")}\n]);`
|
|
45546
|
+
});
|
|
45537
45547
|
const KNOWN_BUILD_CALLEES = /* @__PURE__ */ new Set([
|
|
45538
45548
|
"bare",
|
|
45539
45549
|
"expo",
|
|
@@ -45605,6 +45615,18 @@ const getObjectPropertyName = (property) => {
|
|
|
45605
45615
|
return null;
|
|
45606
45616
|
};
|
|
45607
45617
|
const isDataProperty = (property) => property.type === "Property" && property.kind === "init" && !property.method && !property.shorthand;
|
|
45618
|
+
/** `process.env.HOT_UPDATER_*`, with or without `!`. */
|
|
45619
|
+
const isHotUpdaterEnvRead = (expression) => {
|
|
45620
|
+
const read = expression.type === "TSNonNullExpression" ? expression.expression : expression;
|
|
45621
|
+
return read.type === "MemberExpression" && !read.computed && read.property.type === "Identifier" && read.property.name.startsWith("HOT_UPDATER_") && read.object.type === "MemberExpression" && !read.object.computed && read.object.object.type === "Identifier" && read.object.object.name === "process" && read.object.property.type === "Identifier" && read.object.property.name === "env";
|
|
45622
|
+
};
|
|
45623
|
+
/**
|
|
45624
|
+
* A value init writes into a managed adapter's options: a string, a
|
|
45625
|
+
* `process.env.HOT_UPDATER_*` read, as configs of 1.1 read every value, or
|
|
45626
|
+
* an object of only those, such as `credentials`. Init replaces it with
|
|
45627
|
+
* what its run resolves; any other value is the project's own and kept.
|
|
45628
|
+
*/
|
|
45629
|
+
const isInitWrittenValue = (expression) => expression.type === "Literal" && typeof expression.value === "string" || isHotUpdaterEnvRead(expression) || expression.type === "ObjectExpression" && expression.properties.length > 0 && expression.properties.every((property) => isDataProperty(property) && isInitWrittenValue(property.value));
|
|
45608
45630
|
const dedentBlock = (text) => {
|
|
45609
45631
|
const lines = text.replace(/\s+$/, "").split("\n");
|
|
45610
45632
|
const indents = lines.filter((line) => line.trim() !== "").map((line) => line.match(/^\s*/)?.[0].length ?? 0);
|
|
@@ -45677,9 +45699,17 @@ const mergeObjectLiteralText = (existingObject, newObject, replaceIncompatiblePr
|
|
|
45677
45699
|
existingPropertyNames.add(propertyName);
|
|
45678
45700
|
const nextProperty = newObject.objectExpression.properties.find((candidate) => getObjectPropertyName(candidate) === propertyName);
|
|
45679
45701
|
if (!nextProperty || !isDataProperty(property) || !isDataProperty(nextProperty)) continue;
|
|
45702
|
+
if (isInitWrittenValue(property.value)) {
|
|
45703
|
+
edits.push({
|
|
45704
|
+
start: property.value.start - existingObject.objectExpression.start,
|
|
45705
|
+
end: property.value.end - existingObject.objectExpression.start,
|
|
45706
|
+
text: getNodeText(newObject.source, nextProperty.value)
|
|
45707
|
+
});
|
|
45708
|
+
continue;
|
|
45709
|
+
}
|
|
45680
45710
|
const existingCallee = getCallCallee(property.value);
|
|
45681
45711
|
const nextCallee = getCallCallee(nextProperty.value);
|
|
45682
|
-
const hasIncompatibleValue = existingCallee !== null && nextCallee !== null && existingCallee !== nextCallee || property.value.type
|
|
45712
|
+
const hasIncompatibleValue = existingCallee !== null && nextCallee !== null && existingCallee !== nextCallee || property.value.type !== nextProperty.value.type;
|
|
45683
45713
|
if (replaceIncompatibleProperties.includes(propertyName) && hasIncompatibleValue) {
|
|
45684
45714
|
edits.push({
|
|
45685
45715
|
start: property.value.start - existingObject.objectExpression.start,
|
|
@@ -47084,15 +47114,26 @@ const loadPlatformConfigs = async (platforms, { channel }) => {
|
|
|
47084
47114
|
*/
|
|
47085
47115
|
const resolveConfig = (config, scopes) => {
|
|
47086
47116
|
const mergedConfig = mergeConfigSources(config, getDefaultConfig());
|
|
47087
|
-
const secrets =
|
|
47117
|
+
const secrets = resolveSecretsAdapter(mergedConfig.secrets);
|
|
47088
47118
|
const signing = normalizeSigningConfig(mergedConfig.signing, { secrets });
|
|
47089
47119
|
for (const scope of scopes) scope.secrets ??= secrets;
|
|
47090
47120
|
setConfiguredSecrets(secrets);
|
|
47091
|
-
|
|
47121
|
+
const resolved = {
|
|
47092
47122
|
...mergedConfig,
|
|
47093
47123
|
signing,
|
|
47094
47124
|
secrets
|
|
47095
47125
|
};
|
|
47126
|
+
Object.defineProperty(resolved, SECRET_ENV_NAMES, { value: [...new Set(scopes.flatMap((scope) => [...scope.names ?? []]))] });
|
|
47127
|
+
return resolved;
|
|
47128
|
+
};
|
|
47129
|
+
const SECRET_ENV_NAMES = Symbol.for("@hot-updater/cli-tools/secret-env-names");
|
|
47130
|
+
/**
|
|
47131
|
+
* The names every `secretEnv` the config created while it loaded reads,
|
|
47132
|
+
* such as `HOT_UPDATER_CLOUDFLARE_API_TOKEN`, in the order it created them.
|
|
47133
|
+
*/
|
|
47134
|
+
const getSecretEnvNames = (config) => {
|
|
47135
|
+
const names = Reflect.get(config, SECRET_ENV_NAMES);
|
|
47136
|
+
return Array.isArray(names) ? names : [];
|
|
47096
47137
|
};
|
|
47097
47138
|
//#endregion
|
|
47098
47139
|
//#region src/serverDefinition.ts
|
|
@@ -47384,6 +47425,74 @@ const resolveHotUpdaterServerVersion = (currentPackageName, options) => {
|
|
|
47384
47425
|
return process.env["HOT_UPDATER_SERVER_PACKAGE_VERSION"]?.trim() || resolvePackageVersion(currentPackageName, options);
|
|
47385
47426
|
};
|
|
47386
47427
|
//#endregion
|
|
47428
|
+
//#region src/teamSecrets.ts
|
|
47429
|
+
/**
|
|
47430
|
+
* The client API key the managed servers' `apiKeys()` gives an app, which
|
|
47431
|
+
* init saves (`API_KEY_ENV` in `@hot-updater/plugin-api-keys`). The config
|
|
47432
|
+
* never reads it; a team store shares it with the deploy credentials, so a
|
|
47433
|
+
* teammate's init registers the same key again rather than a new one.
|
|
47434
|
+
*/
|
|
47435
|
+
const MANAGED_CLIENT_CREDENTIAL_ENV = "HOT_UPDATER_API_KEY";
|
|
47436
|
+
/**
|
|
47437
|
+
* The config's own `secrets` adapter, the team store: undefined when the
|
|
47438
|
+
* config sets none, which is `localSecrets()`. Loading the config runs its
|
|
47439
|
+
* `process.loadEnvFile(".env.hotupdater")`, which init must not see, so
|
|
47440
|
+
* `process.env` is restored after it.
|
|
47441
|
+
*/
|
|
47442
|
+
const loadTeamSecrets = async () => {
|
|
47443
|
+
const before = { ...process.env };
|
|
47444
|
+
try {
|
|
47445
|
+
return getConfiguredAdapter((await loadConfig(null)).secrets);
|
|
47446
|
+
} finally {
|
|
47447
|
+
for (const key of Object.keys(process.env)) if (!Object.hasOwn(before, key)) delete process.env[key];
|
|
47448
|
+
Object.assign(process.env, before);
|
|
47449
|
+
}
|
|
47450
|
+
};
|
|
47451
|
+
/**
|
|
47452
|
+
* `names` neither the environment nor `env`, init's env files, sets, read
|
|
47453
|
+
* from `secrets` in one call: the values a teammate's init, or a run after
|
|
47454
|
+
* the file's lines were deleted, replays with.
|
|
47455
|
+
*/
|
|
47456
|
+
const readMissingFromTeamSecrets = async (secrets, names, { env, cwd }) => {
|
|
47457
|
+
const missing = names.filter((name) => getHotUpdaterEnvValue(env, name) === void 0);
|
|
47458
|
+
if (missing.length === 0) return {};
|
|
47459
|
+
const held = await secrets.get(missing, { cwd });
|
|
47460
|
+
const values = Object.create(null);
|
|
47461
|
+
for (const name of missing) {
|
|
47462
|
+
const value = Object.hasOwn(held, name) ? held[name] : void 0;
|
|
47463
|
+
if (typeof value === "string" && value !== "") {
|
|
47464
|
+
registerSecretValue(value);
|
|
47465
|
+
values[name] = value;
|
|
47466
|
+
}
|
|
47467
|
+
}
|
|
47468
|
+
return values;
|
|
47469
|
+
};
|
|
47470
|
+
/**
|
|
47471
|
+
* Stores each of `names` that `.env.hotupdater` holds through `secrets`,
|
|
47472
|
+
* then reads them back in one call. The file is left as it is. Returns the
|
|
47473
|
+
* names stored, in the order of `names`; throws when the adapter reads
|
|
47474
|
+
* back another value.
|
|
47475
|
+
*/
|
|
47476
|
+
const storeEnvFileSecrets = async (secrets, names, cwd) => {
|
|
47477
|
+
const file = await readLoadedHotUpdaterEnv(cwd);
|
|
47478
|
+
const values = /* @__PURE__ */ new Map();
|
|
47479
|
+
for (const name of names) {
|
|
47480
|
+
const value = Object.hasOwn(file, name) ? file[name]?.trim() : void 0;
|
|
47481
|
+
if (value) values.set(name, value);
|
|
47482
|
+
}
|
|
47483
|
+
if (values.size === 0) return [];
|
|
47484
|
+
const stored = [...values.keys()];
|
|
47485
|
+
for (const [name, value] of values) {
|
|
47486
|
+
registerSecretValue(value);
|
|
47487
|
+
await secrets.set(name, value, { cwd });
|
|
47488
|
+
}
|
|
47489
|
+
const read = await secrets.get(stored, { cwd });
|
|
47490
|
+
const unread = stored.filter((name) => (Object.hasOwn(read, name) ? read[name] : void 0) !== values.get(name));
|
|
47491
|
+
if (unread.length > 0) throw new Error(`The ${secrets.name} secrets adapter did not read back ${list(unread)} (${unread.map((name) => describeSecret(secrets, name)).join("; ")}).`);
|
|
47492
|
+
return stored;
|
|
47493
|
+
};
|
|
47494
|
+
const list = (names) => names.length <= 2 ? names.join(" and ") : `${names.slice(0, -1).join(", ")}, and ${names.at(-1)}`;
|
|
47495
|
+
//#endregion
|
|
47387
47496
|
//#region src/storageFiles.ts
|
|
47388
47497
|
const getStorageFileByteSize = async (filePath) => {
|
|
47389
47498
|
const { size } = await fs$2.stat(filePath, { bigint: true });
|
|
@@ -47434,4 +47543,4 @@ function transformTemplate(templateString, values) {
|
|
|
47434
47543
|
return result;
|
|
47435
47544
|
}
|
|
47436
47545
|
//#endregion
|
|
47437
|
-
export { BuildFailedError, BuildLogger, CREDENTIAL_ENV_NAMES, CREDENTIAL_LOGIN_GROUPS, ConfigBuilder, CredentialUnavailableError, DEFAULT_SIGNING_KEY_SECRET, HOT_UPDATER_HOME_ENV, HOT_UPDATER_SERVER_PACKAGE_VERSION_ENV, HotUpdateDirUtil, INTERFACE_FINGERPRINT_ENGINE, InitEnvFileError, InitError, LOCAL_SECRETS_NAME, LegacyInfrastructureError, MissingInitInputsError, ServerDefinitionError, assembleServer, assertInfrastructureGenerationAtUrl, assertInfrastructureGenerationPayload, assertInitInputs, assertInitProviderInputs, assertUnreservedEnv, autolinkingConfigCommand, banner, clientAuthOf, typedColors as colors, confirmInitInputPersistence, copyDirToTmp, createCredentialResolver, createHotUpdaterConfigScaffold, createHotUpdaterConfigScaffoldFromBuilder, createInterfaceFingerprint, createLocalSecretsStore, createLogWriter, createMigrator, createMinBundleId, createTarBrTargetFiles, createZip, credentialIssueCommand, credentialLoginCommand,
|
|
47546
|
+
export { BuildFailedError, BuildLogger, CREDENTIAL_ENV_NAMES, CREDENTIAL_LOGIN_GROUPS, ConfigBuilder, CredentialUnavailableError, DEFAULT_SIGNING_KEY_SECRET, HOT_UPDATER_HOME_ENV, HOT_UPDATER_SERVER_PACKAGE_VERSION_ENV, HotUpdateDirUtil, INTERFACE_FINGERPRINT_ENGINE, InitEnvFileError, InitError, LOCAL_SECRETS_NAME, LegacyInfrastructureError, MANAGED_CLIENT_CREDENTIAL_ENV, MissingInitInputsError, ServerDefinitionError, assembleServer, assertInfrastructureGenerationAtUrl, assertInfrastructureGenerationPayload, assertInitInputs, assertInitProviderInputs, assertUnreservedEnv, autolinkingConfigCommand, banner, clientAuthOf, typedColors as colors, confirmInitInputPersistence, copyDirToTmp, createCredentialResolver, createHotUpdaterConfigScaffold, createHotUpdaterConfigScaffoldFromBuilder, createInterfaceFingerprint, createLocalSecretsStore, createLogWriter, createMigrator, createMinBundleId, createTarBrTargetFiles, createZip, credentialIssueCommand, credentialLoginCommand, credentialStoreCommand, decryptJson, defineInitProvider, describeSecret, describeStoredSecret, encryptJson, ensureInstallPackages, generateClientCredential, generateSchema, generatesSchema, getAndroidSdkPath, getBundleSigningPublicKey, getConfiguredAdapter, getConfiguredSecrets, getCwd, getHotUpdaterEnvValue, getHotUpdaterHome, getHotUpdaterInitInputEnv, getInitProviderEnvVars, getInitProviderTextPromptValues, getLocalKeyFile, getLocalKeyStoreDir, getLocalSecretsStore, getMissingInitInputs, getMissingInitProviderInputs, getPackageManager, getProjectKey, getReactNativeMetadatas, getSecretEnvNames, getStorageFileByteSize, isCI, isCodegenSpecFile, isReservedVariableName, isSecretName, isServerDefinition, keepsKeysOnThisMachine, link, loadConfig, loadPlatformConfigs, loadTeamSecrets, localSecrets, log, makeEnv, maskSecretValues, moduleSpecifiersOf, nonInteractiveReason, p, parseLoadedEnv, parseSecretReference, prepareBundleSigning, printAppSetup, printBanner, provisionClientCredential, putStorageFile, reactNativeCliFingerprint, reactNativeConfigCommand, readBundleSigningPublicKeyFile, readHotUpdaterInitEnv, readLoadedHotUpdaterEnv, readLocalKey, readMissingFromTeamSecrets, readPackageUp, readReactNativeInterface, readSecretVariables, readSecretWithSource, registerCredentialValue, registerSecretValue, renderAppImports, renderAppSetup, renderImportStatements, resolveExpoAutolinking, resolveHotUpdaterServerVersion, resolveInitProviderInput, resolveInitProviderInputs, resolveLocalSecretsPath, resolvePackageVersion, resolveSecretsAdapter, secretEnv, secretEnvHelper, secretVariableName, serverDefinitionOf, setConfiguredSecrets, shouldAutoSelectOnlyInitResource, storeEnvFileSecrets, stripAnsi, transformEnv, transformTemplate, withCredentialResolver, withSecretsScope, writeHotUpdaterConfig, writeHotUpdaterFiles, writeLocalKey };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hot-updater/cli-tools",
|
|
3
|
-
"version": "1.2.0-rc.
|
|
3
|
+
"version": "1.2.0-rc.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": "^22.23.4 || ^24.22.1 || >=26.12.1"
|
|
@@ -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/
|
|
53
|
-
"@hot-updater/protocol": "1.2.0-rc.
|
|
54
|
-
"@hot-updater/
|
|
52
|
+
"@hot-updater/plugin-core": "1.2.0-rc.5",
|
|
53
|
+
"@hot-updater/protocol": "1.2.0-rc.5",
|
|
54
|
+
"@hot-updater/server": "1.2.0-rc.5"
|
|
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.2.0-rc.
|
|
68
|
+
"@hot-updater/test-utils": "1.2.0-rc.5"
|
|
69
69
|
},
|
|
70
70
|
"inlinedDependencies": {
|
|
71
71
|
"@babel/code-frame": "7.29.0",
|