@hot-updater/cli-tools 1.2.0-rc.12 → 1.2.0-rc.13
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 +106 -18
- package/dist/index.mjs +549 -149
- package/package.json +5 -5
package/dist/index.d.mts
CHANGED
|
@@ -509,12 +509,6 @@ interface CredentialResolver {
|
|
|
509
509
|
*/
|
|
510
510
|
dispose(): Promise<void>;
|
|
511
511
|
}
|
|
512
|
-
/**
|
|
513
|
-
* A command to print, broken at spaces with ` \` once a line would pass
|
|
514
|
-
* 74 columns, each continuation indented two spaces, so a terminal never
|
|
515
|
-
* wraps it mid-word and it still pastes as one command.
|
|
516
|
-
*/
|
|
517
|
-
declare const formatCommand: (command: string) => string;
|
|
518
512
|
/** The credentials Hot Updater issues itself after a sign-in. */
|
|
519
513
|
type IssuedCredentialKind = "asc-api-key";
|
|
520
514
|
/** The credentials `secrets put <name> <file>` stores from one file. */
|
|
@@ -920,8 +914,11 @@ declare const nonInteractiveReason: ({ env, input }?: {
|
|
|
920
914
|
//#region src/interfaceFingerprint.d.ts
|
|
921
915
|
/**
|
|
922
916
|
* The engine fingerprint.json records for the interface fingerprint. Change
|
|
923
|
-
* it with any change to what is hashed or how
|
|
924
|
-
* as an engine change rather than a
|
|
917
|
+
* it with any change to what is hashed or how that moves every project's
|
|
918
|
+
* hash, so the new hashes are reported as an engine change rather than a
|
|
919
|
+
* native one. The name is part of every digest, so changing it moves them all:
|
|
920
|
+
* tracking an input nothing tracked before keeps the name, moves only the
|
|
921
|
+
* projects that have that input, and is reported as a change to its files.
|
|
925
922
|
*/
|
|
926
923
|
declare const INTERFACE_FINGERPRINT_ENGINE = "hot-updater/interface@1";
|
|
927
924
|
/**
|
|
@@ -1093,6 +1090,13 @@ declare const loadPlatformConfigs: <TPlatform extends Platform>(platforms: reado
|
|
|
1093
1090
|
readonly platform: TPlatform;
|
|
1094
1091
|
readonly config: ConfigResponse;
|
|
1095
1092
|
}[]>;
|
|
1093
|
+
/**
|
|
1094
|
+
* The names the config's build credentials reference, such as
|
|
1095
|
+
* `release-keystore`, which builds read from the `secrets` adapter for their
|
|
1096
|
+
* kind: `signing`'s, and each scheme's `signing.ascApiKey` and
|
|
1097
|
+
* `signing.keystore`.
|
|
1098
|
+
*/
|
|
1099
|
+
declare const getBuildSecretNames: (config: ConfigResponse) => readonly string[];
|
|
1096
1100
|
/**
|
|
1097
1101
|
* The names the config's `secret()`s reference for its adapters, such as
|
|
1098
1102
|
* `HOT_UPDATER_CLOUDFLARE_API_TOKEN`, in the order the config created
|
|
@@ -1196,6 +1200,32 @@ declare const createMigrator: ({ database, plugins }: ToolingSource) => Migrator
|
|
|
1196
1200
|
/** The schema file `hot-updater db generate` writes for a definition's database. */
|
|
1197
1201
|
declare const generateSchema: ({ database, plugins }: ToolingSource, version: Parameters<SchemaGenerator>[0], name?: Parameters<SchemaGenerator>[1]) => ReturnType<SchemaGenerator>;
|
|
1198
1202
|
//#endregion
|
|
1203
|
+
//#region src/expoSdk.d.ts
|
|
1204
|
+
/**
|
|
1205
|
+
* The lowest Expo SDK Hot Updater supports, the floor of `@hot-updater/expo`'s
|
|
1206
|
+
* `expo` peer range. doctor, the config plugin and the build adapter read it.
|
|
1207
|
+
*/
|
|
1208
|
+
declare const MINIMUM_EXPO_SDK = 55;
|
|
1209
|
+
/** The command that moves a project to the minimum Expo SDK. */
|
|
1210
|
+
declare const EXPO_SDK_UPGRADE_COMMAND = "npx expo install expo@^55.0.0 --fix";
|
|
1211
|
+
interface ExpoSdk {
|
|
1212
|
+
/** The SDK's major version. */
|
|
1213
|
+
readonly sdk: number;
|
|
1214
|
+
/** The installed version, or the range's lowest when Expo isn't installed. */
|
|
1215
|
+
readonly version: string;
|
|
1216
|
+
readonly installed: boolean;
|
|
1217
|
+
}
|
|
1218
|
+
/**
|
|
1219
|
+
* A project's Expo SDK: the installed `expo` the project resolves, else the
|
|
1220
|
+
* lowest version its declared range allows. Null without either.
|
|
1221
|
+
*/
|
|
1222
|
+
declare const readExpoSdk: (cwd: string) => ExpoSdk | null;
|
|
1223
|
+
/**
|
|
1224
|
+
* Refuses a project whose installed Expo is below the minimum SDK. A project
|
|
1225
|
+
* without Expo installed passes: nothing could build it yet.
|
|
1226
|
+
*/
|
|
1227
|
+
declare const assertSupportedExpoSdk: (cwd: string) => void;
|
|
1228
|
+
//#endregion
|
|
1199
1229
|
//#region src/log.d.ts
|
|
1200
1230
|
type LogMessage = string | number | null | undefined;
|
|
1201
1231
|
declare const log: {
|
|
@@ -1212,6 +1242,11 @@ type EnvVarValue = string | {
|
|
|
1212
1242
|
comment: string;
|
|
1213
1243
|
value: string;
|
|
1214
1244
|
};
|
|
1245
|
+
/**
|
|
1246
|
+
* Replaces `filePath` whole or not at all, through a new 0600 file beside it,
|
|
1247
|
+
* so the result is 0600 whatever the mode of the file it replaces.
|
|
1248
|
+
*/
|
|
1249
|
+
declare const writeEnvAtomically: (filePath: string, content: string) => Promise<void>;
|
|
1215
1250
|
declare const makeEnv: (newEnvVars: Record<string, EnvVarValue>, filePath?: string, options?: {
|
|
1216
1251
|
readonly preserveKeys?: readonly string[];
|
|
1217
1252
|
readonly removeKeys?: readonly string[];
|
|
@@ -1219,10 +1254,29 @@ declare const makeEnv: (newEnvVars: Record<string, EnvVarValue>, filePath?: stri
|
|
|
1219
1254
|
readonly expectedContent?: string;
|
|
1220
1255
|
}) => Promise<string>;
|
|
1221
1256
|
//#endregion
|
|
1257
|
+
//#region src/mergeEnvFile.d.ts
|
|
1258
|
+
/**
|
|
1259
|
+
* `value` as an env file's value, in the first form `parseLoadedEnv` reads
|
|
1260
|
+
* back exactly: bare, single-quoted on one line, double-quoted with its
|
|
1261
|
+
* newlines as `\n`, in backticks, or single-quoted across lines. Undefined
|
|
1262
|
+
* for a value no form holds, such as one with every quote mark and a newline.
|
|
1263
|
+
*/
|
|
1264
|
+
declare const formatEnvValue: (value: string) => string | undefined;
|
|
1265
|
+
/**
|
|
1266
|
+
* `content`, an env file, with `values` set, already as `formatEnvValue`
|
|
1267
|
+
* gives them: a variable it assigns has only that value replaced, the last
|
|
1268
|
+
* assignment when it repeats, which is the one that counts, and a new one
|
|
1269
|
+
* goes at the end. Every other byte stays: other lines, comments, an
|
|
1270
|
+
* `export`, an inline comment. Undefined when `parseLoadedEnv` wouldn't read
|
|
1271
|
+
* exactly that back, so a file this can't follow is never rewritten.
|
|
1272
|
+
*/
|
|
1273
|
+
declare const mergeEnvContent: (content: string, values: ReadonlyMap<string, string>) => string | undefined;
|
|
1274
|
+
//#endregion
|
|
1222
1275
|
//#region src/messageLines.d.ts
|
|
1223
1276
|
/**
|
|
1224
|
-
*
|
|
1225
|
-
* 3-column gutter
|
|
1277
|
+
* Where the CLI wraps a message's prose, so it reads well after clack's
|
|
1278
|
+
* 3-column gutter. It is a default for readability: a command, a URL, or a
|
|
1279
|
+
* path stays whole, however long.
|
|
1226
1280
|
*/
|
|
1227
1281
|
declare const MESSAGE_WIDTH = 73;
|
|
1228
1282
|
/**
|
|
@@ -1236,12 +1290,33 @@ declare const displayWidth: (text: string) => number;
|
|
|
1236
1290
|
*/
|
|
1237
1291
|
declare const truncateLine: (text: string, width: number) => string;
|
|
1238
1292
|
/**
|
|
1239
|
-
*
|
|
1240
|
-
*
|
|
1241
|
-
|
|
1242
|
-
|
|
1293
|
+
* Whether a line is a command to copy, by the program it runs, rather than
|
|
1294
|
+
* prose: the next word is an argument, so "aws CLI not found" is prose.
|
|
1295
|
+
*/
|
|
1296
|
+
declare const isCommandLine: (line: string) => boolean;
|
|
1297
|
+
/**
|
|
1298
|
+
* What the CLI prints for people, readable: prose wraps at spaces around
|
|
1299
|
+
* `width` display columns, and a command to copy, a table's row, and a word
|
|
1300
|
+
* longer than a line, such as a URL or a path, stay whole on their lines.
|
|
1301
|
+
* A line that fits stays as it is, so fitting twice changes nothing. `p`
|
|
1302
|
+
* applies it to all it prints; text printed another way, such as to stderr
|
|
1303
|
+
* under `--json`, calls it.
|
|
1304
|
+
*/
|
|
1305
|
+
declare const fitText: (text: string, width?: number) => string;
|
|
1306
|
+
/**
|
|
1307
|
+
* A message of `lines`, each fitted as `fitText` does, so a name, a path, or
|
|
1308
|
+
* a list it embeds doesn't run on.
|
|
1243
1309
|
*/
|
|
1244
1310
|
declare const fitMessage: (lines: readonly string[]) => string;
|
|
1311
|
+
/**
|
|
1312
|
+
* `text` with each JSON body in it, such as the response an SDK's error
|
|
1313
|
+
* embeds, replaced with the message the body says, as people read an error:
|
|
1314
|
+
* Cloudflare's `errors[].message`, or a `message` or an `error`. A body that
|
|
1315
|
+
* says none is cut. Printed errors go through it; `--json` keeps the body.
|
|
1316
|
+
* A registered value is masked before anything is cut, as a cut one no longer
|
|
1317
|
+
* matches, and again after, as a body's string may have escaped it.
|
|
1318
|
+
*/
|
|
1319
|
+
declare const withProviderMessages: (message: string) => string;
|
|
1245
1320
|
//#endregion
|
|
1246
1321
|
//#region src/minBundleId.d.ts
|
|
1247
1322
|
/**
|
|
@@ -2468,10 +2543,20 @@ declare const taskLog: (opts: TaskLogOptions) => {
|
|
|
2468
2543
|
};
|
|
2469
2544
|
//#endregion
|
|
2470
2545
|
//#region src/prompts.d.ts
|
|
2546
|
+
/**
|
|
2547
|
+
* What a tool wrote, such as a build's output: masked, but printed as the
|
|
2548
|
+
* tool wrote it, while everything the CLI writes goes through `p`.
|
|
2549
|
+
*/
|
|
2550
|
+
declare const toolOutput: {
|
|
2551
|
+
error: (message: string, options?: LogMessageOptions | undefined) => void;
|
|
2552
|
+
note: (message: string, title?: string, options?: Parameters<typeof note>[2]) => void;
|
|
2553
|
+
};
|
|
2471
2554
|
/**
|
|
2472
2555
|
* `@clack/prompts` with CI-safe spinners, with every message a registered
|
|
2473
|
-
* credential value appears in masked (`registerSecretValue`)
|
|
2474
|
-
*
|
|
2556
|
+
* credential value appears in masked (`registerSecretValue`) and its prose
|
|
2557
|
+
* wrapped (`fitText`), with a provider's JSON body in an error or a warning
|
|
2558
|
+
* replaced with its message, and with prompts that refuse to ask without a
|
|
2559
|
+
* terminal.
|
|
2475
2560
|
*/
|
|
2476
2561
|
declare const p: typeof index_d_exports;
|
|
2477
2562
|
type PromptProgress = ReturnType<typeof p.progress>;
|
|
@@ -2518,7 +2603,10 @@ declare const resolveHotUpdaterServerVersion: (currentPackageName: string, optio
|
|
|
2518
2603
|
* masked.
|
|
2519
2604
|
*/
|
|
2520
2605
|
declare const registerSecretValue: (value: string) => void;
|
|
2521
|
-
/**
|
|
2606
|
+
/**
|
|
2607
|
+
* Replaces every registered credential value in `text`, and any JSON Web Token,
|
|
2608
|
+
* with a mask.
|
|
2609
|
+
*/
|
|
2522
2610
|
declare const maskSecretValues: (text: string) => string;
|
|
2523
2611
|
//#endregion
|
|
2524
2612
|
//#region src/localKeyStore.d.ts
|
|
@@ -2857,4 +2945,4 @@ type TransformTemplateArgs<T extends string> = { [Key in ExtractPlaceholders<T>]
|
|
|
2857
2945
|
*/
|
|
2858
2946
|
declare function transformTemplate<T extends string>(templateString: T, values: TransformTemplateArgs<T>): string;
|
|
2859
2947
|
//#endregion
|
|
2860
|
-
export { AddSigningSecretResult, AppClientCredential, AppClientPlugin, AssembleServerOptions, AssembledServer, BUNDLE_SIGNING_KEY_SECRET, BuildFailedError, BuildFailureSummaryConfig, BuildLogger, BuildLoggerConfig, BuildPhaseReader, BuildType, BundleSigningKeyError, BundleSigningOptions, BundleSigningSession, BundleSigningState, CREDENTIAL_ENV_NAMES, CREDENTIAL_LOGIN_GROUPS, ClientAuthSpec, ClientCredentialSpec, ConfigBuilder, ConfigBuilderScaffold, ConfigResponse, CreateCredentialResolverOptions, CreateHotUpdaterConfigScaffoldFromBuilderOptions, CreateHotUpdaterConfigScaffoldOptions, type CredentialFiles, CredentialKind, CredentialRequest, CredentialResolver, CredentialUnavailableError, Credentials, FileCredentialKind, HOT_UPDATER_HOME_ENV, HOT_UPDATER_SERVER_PACKAGE_VERSION_ENV, HotUpdateDirUtil, HotUpdaterConfigOptions, HotUpdaterConfigScaffold, HotUpdaterInitEnv, HotUpdaterLogWriter, INTERFACE_FINGERPRINT_ENGINE, INVALID_SIGNING_MESSAGE, INVALID_SIGNING_NEXT_STEP, ISSUE_SIGNING_KEY_COMMAND, ImportInfo, InitEnvFileError, InitError, InitProviderDefinition, InitProviderInputDefinition, InitProviderInputPersistence, IssuedCredentialKind, LOCAL_SECRETS_NAME, LegacyInfrastructureError, LocalSecretsOptions, LocalSecretsStore, MANAGED_CLIENT_CREDENTIAL_ENV, MESSAGE_WIDTH, ManagedHelperStatement, ManagedHelperStrategy, MissingInitInputsError, NativeDependency, NativeInterface, PUT_SIGNING_KEY_COMMAND, PromptProgress, PromptSpinner, ProviderConfig, ProvisionedClientCredential, ReactNativeMetadata, ReadPackageUpResult, RunInitOptions, SecretsScope, ServerClientEndpoint, ServerDefinition, ServerDefinitionError, Step, StepLevel, UNSIGNED_BUNDLES_NEXT_STEP, UNSIGNED_BUNDLES_WARNING, WriteHotUpdaterConfigResult, addSigningSecretToConfig, addSigningSecretToConfigText, assembleServer, assertInfrastructureGenerationAtUrl, assertInfrastructureGenerationPayload, assertInitInputs, assertInitProviderInputs, assertUnreservedEnv, autolinkingConfigCommand, banner, bundleSigningPutCommand, bundleSigningSecretRef, clientAuthOf, typedColors as colors, configSetsTeamSecrets, confirmInitInputPersistence, copyDirToTmp, createCredentialResolver, createHotUpdaterConfigScaffold, createHotUpdaterConfigScaffoldFromBuilder, createInterfaceFingerprint, createLocalSecretsStore, createLogWriter, createMigrator, createMinBundleId, createStep, createTarBrTargetFiles, createZip, credentialIssueCommand, credentialLoginCommand, credentialPutCommand, credentialStoreCommand, decryptJson, defineInitProvider, describeMissingSecret, describeSecret, describeStoredSecret, displayWidth, encryptJson, ensureInstallPackages, failActiveStep, fitMessage,
|
|
2948
|
+
export { AddSigningSecretResult, AppClientCredential, AppClientPlugin, AssembleServerOptions, AssembledServer, BUNDLE_SIGNING_KEY_SECRET, BuildFailedError, BuildFailureSummaryConfig, BuildLogger, BuildLoggerConfig, BuildPhaseReader, BuildType, BundleSigningKeyError, BundleSigningOptions, BundleSigningSession, BundleSigningState, CREDENTIAL_ENV_NAMES, CREDENTIAL_LOGIN_GROUPS, ClientAuthSpec, ClientCredentialSpec, ConfigBuilder, ConfigBuilderScaffold, ConfigResponse, CreateCredentialResolverOptions, CreateHotUpdaterConfigScaffoldFromBuilderOptions, CreateHotUpdaterConfigScaffoldOptions, type CredentialFiles, CredentialKind, CredentialRequest, CredentialResolver, CredentialUnavailableError, Credentials, EXPO_SDK_UPGRADE_COMMAND, ExpoSdk, FileCredentialKind, HOT_UPDATER_HOME_ENV, HOT_UPDATER_SERVER_PACKAGE_VERSION_ENV, HotUpdateDirUtil, HotUpdaterConfigOptions, HotUpdaterConfigScaffold, HotUpdaterInitEnv, HotUpdaterLogWriter, INTERFACE_FINGERPRINT_ENGINE, INVALID_SIGNING_MESSAGE, INVALID_SIGNING_NEXT_STEP, ISSUE_SIGNING_KEY_COMMAND, ImportInfo, InitEnvFileError, InitError, InitProviderDefinition, InitProviderInputDefinition, InitProviderInputPersistence, IssuedCredentialKind, LOCAL_SECRETS_NAME, LegacyInfrastructureError, LocalSecretsOptions, LocalSecretsStore, MANAGED_CLIENT_CREDENTIAL_ENV, MESSAGE_WIDTH, MINIMUM_EXPO_SDK, ManagedHelperStatement, ManagedHelperStrategy, MissingInitInputsError, NativeDependency, NativeInterface, PUT_SIGNING_KEY_COMMAND, PromptProgress, PromptSpinner, ProviderConfig, ProvisionedClientCredential, ReactNativeMetadata, ReadPackageUpResult, RunInitOptions, SecretsScope, ServerClientEndpoint, ServerDefinition, ServerDefinitionError, Step, StepLevel, UNSIGNED_BUNDLES_NEXT_STEP, UNSIGNED_BUNDLES_WARNING, WriteHotUpdaterConfigResult, addSigningSecretToConfig, addSigningSecretToConfigText, assembleServer, assertInfrastructureGenerationAtUrl, assertInfrastructureGenerationPayload, assertInitInputs, assertInitProviderInputs, assertSupportedExpoSdk, assertUnreservedEnv, autolinkingConfigCommand, banner, bundleSigningPutCommand, bundleSigningSecretRef, clientAuthOf, typedColors as colors, configSetsTeamSecrets, confirmInitInputPersistence, copyDirToTmp, createCredentialResolver, createHotUpdaterConfigScaffold, createHotUpdaterConfigScaffoldFromBuilder, createInterfaceFingerprint, createLocalSecretsStore, createLogWriter, createMigrator, createMinBundleId, createStep, createTarBrTargetFiles, createZip, credentialIssueCommand, credentialLoginCommand, credentialPutCommand, credentialStoreCommand, decryptJson, defineInitProvider, describeMissingSecret, describeSecret, describeStoredSecret, displayWidth, encryptJson, ensureInstallPackages, failActiveStep, fitMessage, fitText, formatDuration, formatEnvValue, generateClientCredential, generateSchema, generatesSchema, getAndroidSdkPath, getBuildSecretNames, getBundleSigningPublicKey, getConfiguredAdapter, getConfiguredSecrets, getCwd, getDeploySecretNames, getHotUpdaterEnvValue, getHotUpdaterHome, getHotUpdaterInitInputEnv, getInitProviderEnvVars, getInitProviderTextPromptValues, getLocalKeyFile, getLocalKeyStoreDir, getLocalSecretsStore, getMissingInitInputs, getMissingInitProviderInputs, getPackageManager, getProjectKey, getReactNativeMetadatas, getStorageFileByteSize, isCI, isCodegenSpecFile, isCommandLine, isReservedVariableName, isSecretName, isSecretRef, isServerDefinition, keepsKeysOnThisMachine, link, loadConfig, loadPlatformConfigs, loadTeamSecrets, localSecrets, log, makeEnv, maskSecretValues, mergeEnvContent, moduleSpecifiersOf, nonInteractiveReason, p, parseLoadedEnv, parseSecretReference, prepareBundleSigning, printAppSetup, printBanner, provisionClientCredential, putStorageFile, reactNativeCliFingerprint, reactNativeConfigCommand, readBundleSigningPublicKeyFile, readBundleSigningState, readExpoSdk, readHotUpdaterInitEnv, readLoadedEnvFile, readLoadedHotUpdaterEnv, readLocalKey, readMissingFromTeamSecrets, readPackageUp, readReactNativeInterface, readSecretVariables, readSecretWithSource, readSecrets, registerCredentialValue, registerSecretValue, renderAppImports, renderAppSetup, renderImportStatements, resolveExpoAutolinking, resolveHotUpdaterServerVersion, resolveInitProviderInput, resolveInitProviderInputs, resolveLocalSecretsPath, resolvePackageVersion, resolveSecretsAdapter, secret, secretVariableName, serverDefinitionOf, setConfiguredSecrets, shouldAutoSelectOnlyInitResource, startStep, storeEnvFileSecrets, stripAnsi, toolOutput, transformEnv, transformTemplate, truncateLine, withCredentialResolver, withDuration, withProviderMessages, withSecretsScope, withStep, writeEnvAtomically, writeHotUpdaterConfig, writeHotUpdaterFiles, writeLocalKey };
|
package/dist/index.mjs
CHANGED
|
@@ -22584,14 +22584,221 @@ const registerSecretValue = (value) => {
|
|
|
22584
22584
|
registry.secretValuesLongestFirst = longestFirst(secretValues);
|
|
22585
22585
|
registry.shortSecretPattern = shortSecretValues.size > 0 ? new RegExp(`${SHORT_SECRET_BEFORE}(?:${longestFirst(shortSecretValues).map(escapeRegExp).join("|")})${SHORT_SECRET_AFTER}`, "g") : void 0;
|
|
22586
22586
|
};
|
|
22587
|
-
/**
|
|
22587
|
+
/**
|
|
22588
|
+
* A JSON Web Token, whether or not it was registered: Xcode mints its own from
|
|
22589
|
+
* the App Store Connect API key and may print it, and a build's log is uploaded
|
|
22590
|
+
* when the build fails.
|
|
22591
|
+
*/
|
|
22592
|
+
const JSON_WEB_TOKEN = /\beyJ[\w-]{8,}\.[\w-]{8,}\.[\w-]{8,}/g;
|
|
22593
|
+
/**
|
|
22594
|
+
* Replaces every registered credential value in `text`, and any JSON Web Token,
|
|
22595
|
+
* with a mask.
|
|
22596
|
+
*/
|
|
22588
22597
|
const maskSecretValues = (text) => {
|
|
22589
22598
|
let masked = text;
|
|
22590
22599
|
for (const value of registry.secretValuesLongestFirst) if (masked.includes(value)) masked = masked.split(value).join(SECRET_MASK);
|
|
22600
|
+
masked = masked.replace(JSON_WEB_TOKEN, SECRET_MASK);
|
|
22591
22601
|
const { shortSecretPattern } = registry;
|
|
22592
22602
|
return shortSecretPattern ? masked.replace(shortSecretPattern, SECRET_MASK) : masked;
|
|
22593
22603
|
};
|
|
22594
22604
|
//#endregion
|
|
22605
|
+
//#region src/messageLines.ts
|
|
22606
|
+
/**
|
|
22607
|
+
* Where the CLI wraps a message's prose, so it reads well after clack's
|
|
22608
|
+
* 3-column gutter. It is a default for readability: a command, a URL, or a
|
|
22609
|
+
* path stays whole, however long.
|
|
22610
|
+
*/
|
|
22611
|
+
const MESSAGE_WIDTH = 73;
|
|
22612
|
+
const graphemes = new Intl.Segmenter();
|
|
22613
|
+
/** Combining marks, format and control characters: they take no column. */
|
|
22614
|
+
const ZERO_WIDTH = /^[\p{M}\p{Cf}\p{Cc}]+$/u;
|
|
22615
|
+
/** East Asian wide and fullwidth characters, and emoji: two columns each. */
|
|
22616
|
+
const WIDE = /[\u1100-\u115F\u2E80-\u303E\u3041-\u33FF\u3400-\u4DBF\u4E00-\u9FFF\uA000-\uA4CF\uAC00-\uD7A3\uF900-\uFAFF\uFE30-\uFE4F\uFF00-\uFF60\uFFE0-\uFFE6\u{20000}-\u{3FFFD}]|\p{Emoji_Presentation}/u;
|
|
22617
|
+
/** The columns one grapheme takes. */
|
|
22618
|
+
const segmentWidth = (segment) => ZERO_WIDTH.test(segment) ? 0 : WIDE.test(segment) ? 2 : 1;
|
|
22619
|
+
/**
|
|
22620
|
+
* The columns `text` takes in a terminal, without its colors: `│`, `■`,
|
|
22621
|
+
* and `…` take one each, though each is several bytes.
|
|
22622
|
+
*/
|
|
22623
|
+
const displayWidth = (text) => {
|
|
22624
|
+
let width = 0;
|
|
22625
|
+
for (const { segment } of graphemes.segment(stripVTControlCharacters(text))) width += segmentWidth(segment);
|
|
22626
|
+
return width;
|
|
22627
|
+
};
|
|
22628
|
+
/**
|
|
22629
|
+
* `text` cut to `width` display columns, with `…` for the rest, never
|
|
22630
|
+
* inside a character: a cut line loses its colors.
|
|
22631
|
+
*/
|
|
22632
|
+
const truncateLine = (text, width) => {
|
|
22633
|
+
const plain = stripVTControlCharacters(text);
|
|
22634
|
+
if (displayWidth(plain) <= width) return text;
|
|
22635
|
+
let cut = "";
|
|
22636
|
+
let used = 0;
|
|
22637
|
+
for (const { segment } of graphemes.segment(plain)) {
|
|
22638
|
+
used += segmentWidth(segment);
|
|
22639
|
+
if (used > width - 1) break;
|
|
22640
|
+
cut += segment;
|
|
22641
|
+
}
|
|
22642
|
+
return `${cut.trimEnd()}…`;
|
|
22643
|
+
};
|
|
22644
|
+
const COLOR_CODE = /\u001B\[(\d+)(?:;\d+)*m/gu;
|
|
22645
|
+
/** The code that ends a color or style, by the code that starts it. */
|
|
22646
|
+
const closingCode = (code) => {
|
|
22647
|
+
if (code >= 30 && code <= 38 || code >= 90 && code <= 97) return 39;
|
|
22648
|
+
if (code >= 40 && code <= 48 || code >= 100 && code <= 107) return 49;
|
|
22649
|
+
return {
|
|
22650
|
+
1: 22,
|
|
22651
|
+
2: 22,
|
|
22652
|
+
3: 23,
|
|
22653
|
+
4: 24,
|
|
22654
|
+
7: 27,
|
|
22655
|
+
8: 28,
|
|
22656
|
+
9: 29
|
|
22657
|
+
}[code];
|
|
22658
|
+
};
|
|
22659
|
+
/**
|
|
22660
|
+
* `lines` with each color that runs past a line's end closed there and
|
|
22661
|
+
* reopened on the next, as clack's gutter, colored between them, ends it.
|
|
22662
|
+
*/
|
|
22663
|
+
const carryColors = (lines) => {
|
|
22664
|
+
const open = /* @__PURE__ */ new Map();
|
|
22665
|
+
return lines.map((line) => {
|
|
22666
|
+
const reopened = [...open.values()].join("");
|
|
22667
|
+
for (const match of line.matchAll(COLOR_CODE)) {
|
|
22668
|
+
const code = Number(match[1]);
|
|
22669
|
+
const closing = closingCode(code);
|
|
22670
|
+
if (code === 0) open.clear();
|
|
22671
|
+
else if (closing === void 0) open.delete(code);
|
|
22672
|
+
else open.set(closing, match[0]);
|
|
22673
|
+
}
|
|
22674
|
+
return `${reopened}${line}${[...open.keys()].reverse().map((code) => `\u001B[${code}m`).join("")}`;
|
|
22675
|
+
});
|
|
22676
|
+
};
|
|
22677
|
+
/** A line's words, with a color code on its own joined to the next word. */
|
|
22678
|
+
const wordsOf = (line) => {
|
|
22679
|
+
const words = [];
|
|
22680
|
+
let colors = "";
|
|
22681
|
+
for (const word of line.match(/\S+/gu) ?? []) if (displayWidth(word) === 0) colors += word;
|
|
22682
|
+
else {
|
|
22683
|
+
words.push(`${colors}${word}`);
|
|
22684
|
+
colors = "";
|
|
22685
|
+
}
|
|
22686
|
+
if (colors && words.length > 0) words[words.length - 1] += colors;
|
|
22687
|
+
return words;
|
|
22688
|
+
};
|
|
22689
|
+
/**
|
|
22690
|
+
* Whether a line is a command to copy, by the program it runs, rather than
|
|
22691
|
+
* prose: the next word is an argument, so "aws CLI not found" is prose.
|
|
22692
|
+
*/
|
|
22693
|
+
const isCommandLine = (line) => /^\s*(?:\$ )?(?:npx|pnpx|bunx|pnpm|npm|yarn|bun|hot-updater|aws|az|gcloud|gh|git)\s+[a-z0-9@./<-]/u.test(stripVTControlCharacters(line));
|
|
22694
|
+
/** A row of a bordered table, which a line break would garble. */
|
|
22695
|
+
const isTableRow = (line) => /^\s*[┌├└│]/u.test(stripVTControlCharacters(line));
|
|
22696
|
+
/** Prose's lines, broken at spaces, each with the line's indentation. */
|
|
22697
|
+
const wrapProse = (line, width) => {
|
|
22698
|
+
const indent = /^\s*/u.exec(stripVTControlCharacters(line))?.[0] ?? "";
|
|
22699
|
+
const lines = [];
|
|
22700
|
+
let current = "";
|
|
22701
|
+
for (const word of wordsOf(line)) if (current && displayWidth(`${indent}${current} ${word}`) > width) {
|
|
22702
|
+
lines.push(`${indent}${current}`);
|
|
22703
|
+
current = word;
|
|
22704
|
+
} else current = current ? `${current} ${word}` : word;
|
|
22705
|
+
return [...lines, `${indent}${current}`];
|
|
22706
|
+
};
|
|
22707
|
+
/**
|
|
22708
|
+
* What the CLI prints for people, readable: prose wraps at spaces around
|
|
22709
|
+
* `width` display columns, and a command to copy, a table's row, and a word
|
|
22710
|
+
* longer than a line, such as a URL or a path, stay whole on their lines.
|
|
22711
|
+
* A line that fits stays as it is, so fitting twice changes nothing. `p`
|
|
22712
|
+
* applies it to all it prints; text printed another way, such as to stderr
|
|
22713
|
+
* under `--json`, calls it.
|
|
22714
|
+
*/
|
|
22715
|
+
const fitText = (text, width = 73) => {
|
|
22716
|
+
const lines = text.split("\n").flatMap((line) => displayWidth(line) <= width || isCommandLine(line) || isTableRow(line) ? [line] : wrapProse(line, width));
|
|
22717
|
+
return (text.includes("\x1B[") ? carryColors(lines) : lines).join("\n");
|
|
22718
|
+
};
|
|
22719
|
+
/**
|
|
22720
|
+
* A message of `lines`, each fitted as `fitText` does, so a name, a path, or
|
|
22721
|
+
* a list it embeds doesn't run on.
|
|
22722
|
+
*/
|
|
22723
|
+
const fitMessage = (lines) => fitText(lines.join("\n"));
|
|
22724
|
+
/** The most a provider's message takes: two lines of a message. */
|
|
22725
|
+
const PROVIDER_MESSAGE_WIDTH = 146;
|
|
22726
|
+
/** What a response with no message it says shows of itself. */
|
|
22727
|
+
const UNREAD_BODY_WIDTH = 40;
|
|
22728
|
+
/** Where an error body says what went wrong, most specific first. */
|
|
22729
|
+
const MESSAGE_KEYS = [
|
|
22730
|
+
"errors",
|
|
22731
|
+
"error_description",
|
|
22732
|
+
"message",
|
|
22733
|
+
"error",
|
|
22734
|
+
"detail",
|
|
22735
|
+
"title",
|
|
22736
|
+
"msg"
|
|
22737
|
+
];
|
|
22738
|
+
const messagesIn = (body) => {
|
|
22739
|
+
if (typeof body === "string") return body.trim() ? [body.trim()] : [];
|
|
22740
|
+
if (Array.isArray(body)) return body.flatMap(messagesIn);
|
|
22741
|
+
if (!body || typeof body !== "object") return [];
|
|
22742
|
+
for (const key of MESSAGE_KEYS) {
|
|
22743
|
+
const messages = messagesIn(body[key]);
|
|
22744
|
+
if (messages.length > 0) return messages;
|
|
22745
|
+
}
|
|
22746
|
+
return [];
|
|
22747
|
+
};
|
|
22748
|
+
/** Where the JSON value that opens at `start` closes, or -1. */
|
|
22749
|
+
const closingIndex = (text, start) => {
|
|
22750
|
+
let depth = 0;
|
|
22751
|
+
let inString = false;
|
|
22752
|
+
for (let index = start; index < text.length; index += 1) {
|
|
22753
|
+
const char = text[index];
|
|
22754
|
+
if (inString) {
|
|
22755
|
+
if (char === "\\") index += 1;
|
|
22756
|
+
else if (char === "\"") inString = false;
|
|
22757
|
+
} else if (char === "\"") inString = true;
|
|
22758
|
+
else if (char === "{" || char === "[") depth += 1;
|
|
22759
|
+
else if (char === "}" || char === "]") {
|
|
22760
|
+
depth -= 1;
|
|
22761
|
+
if (depth === 0) return index;
|
|
22762
|
+
}
|
|
22763
|
+
}
|
|
22764
|
+
return -1;
|
|
22765
|
+
};
|
|
22766
|
+
const parseBody = (text) => {
|
|
22767
|
+
try {
|
|
22768
|
+
const value = JSON.parse(text);
|
|
22769
|
+
return value && typeof value === "object" && (!Array.isArray(value) || value.every((entry) => entry && typeof entry === "object")) ? value : void 0;
|
|
22770
|
+
} catch {
|
|
22771
|
+
return;
|
|
22772
|
+
}
|
|
22773
|
+
};
|
|
22774
|
+
/**
|
|
22775
|
+
* `text` with each JSON body in it, such as the response an SDK's error
|
|
22776
|
+
* embeds, replaced with the message the body says, as people read an error:
|
|
22777
|
+
* Cloudflare's `errors[].message`, or a `message` or an `error`. A body that
|
|
22778
|
+
* says none is cut. Printed errors go through it; `--json` keeps the body.
|
|
22779
|
+
* A registered value is masked before anything is cut, as a cut one no longer
|
|
22780
|
+
* matches, and again after, as a body's string may have escaped it.
|
|
22781
|
+
*/
|
|
22782
|
+
const withProviderMessages = (message) => {
|
|
22783
|
+
const text = maskSecretValues(message);
|
|
22784
|
+
let result = "";
|
|
22785
|
+
let index = 0;
|
|
22786
|
+
while (index < text.length) {
|
|
22787
|
+
const char = text[index];
|
|
22788
|
+
const end = char === "{" || char === "[" ? closingIndex(text, index) : -1;
|
|
22789
|
+
const body = end === -1 ? void 0 : parseBody(text.slice(index, end + 1));
|
|
22790
|
+
if (body === void 0) {
|
|
22791
|
+
result += char;
|
|
22792
|
+
index += 1;
|
|
22793
|
+
continue;
|
|
22794
|
+
}
|
|
22795
|
+
const messages = [...new Set(messagesIn(body))];
|
|
22796
|
+
result += messages.length ? truncateLine(messages.join("; "), PROVIDER_MESSAGE_WIDTH) : truncateLine(text.slice(index, end + 1), UNREAD_BODY_WIDTH);
|
|
22797
|
+
index = end + 1;
|
|
22798
|
+
}
|
|
22799
|
+
return maskSecretValues(result);
|
|
22800
|
+
};
|
|
22801
|
+
//#endregion
|
|
22595
22802
|
//#region src/stepCapture.ts
|
|
22596
22803
|
let current;
|
|
22597
22804
|
const stepCapture = () => current;
|
|
@@ -22600,7 +22807,15 @@ const setStepCapture = (capture) => {
|
|
|
22600
22807
|
};
|
|
22601
22808
|
//#endregion
|
|
22602
22809
|
//#region src/prompts.ts
|
|
22603
|
-
|
|
22810
|
+
/**
|
|
22811
|
+
* A message as people read it: masked first, so a line break never splits
|
|
22812
|
+
* a value, then its prose wrapped.
|
|
22813
|
+
*/
|
|
22814
|
+
const printable = (message) => fitText(maskSecretValues(message));
|
|
22815
|
+
/** An error or a warning, which says what a provider's JSON body says. */
|
|
22816
|
+
const printableFailure = (message) => printable(withProviderMessages(message));
|
|
22817
|
+
const printableMessage = (message) => message === void 0 ? message : printable(message);
|
|
22818
|
+
const printableFailureMessage = (message) => message === void 0 ? message : printableFailure(message);
|
|
22604
22819
|
/**
|
|
22605
22820
|
* Prints a log line, or, while a step runs, holds it until the step's line
|
|
22606
22821
|
* prints. An info line says what the step is doing, so it shows as the
|
|
@@ -22623,13 +22838,21 @@ const logThroughStep = (write, { inStep = "hold" } = {}) => (...args) => {
|
|
|
22623
22838
|
capture.hold(() => write(...args));
|
|
22624
22839
|
};
|
|
22625
22840
|
const log$1 = {
|
|
22626
|
-
message: logThroughStep((message, options) => log$2.message(Array.isArray(message) ? message.map(
|
|
22627
|
-
info: logThroughStep((message, options) => log$2.info(
|
|
22628
|
-
success: logThroughStep((message, options) => log$2.success(
|
|
22629
|
-
step: logThroughStep((message, options) => log$2.step(
|
|
22630
|
-
warn: logThroughStep((message, options) => log$2.warn(
|
|
22631
|
-
warning: logThroughStep((message, options) => log$2.warning(
|
|
22632
|
-
error: logThroughStep((message, options) => log$2.error(
|
|
22841
|
+
message: logThroughStep((message, options) => log$2.message(Array.isArray(message) ? message.map((line) => printable(line)) : printableMessage(message), options)),
|
|
22842
|
+
info: logThroughStep((message, options) => log$2.info(printable(message), options), { inStep: "detail" }),
|
|
22843
|
+
success: logThroughStep((message, options) => log$2.success(printable(message), options)),
|
|
22844
|
+
step: logThroughStep((message, options) => log$2.step(printable(message), options)),
|
|
22845
|
+
warn: logThroughStep((message, options) => log$2.warn(printableFailure(message), options)),
|
|
22846
|
+
warning: logThroughStep((message, options) => log$2.warning(printableFailure(message), options)),
|
|
22847
|
+
error: logThroughStep((message, options) => log$2.error(printableFailure(message), options), { inStep: "interrupt" })
|
|
22848
|
+
};
|
|
22849
|
+
/**
|
|
22850
|
+
* What a tool wrote, such as a build's output: masked, but printed as the
|
|
22851
|
+
* tool wrote it, while everything the CLI writes goes through `p`.
|
|
22852
|
+
*/
|
|
22853
|
+
const toolOutput = {
|
|
22854
|
+
error: logThroughStep((message, options) => log$2.error(maskSecretValues(message), options), { inStep: "interrupt" }),
|
|
22855
|
+
note: (message, title, options) => note(maskSecretValues(message), title === void 0 ? title : maskSecretValues(title), options)
|
|
22633
22856
|
};
|
|
22634
22857
|
/** A spinner inside a running step, whose messages are the step's detail. */
|
|
22635
22858
|
const spinnerInStep = (capture) => ({
|
|
@@ -22643,20 +22866,20 @@ const spinnerInStep = (capture) => ({
|
|
|
22643
22866
|
return false;
|
|
22644
22867
|
}
|
|
22645
22868
|
});
|
|
22646
|
-
const
|
|
22647
|
-
start: (message) => target.start(
|
|
22648
|
-
stop: (message) => target.stop(
|
|
22649
|
-
cancel: (message) => target.cancel(
|
|
22650
|
-
error: (message) => target.error(
|
|
22651
|
-
message: (message) => target.message(
|
|
22869
|
+
const printableSpinner = (target) => ({
|
|
22870
|
+
start: (message) => target.start(printableMessage(message)),
|
|
22871
|
+
stop: (message) => target.stop(printableMessage(message)),
|
|
22872
|
+
cancel: (message) => target.cancel(printableFailureMessage(message)),
|
|
22873
|
+
error: (message) => target.error(printableFailureMessage(message)),
|
|
22874
|
+
message: (message) => target.message(printableMessage(message)),
|
|
22652
22875
|
clear: () => target.clear(),
|
|
22653
22876
|
get isCancelled() {
|
|
22654
22877
|
return target.isCancelled;
|
|
22655
22878
|
}
|
|
22656
22879
|
});
|
|
22657
|
-
const
|
|
22658
|
-
...
|
|
22659
|
-
advance: (step, message) => target.advance(step,
|
|
22880
|
+
const printableProgress = (target) => ({
|
|
22881
|
+
...printableSpinner(target),
|
|
22882
|
+
advance: (step, message) => target.advance(step, printableMessage(message)),
|
|
22660
22883
|
get isCancelled() {
|
|
22661
22884
|
return target.isCancelled;
|
|
22662
22885
|
}
|
|
@@ -22705,7 +22928,7 @@ const spinner = (options = {}) => {
|
|
|
22705
22928
|
if (capture) return spinnerInStep(capture);
|
|
22706
22929
|
const output = options.output ?? process.stdout;
|
|
22707
22930
|
const isInteractive = isTTY(output) && !isCI$1();
|
|
22708
|
-
return
|
|
22931
|
+
return printableSpinner(isInteractive ? spinner$1(options) : createStaticSpinner(options));
|
|
22709
22932
|
};
|
|
22710
22933
|
const progress = (options = {}) => {
|
|
22711
22934
|
const capture = stepCapture();
|
|
@@ -22719,8 +22942,8 @@ const progress = (options = {}) => {
|
|
|
22719
22942
|
}
|
|
22720
22943
|
};
|
|
22721
22944
|
const output = options.output ?? process.stdout;
|
|
22722
|
-
if (isTTY(output) && !isCI$1()) return
|
|
22723
|
-
const staticSpinner =
|
|
22945
|
+
if (isTTY(output) && !isCI$1()) return printableProgress(progress$1(options));
|
|
22946
|
+
const staticSpinner = printableSpinner(createStaticSpinner(options));
|
|
22724
22947
|
return {
|
|
22725
22948
|
...staticSpinner,
|
|
22726
22949
|
advance: (_step, message) => staticSpinner.message(message),
|
|
@@ -22756,13 +22979,23 @@ const requireTerminal = (prompt) => {
|
|
|
22756
22979
|
const message = options.message;
|
|
22757
22980
|
return Promise.reject(/* @__PURE__ */ new Error(`${typeof message === "string" ? `${message} ` : ""}The CLI asks nothing ${reason}: pass the command's options instead (see --help).`));
|
|
22758
22981
|
}
|
|
22759
|
-
return prompt(options);
|
|
22982
|
+
return prompt(fitPromptMessage(options));
|
|
22760
22983
|
};
|
|
22761
22984
|
};
|
|
22985
|
+
/** Prompt `options` whose message, when it has one, is fitted. */
|
|
22986
|
+
const fitPromptMessage = (options) => {
|
|
22987
|
+
const { message } = options;
|
|
22988
|
+
return typeof message === "string" ? {
|
|
22989
|
+
...options,
|
|
22990
|
+
message: printable(message)
|
|
22991
|
+
} : options;
|
|
22992
|
+
};
|
|
22762
22993
|
/**
|
|
22763
22994
|
* `@clack/prompts` with CI-safe spinners, with every message a registered
|
|
22764
|
-
* credential value appears in masked (`registerSecretValue`)
|
|
22765
|
-
*
|
|
22995
|
+
* credential value appears in masked (`registerSecretValue`) and its prose
|
|
22996
|
+
* wrapped (`fitText`), with a provider's JSON body in an error or a warning
|
|
22997
|
+
* replaced with its message, and with prompts that refuse to ask without a
|
|
22998
|
+
* terminal.
|
|
22766
22999
|
*/
|
|
22767
23000
|
const p = {
|
|
22768
23001
|
...dist_exports,
|
|
@@ -22779,10 +23012,10 @@ const p = {
|
|
|
22779
23012
|
selectKey: requireTerminal(selectKey),
|
|
22780
23013
|
text: requireTerminal(text),
|
|
22781
23014
|
log: log$1,
|
|
22782
|
-
cancel: (message, options) => cancel(
|
|
22783
|
-
intro: (title, options) => intro(
|
|
22784
|
-
note: (message, title, options) => note(
|
|
22785
|
-
outro: (message, options) => outro(
|
|
23015
|
+
cancel: (message, options) => cancel(printableFailureMessage(message), options),
|
|
23016
|
+
intro: (title, options) => intro(printableMessage(title), options),
|
|
23017
|
+
note: (message, title, options) => note(message === void 0 ? message : maskSecretValues(message), title === void 0 ? title : maskSecretValues(title), options),
|
|
23018
|
+
outro: (message, options) => outro(printableMessage(message), options),
|
|
22786
23019
|
progress,
|
|
22787
23020
|
spinner,
|
|
22788
23021
|
tasks
|
|
@@ -22886,61 +23119,6 @@ const createLogWriter = async ({ prefix, logFilePath }) => {
|
|
|
22886
23119
|
}
|
|
22887
23120
|
};
|
|
22888
23121
|
//#endregion
|
|
22889
|
-
//#region src/messageLines.ts
|
|
22890
|
-
/**
|
|
22891
|
-
* The widest line of a message the CLI prints: clack puts it after its
|
|
22892
|
-
* 3-column gutter (`│ `), and the whole line stays within 76 columns.
|
|
22893
|
-
*/
|
|
22894
|
-
const MESSAGE_WIDTH = 73;
|
|
22895
|
-
const graphemes = new Intl.Segmenter();
|
|
22896
|
-
/** Combining marks, format and control characters: they take no column. */
|
|
22897
|
-
const ZERO_WIDTH = /^[\p{M}\p{Cf}\p{Cc}]+$/u;
|
|
22898
|
-
/** East Asian wide and fullwidth characters, and emoji: two columns each. */
|
|
22899
|
-
const WIDE = /[\u1100-\u115F\u2E80-\u303E\u3041-\u33FF\u3400-\u4DBF\u4E00-\u9FFF\uA000-\uA4CF\uAC00-\uD7A3\uF900-\uFAFF\uFE30-\uFE4F\uFF00-\uFF60\uFFE0-\uFFE6\u{20000}-\u{3FFFD}]|\p{Emoji_Presentation}/u;
|
|
22900
|
-
/** The columns one grapheme takes. */
|
|
22901
|
-
const segmentWidth = (segment) => ZERO_WIDTH.test(segment) ? 0 : WIDE.test(segment) ? 2 : 1;
|
|
22902
|
-
/**
|
|
22903
|
-
* The columns `text` takes in a terminal, without its colors: `│`, `■`,
|
|
22904
|
-
* and `…` take one each, though each is several bytes.
|
|
22905
|
-
*/
|
|
22906
|
-
const displayWidth = (text) => {
|
|
22907
|
-
let width = 0;
|
|
22908
|
-
for (const { segment } of graphemes.segment(stripVTControlCharacters(text))) width += segmentWidth(segment);
|
|
22909
|
-
return width;
|
|
22910
|
-
};
|
|
22911
|
-
/**
|
|
22912
|
-
* `text` cut to `width` display columns, with `…` for the rest, never
|
|
22913
|
-
* inside a character: a cut line loses its colors.
|
|
22914
|
-
*/
|
|
22915
|
-
const truncateLine = (text, width) => {
|
|
22916
|
-
const plain = stripVTControlCharacters(text);
|
|
22917
|
-
if (displayWidth(plain) <= width) return text;
|
|
22918
|
-
let cut = "";
|
|
22919
|
-
let used = 0;
|
|
22920
|
-
for (const { segment } of graphemes.segment(plain)) {
|
|
22921
|
-
used += segmentWidth(segment);
|
|
22922
|
-
if (used > width - 1) break;
|
|
22923
|
-
cut += segment;
|
|
22924
|
-
}
|
|
22925
|
-
return `${cut.trimEnd()}…`;
|
|
22926
|
-
};
|
|
22927
|
-
/**
|
|
22928
|
-
* A message of `lines`, each at most `MESSAGE_WIDTH` columns, broken at
|
|
22929
|
-
* spaces, so a name, a path, or a list it embeds never widens a line. A
|
|
22930
|
-
* word longer than a line, such as a URL or a path, gets a line of its
|
|
22931
|
-
* own. Lines that fit, such as an indented command, stay as they are.
|
|
22932
|
-
*/
|
|
22933
|
-
const fitMessage = (lines) => lines.flatMap((line) => {
|
|
22934
|
-
if (displayWidth(line) <= 73) return [line];
|
|
22935
|
-
const fitted = [];
|
|
22936
|
-
let current = "";
|
|
22937
|
-
for (const word of line.split(" ").filter(Boolean)) if (current && displayWidth(current) + 1 + displayWidth(word) > 73) {
|
|
22938
|
-
fitted.push(current);
|
|
22939
|
-
current = word;
|
|
22940
|
-
} else current = current ? `${current} ${word}` : word;
|
|
22941
|
-
return [...fitted, current];
|
|
22942
|
-
}).join("\n");
|
|
22943
|
-
//#endregion
|
|
22944
23122
|
//#region src/colors.ts
|
|
22945
23123
|
var import_picocolors = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
22946
23124
|
let p = process || {};
|
|
@@ -23013,8 +23191,6 @@ var import_picocolors = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin((
|
|
|
23013
23191
|
const typedColors = import_picocolors.default;
|
|
23014
23192
|
//#endregion
|
|
23015
23193
|
//#region src/step.ts
|
|
23016
|
-
/** The widest a finished step's line is, after clack's 3-column gutter. */
|
|
23017
|
-
const STEP_WIDTH = 73;
|
|
23018
23194
|
/** The room the spinner's symbol and its `[12m 34s]` timer take. */
|
|
23019
23195
|
const LIVE_LINE_CHROME = 13;
|
|
23020
23196
|
/** `4.2s`, `38s`, `6m41s`, `1h02m`. */
|
|
@@ -23030,7 +23206,7 @@ const formatDuration = (ms) => {
|
|
|
23030
23206
|
* `message` with `duration` at the right edge of a step's line, the message
|
|
23031
23207
|
* cut with `…` when the line would be wider.
|
|
23032
23208
|
*/
|
|
23033
|
-
const withDuration = (message, ms, width =
|
|
23209
|
+
const withDuration = (message, ms, width = 73) => {
|
|
23034
23210
|
const duration = formatDuration(ms);
|
|
23035
23211
|
const text = truncateLine(message, width - duration.length - 2);
|
|
23036
23212
|
const gap = Math.max(2, width - displayWidth(text) - duration.length);
|
|
@@ -23127,7 +23303,7 @@ const createStep = (title, { output = process.stdout } = {}) => {
|
|
|
23127
23303
|
return;
|
|
23128
23304
|
}
|
|
23129
23305
|
finish(() => {
|
|
23130
|
-
log$2.error(maskSecretValues(withDuration(message, elapsed)), logOptions());
|
|
23306
|
+
log$2.error(maskSecretValues(withDuration(withProviderMessages(message), elapsed)), logOptions());
|
|
23131
23307
|
});
|
|
23132
23308
|
},
|
|
23133
23309
|
get elapsedMs() {
|
|
@@ -23362,7 +23538,7 @@ var BuildLogger = class {
|
|
|
23362
23538
|
const summary = this.failureSummary;
|
|
23363
23539
|
const logPath = this.logPath;
|
|
23364
23540
|
const message = [...summary, ...logPath ? [...summary.length ? [""] : [], `Build log: ${logPath}`] : []].join("\n");
|
|
23365
|
-
if (message)
|
|
23541
|
+
if (message) toolOutput.error(message, { output: process.stderr });
|
|
23366
23542
|
}
|
|
23367
23543
|
};
|
|
23368
23544
|
//#endregion
|
|
@@ -25716,12 +25892,7 @@ const assertPrivateDirectory = async (directory) => {
|
|
|
25716
25892
|
if (!root) return;
|
|
25717
25893
|
const top = realpath(root);
|
|
25718
25894
|
if (isIgnoredByGit(top, real)) return;
|
|
25719
|
-
throw new Error(fitMessage([
|
|
25720
|
-
`localSecrets() refuses ${directory}: its secrets could be committed.`,
|
|
25721
|
-
`The git repository ${top} doesn't ignore it.`,
|
|
25722
|
-
`Add ${path$1.relative(top, real).split(path$1.sep).join("/")}/ to its .gitignore,`,
|
|
25723
|
-
"or set localSecrets({ path })."
|
|
25724
|
-
]));
|
|
25895
|
+
throw new Error(fitMessage([`localSecrets() refuses ${directory}: its secrets could be committed.`, `Add ${path$1.relative(top, real).split(path$1.sep).join("/")}/ to ${top}'s .gitignore, or set localSecrets({ path }).`]));
|
|
25725
25896
|
};
|
|
25726
25897
|
/** Makes `directory` 0700, and every directory it creates on the way. */
|
|
25727
25898
|
const makePrivateDirectory = async (directory) => {
|
|
@@ -26071,11 +26242,7 @@ const readSecretRef = async (name, scope) => {
|
|
|
26071
26242
|
return fromEnv;
|
|
26072
26243
|
}
|
|
26073
26244
|
const secrets = getConfiguredAdapter(scope?.secrets ?? getConfiguredSecrets());
|
|
26074
|
-
if (secrets === void 0) throw new Error(fitMessage([
|
|
26075
|
-
`secret("${name}") has no value.`,
|
|
26076
|
-
`Set ${variable} in the environment or .env.hotupdater.`,
|
|
26077
|
-
"Or set the config's secrets to a team store."
|
|
26078
|
-
]));
|
|
26245
|
+
if (secrets === void 0) throw new Error(fitMessage([`secret("${name}") has no value.`, `Set ${variable} in the environment or .env.hotupdater.`]));
|
|
26079
26246
|
let values;
|
|
26080
26247
|
if (scope) {
|
|
26081
26248
|
scope.read ??= readSecrets(secrets, batchedNames(scope, name, fileEnv), cwd).catch((error) => {
|
|
@@ -26105,28 +26272,10 @@ const describeFields = (fields) => {
|
|
|
26105
26272
|
return `${described.slice(0, -1).join(", ")}, and ${described.at(-1)}`;
|
|
26106
26273
|
};
|
|
26107
26274
|
/**
|
|
26108
|
-
*
|
|
26109
|
-
*
|
|
26110
|
-
* wraps it mid-word and it still pastes as one command.
|
|
26275
|
+
* `lead` and the command it names on one line when they fit, else the
|
|
26276
|
+
* command on the lines after it.
|
|
26111
26277
|
*/
|
|
26112
|
-
const
|
|
26113
|
-
const lines = [];
|
|
26114
|
-
let line = "";
|
|
26115
|
-
for (const word of command.split(" ")) {
|
|
26116
|
-
const next = line ? `${line} ${word}` : word;
|
|
26117
|
-
if (line && next.length > 72) {
|
|
26118
|
-
lines.push(`${line} \\`);
|
|
26119
|
-
line = ` ${word}`;
|
|
26120
|
-
} else line = next;
|
|
26121
|
-
}
|
|
26122
|
-
lines.push(line);
|
|
26123
|
-
return lines.join("\n");
|
|
26124
|
-
};
|
|
26125
|
-
/**
|
|
26126
|
-
* `lead` and the command it names on one line when they fit 77 columns,
|
|
26127
|
-
* else the command on the lines after it.
|
|
26128
|
-
*/
|
|
26129
|
-
const withLead = (lead, command) => lead.length + 1 + command.length <= 77 ? `${lead} ${command}` : `${lead}\n${formatCommand(command)}`;
|
|
26278
|
+
const withLead = (lead, command) => displayWidth(`${lead} ${command}`) <= 73 ? `${lead} ${command}` : `${lead}\n${command}`;
|
|
26130
26279
|
/**
|
|
26131
26280
|
* Why a build has no upload keystore, steering away from a second key:
|
|
26132
26281
|
* Play rejects builds signed with a key it does not have. Under a team's
|
|
@@ -26139,7 +26288,7 @@ const describeMissingKeystore = (store, item, name) => {
|
|
|
26139
26288
|
if (configured) return [
|
|
26140
26289
|
`No ${item} in ${describeSecret(configured, name)}.`,
|
|
26141
26290
|
"Ask whoever set it up to store it there, or import it with:",
|
|
26142
|
-
|
|
26291
|
+
credentialPutCommand("android-keystore", name)
|
|
26143
26292
|
].join("\n");
|
|
26144
26293
|
return [
|
|
26145
26294
|
`No ${item} in the ${store.name} secrets adapter.`,
|
|
@@ -26183,7 +26332,7 @@ const credentialStoreCommand = (kind, name = kind) => kind === "asc-api-key" ? c
|
|
|
26183
26332
|
* trusted by a shipped app or a store. Under a team's adapter the name or
|
|
26184
26333
|
* the access is wrong; under `localSecrets()` this machine lacks it.
|
|
26185
26334
|
*/
|
|
26186
|
-
const describeMissingSecret = (secrets, name, { action = "import your key", command }) => [fitMessage(getConfiguredAdapter(secrets) ? [`${name} isn't in the ${secrets.name} secrets adapter.`, `Check the name and your access, or ${action}:`] : [`${name} isn't on this machine.`, `Check the name, set ${secretVariableName(name)}, or ${action}:`]),
|
|
26335
|
+
const describeMissingSecret = (secrets, name, { action = "import your key", command }) => [fitMessage(getConfiguredAdapter(secrets) ? [`${name} isn't in the ${secrets.name} secrets adapter.`, `Check the name and your access, or ${action}:`] : [`${name} isn't on this machine.`, `Check the name, set ${secretVariableName(name)}, or ${action}:`]), command].join("\n");
|
|
26187
26336
|
var CredentialUnavailableError = class extends Error {
|
|
26188
26337
|
kind;
|
|
26189
26338
|
/** The environment variables that can provide this credential. */
|
|
@@ -26432,7 +26581,7 @@ const createCredentialResolver = ({ env = {}, secrets, cwd } = {}) => {
|
|
|
26432
26581
|
const reason = error instanceof Error ? error.message : String(error);
|
|
26433
26582
|
throw new CredentialUnavailableError(kind, `Couldn't read ${item} from the ${store.name} secrets adapter.\n${reason}`, { cause: error });
|
|
26434
26583
|
}
|
|
26435
|
-
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${
|
|
26584
|
+
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${credentialStoreCommand(kind, name)}`);
|
|
26436
26585
|
registerSecretValue(value);
|
|
26437
26586
|
return fromSecretValue[kind](value, {
|
|
26438
26587
|
files,
|
|
@@ -46600,8 +46749,11 @@ var import_readYaml = require_readYaml();
|
|
|
46600
46749
|
const require$2 = createRequire(import.meta.url);
|
|
46601
46750
|
/**
|
|
46602
46751
|
* The engine fingerprint.json records for the interface fingerprint. Change
|
|
46603
|
-
* it with any change to what is hashed or how
|
|
46604
|
-
* as an engine change rather than a
|
|
46752
|
+
* it with any change to what is hashed or how that moves every project's
|
|
46753
|
+
* hash, so the new hashes are reported as an engine change rather than a
|
|
46754
|
+
* native one. The name is part of every digest, so changing it moves them all:
|
|
46755
|
+
* tracking an input nothing tracked before keeps the name, moves only the
|
|
46756
|
+
* projects that have that input, and is reported as a change to its files.
|
|
46605
46757
|
*/
|
|
46606
46758
|
const INTERFACE_FINGERPRINT_ENGINE = "hot-updater/interface@1";
|
|
46607
46759
|
const GENERATED_DIRS = [
|
|
@@ -46648,6 +46800,9 @@ const nativeSourceDir = (platform, config) => {
|
|
|
46648
46800
|
if (typeof value !== "string") return void 0;
|
|
46649
46801
|
return platform === "ios" ? path$1.dirname(value) : value;
|
|
46650
46802
|
};
|
|
46803
|
+
const isInNodeModules = (dir) => dir.split(path$1.sep).includes("node_modules");
|
|
46804
|
+
/** A folder of native code that exists, outside what an install wrote. */
|
|
46805
|
+
const isLinkedSourceDir = (dir) => !isInNodeModules(dir) && existsSync(dir);
|
|
46651
46806
|
const readManifest = async (dir) => {
|
|
46652
46807
|
try {
|
|
46653
46808
|
return JSON.parse(await fs$2.readFile(path$1.join(dir, "package.json"), "utf8"));
|
|
@@ -46662,6 +46817,25 @@ const parseJsonOutput = (stdout, command) => {
|
|
|
46662
46817
|
} catch {}
|
|
46663
46818
|
throw new Error(`\`${command}\` printed no JSON configuration. Check that it runs in this project.`);
|
|
46664
46819
|
};
|
|
46820
|
+
const EXPO_MODULE_CONFIGS = ["expo-module.config.json", "unimodule.json"];
|
|
46821
|
+
/**
|
|
46822
|
+
* The folder of an Expo module autolinking found outside node_modules: the
|
|
46823
|
+
* nearest one above `anchor` that holds its registration or its package.json.
|
|
46824
|
+
* `create-expo-module --local` writes no package.json, and an empty `name`
|
|
46825
|
+
* reaches here when the module's package.json has none.
|
|
46826
|
+
*/
|
|
46827
|
+
const findLocalExpoModuleRoot = async (anchor, name) => {
|
|
46828
|
+
const start = await fs$2.realpath(anchor).catch(() => anchor);
|
|
46829
|
+
if (isInNodeModules(start)) return null;
|
|
46830
|
+
let dir = start;
|
|
46831
|
+
while (true) {
|
|
46832
|
+
if (name && (await readManifest(dir))?.name === name) return dir;
|
|
46833
|
+
for (const config of EXPO_MODULE_CONFIGS) if (existsSync(path$1.join(dir, config))) return dir;
|
|
46834
|
+
const parent = path$1.dirname(dir);
|
|
46835
|
+
if (parent === dir) return start;
|
|
46836
|
+
dir = parent;
|
|
46837
|
+
}
|
|
46838
|
+
};
|
|
46665
46839
|
const findPackageRoot = async (from, name) => {
|
|
46666
46840
|
let dir = from;
|
|
46667
46841
|
while (true) {
|
|
@@ -46679,7 +46853,7 @@ const findPackageRoot = async (from, name) => {
|
|
|
46679
46853
|
*/
|
|
46680
46854
|
const readExpoModuleConfig = async (root, platform) => {
|
|
46681
46855
|
let file = null;
|
|
46682
|
-
for (const name of
|
|
46856
|
+
for (const name of EXPO_MODULE_CONFIGS) {
|
|
46683
46857
|
file = await fs$2.readFile(path$1.join(root, name), "utf8").catch(() => null);
|
|
46684
46858
|
if (file !== null) break;
|
|
46685
46859
|
}
|
|
@@ -46795,8 +46969,7 @@ const readExpoModules = async (cwd, platform) => {
|
|
|
46795
46969
|
};
|
|
46796
46970
|
};
|
|
46797
46971
|
const readExpoModuleDependencies = (modules) => Promise.all(modules.flatMap((module) => {
|
|
46798
|
-
const name = module.packageName;
|
|
46799
|
-
if (!name) return [];
|
|
46972
|
+
const name = module.packageName ?? "";
|
|
46800
46973
|
const sourceDirs = [
|
|
46801
46974
|
...(module.projects ?? []).map(({ sourceDir }) => sourceDir),
|
|
46802
46975
|
...(module.plugins ?? []).map(({ sourceDir }) => sourceDir),
|
|
@@ -46804,19 +46977,65 @@ const readExpoModuleDependencies = (modules) => Promise.all(modules.flatMap((mod
|
|
|
46804
46977
|
].filter((dir) => Boolean(dir));
|
|
46805
46978
|
const files = [...(module.projects ?? []).flatMap(({ aarProjects }) => aarProjects ?? []), ...module.aarProjects ?? []].map(({ aarFilePath }) => aarFilePath).filter((file) => Boolean(file));
|
|
46806
46979
|
return [(async () => {
|
|
46807
|
-
const anchor = sourceDirs[0] ?? files[0];
|
|
46808
|
-
const root = anchor ? await findPackageRoot(anchor, name) : null;
|
|
46809
|
-
|
|
46810
|
-
name,
|
|
46980
|
+
const anchor = sourceDirs[0] ?? (files[0] ? path$1.dirname(files[0]) : void 0);
|
|
46981
|
+
const root = (anchor ? await findLocalExpoModuleRoot(anchor, name) : null) ?? (anchor && name ? await findPackageRoot(anchor, name) : null);
|
|
46982
|
+
if (root) return [{
|
|
46983
|
+
name: name || path$1.basename(root),
|
|
46811
46984
|
root,
|
|
46812
46985
|
sourceDirs,
|
|
46813
46986
|
...files.length > 0 ? { files } : {}
|
|
46814
|
-
}
|
|
46987
|
+
}];
|
|
46988
|
+
return name ? [{
|
|
46815
46989
|
name,
|
|
46816
46990
|
version: module.packageVersion ?? "unknown"
|
|
46817
|
-
};
|
|
46991
|
+
}] : [];
|
|
46818
46992
|
})()];
|
|
46819
|
-
}));
|
|
46993
|
+
})).then((dependencies) => dependencies.flat());
|
|
46994
|
+
/** The packages an autolinking config links on the platform. */
|
|
46995
|
+
const linkedDependencies = (config, platform) => Object.entries(config.dependencies ?? {}).flatMap(([key, dependency]) => {
|
|
46996
|
+
const platformConfig = dependency.platforms?.[platform];
|
|
46997
|
+
if (!platformConfig) return [];
|
|
46998
|
+
const sourceDir = nativeSourceDir(platform, platformConfig);
|
|
46999
|
+
if (dependency.name && dependency.root) {
|
|
47000
|
+
const { name, root } = dependency;
|
|
47001
|
+
return [sourceDir ? {
|
|
47002
|
+
name,
|
|
47003
|
+
root,
|
|
47004
|
+
sourceDirs: [sourceDir]
|
|
47005
|
+
} : {
|
|
47006
|
+
name,
|
|
47007
|
+
root
|
|
47008
|
+
}];
|
|
47009
|
+
}
|
|
47010
|
+
return sourceDir && isLinkedSourceDir(sourceDir) ? [{
|
|
47011
|
+
name: key,
|
|
47012
|
+
root: sourceDir,
|
|
47013
|
+
sourceDirs: [sourceDir]
|
|
47014
|
+
}] : [];
|
|
47015
|
+
});
|
|
47016
|
+
/**
|
|
47017
|
+
* The local packages of a bare app with Expo modules that only Expo's
|
|
47018
|
+
* `react-native-config` lists. `install-expo-modules` makes its native project
|
|
47019
|
+
* link through that command, which also reads autolinking's search paths, such
|
|
47020
|
+
* as `modules/`. Packages it lists in node_modules stay out, so an app with no
|
|
47021
|
+
* such package keeps its fingerprint.
|
|
47022
|
+
*/
|
|
47023
|
+
const readExpoLocalPackages = async (cwd, platform) => {
|
|
47024
|
+
const { bin, linksReactNative } = resolveExpoAutolinking(cwd);
|
|
47025
|
+
if (!linksReactNative || process.env["EXPO_USE_COMMUNITY_AUTOLINKING"] === "1") return [];
|
|
47026
|
+
const { exitCode, stdout } = await execa(process.execPath, [
|
|
47027
|
+
bin,
|
|
47028
|
+
"react-native-config",
|
|
47029
|
+
"--platform",
|
|
47030
|
+
platform,
|
|
47031
|
+
"--json"
|
|
47032
|
+
], {
|
|
47033
|
+
cwd,
|
|
47034
|
+
reject: false
|
|
47035
|
+
});
|
|
47036
|
+
if (exitCode !== 0 || !stdout.includes("{")) return [];
|
|
47037
|
+
return linkedDependencies(parseJsonOutput(stdout, "react-native-config"), platform).filter((dependency) => "root" in dependency && !isInNodeModules(dependency.root));
|
|
47038
|
+
};
|
|
46820
47039
|
const GRADLE_FLAGS = [
|
|
46821
47040
|
"hermesEnabled",
|
|
46822
47041
|
"hermesV1Enabled",
|
|
@@ -46869,24 +47088,23 @@ const readReactNativeInterface = async ({ cwd, command, args, platform, settings
|
|
|
46869
47088
|
const { stdout } = await execa(command, args, { cwd });
|
|
46870
47089
|
const config = parseJsonOutput(stdout, [path$1.basename(command), ...args].join(" "));
|
|
46871
47090
|
const reactNative = config.reactNativePath ? await readManifest(config.reactNativePath) : null;
|
|
46872
|
-
const dependencies =
|
|
46873
|
-
|
|
46874
|
-
|
|
46875
|
-
sourceDir: nativeSourceDir(platform, dependency.platforms[platform])
|
|
46876
|
-
}].map(({ sourceDir, ...rest }) => sourceDir ? {
|
|
46877
|
-
...rest,
|
|
46878
|
-
sourceDirs: [sourceDir]
|
|
46879
|
-
} : rest) : []);
|
|
46880
|
-
const expo = dependencies.some(({ name }) => name === "expo") ? await readExpoModules(cwd, platform) : {
|
|
47091
|
+
const dependencies = linkedDependencies(config, platform);
|
|
47092
|
+
const linksExpoModules = dependencies.some(({ name }) => name === "expo");
|
|
47093
|
+
const expo = linksExpoModules ? await readExpoModules(cwd, platform) : {
|
|
46881
47094
|
dependencies: [],
|
|
46882
47095
|
output: null
|
|
46883
47096
|
};
|
|
46884
47097
|
const expoModules = expo.dependencies;
|
|
46885
47098
|
const names = new Set(dependencies.map(({ name }) => name));
|
|
47099
|
+
const expoLocalPackages = linksExpoModules && command === process.execPath && !args.includes("react-native-config") ? await readExpoLocalPackages(cwd, platform) : [];
|
|
46886
47100
|
const settings = declaredSettings ?? (platform === "android" ? await readGradleFlags(config.project?.android?.sourceDir ?? path$1.join(cwd, "android")) : {});
|
|
46887
47101
|
return {
|
|
46888
47102
|
runtime: `react-native@${reactNative?.version ?? "unknown"}`,
|
|
46889
|
-
dependencies: [
|
|
47103
|
+
dependencies: [
|
|
47104
|
+
...dependencies,
|
|
47105
|
+
...expoModules.filter(({ name }) => !names.has(name)),
|
|
47106
|
+
...expoLocalPackages.filter(({ name }) => !names.has(name) && !expoModules.some((module) => module.name === name))
|
|
47107
|
+
],
|
|
46890
47108
|
settings,
|
|
46891
47109
|
autolinking: {
|
|
46892
47110
|
reactNative: config,
|
|
@@ -46982,6 +47200,18 @@ const findConfiguredPatches = async (root) => {
|
|
|
46982
47200
|
return patches;
|
|
46983
47201
|
};
|
|
46984
47202
|
/**
|
|
47203
|
+
* Whether the app's package.json installs `name` from a `file:` folder. pnpm,
|
|
47204
|
+
* Yarn, and Bun copy the folder into node_modules where npm links it, and the
|
|
47205
|
+
* version stays the same while its code changes. A tarball has a version of
|
|
47206
|
+
* its own, like a package from a registry or Git.
|
|
47207
|
+
*/
|
|
47208
|
+
const isFileFolderDependency = async (cwd, name) => {
|
|
47209
|
+
const manifest = await readManifest(cwd);
|
|
47210
|
+
const specifier = manifest?.dependencies?.[name] ?? manifest?.devDependencies?.[name] ?? manifest?.optionalDependencies?.[name];
|
|
47211
|
+
if (!specifier?.startsWith("file:")) return false;
|
|
47212
|
+
return fs$2.stat(path$1.resolve(cwd, specifier.slice(5))).then((stat) => stat.isDirectory(), () => false);
|
|
47213
|
+
};
|
|
47214
|
+
/**
|
|
46985
47215
|
* Fingerprints the native interface JavaScript depends on, not the native
|
|
46986
47216
|
* build: the runtime and its native settings, each registry package as
|
|
46987
47217
|
* `name@version`, the codegen
|
|
@@ -47034,7 +47264,7 @@ const createInterfaceFingerprint = async (cwd, options, nativeInterface) => {
|
|
|
47034
47264
|
continue;
|
|
47035
47265
|
}
|
|
47036
47266
|
const root = await fs$2.realpath(dependency.root);
|
|
47037
|
-
if (root
|
|
47267
|
+
if (isInNodeModules(root) && !await isFileFolderDependency(cwd, dependency.name)) {
|
|
47038
47268
|
const version = (await readManifest(root))?.version ?? "unknown";
|
|
47039
47269
|
addContents(`dependency:${dependency.name}`, `${dependency.name}@${version}`, "native-dependency");
|
|
47040
47270
|
continue;
|
|
@@ -47419,6 +47649,13 @@ const resolveConfig = (config, scopes) => {
|
|
|
47419
47649
|
Object.defineProperty(resolved, DEPLOY_SECRET_NAMES, { value: [...new Set(scopes.flatMap((scope) => [...scope.names ?? []]))].filter((name) => !buildNames.includes(name)) });
|
|
47420
47650
|
return resolved;
|
|
47421
47651
|
};
|
|
47652
|
+
/**
|
|
47653
|
+
* The names the config's build credentials reference, such as
|
|
47654
|
+
* `release-keystore`, which builds read from the `secrets` adapter for their
|
|
47655
|
+
* kind: `signing`'s, and each scheme's `signing.ascApiKey` and
|
|
47656
|
+
* `signing.keystore`.
|
|
47657
|
+
*/
|
|
47658
|
+
const getBuildSecretNames = (config) => [...new Set(buildSecretNames(config))];
|
|
47422
47659
|
const DEPLOY_SECRET_NAMES = Symbol.for("@hot-updater/cli-tools/deploy-secret-names");
|
|
47423
47660
|
/**
|
|
47424
47661
|
* The names the config's `secret()`s reference for its adapters, such as
|
|
@@ -47521,8 +47758,82 @@ const generateSchema = ({ database, plugins }, version, name) => {
|
|
|
47521
47758
|
return database.generateSchema(version, name, toolingTargetOf(plugins));
|
|
47522
47759
|
};
|
|
47523
47760
|
//#endregion
|
|
47761
|
+
//#region src/expoSdk.ts
|
|
47762
|
+
/**
|
|
47763
|
+
* The lowest Expo SDK Hot Updater supports, the floor of `@hot-updater/expo`'s
|
|
47764
|
+
* `expo` peer range. doctor, the config plugin and the build adapter read it.
|
|
47765
|
+
*/
|
|
47766
|
+
const MINIMUM_EXPO_SDK = 55;
|
|
47767
|
+
/** The command that moves a project to the minimum Expo SDK. */
|
|
47768
|
+
const EXPO_SDK_UPGRADE_COMMAND = `npx expo install expo@^55.0.0 --fix`;
|
|
47769
|
+
const readJson = (file) => {
|
|
47770
|
+
try {
|
|
47771
|
+
return JSON.parse(Ei.readFileSync(file, "utf-8"));
|
|
47772
|
+
} catch {
|
|
47773
|
+
return null;
|
|
47774
|
+
}
|
|
47775
|
+
};
|
|
47776
|
+
/**
|
|
47777
|
+
* The `expo` a build in `cwd` loads: the nearest `node_modules/expo` up from
|
|
47778
|
+
* it, as Node resolves a package. `NODE_PATH` isn't read, since package
|
|
47779
|
+
* managers point it at a store that isn't the project's.
|
|
47780
|
+
*/
|
|
47781
|
+
const installedExpoVersion = (cwd) => {
|
|
47782
|
+
let dir = path$1.resolve(cwd);
|
|
47783
|
+
while (true) {
|
|
47784
|
+
const manifest = readJson(path$1.join(dir, "node_modules", "expo", "package.json"));
|
|
47785
|
+
if (manifest?.version) return manifest.version;
|
|
47786
|
+
const parent = path$1.dirname(dir);
|
|
47787
|
+
if (parent === dir) return null;
|
|
47788
|
+
dir = parent;
|
|
47789
|
+
}
|
|
47790
|
+
};
|
|
47791
|
+
/**
|
|
47792
|
+
* The lowest version a declared range allows, for an exact version, `^`, `~`,
|
|
47793
|
+
* `>=`, `=` or an x-range. Null for anything else, such as `catalog:sdk55`,
|
|
47794
|
+
* `workspace:*`, `latest` or a union, whose digits name no version.
|
|
47795
|
+
*/
|
|
47796
|
+
const lowestOfRange = (range) => {
|
|
47797
|
+
const match = /^\s*(?:\^|~|>=|=)?\s*v?(\d+)(?:\.(\d+|x|\*))?(?:\.(\d+|x|\*))?(?:-[0-9A-Za-z.-]+)?\s*$/u.exec(range);
|
|
47798
|
+
if (!match) return null;
|
|
47799
|
+
const part = (value) => value === void 0 || value === "x" || value === "*" ? "0" : value;
|
|
47800
|
+
return `${match[1]}.${part(match[2])}.${part(match[3])}`;
|
|
47801
|
+
};
|
|
47802
|
+
/**
|
|
47803
|
+
* A project's Expo SDK: the installed `expo` the project resolves, else the
|
|
47804
|
+
* lowest version its declared range allows. Null without either.
|
|
47805
|
+
*/
|
|
47806
|
+
const readExpoSdk = (cwd) => {
|
|
47807
|
+
const installed = installedExpoVersion(cwd);
|
|
47808
|
+
if (installed !== null) {
|
|
47809
|
+
const sdk = Number.parseInt(installed, 10);
|
|
47810
|
+
return Number.isNaN(sdk) ? null : {
|
|
47811
|
+
sdk,
|
|
47812
|
+
version: installed,
|
|
47813
|
+
installed: true
|
|
47814
|
+
};
|
|
47815
|
+
}
|
|
47816
|
+
const manifest = readJson(path$1.join(cwd, "package.json"));
|
|
47817
|
+
const declared = manifest?.dependencies?.["expo"] ?? manifest?.devDependencies?.["expo"];
|
|
47818
|
+
const lowest = declared === void 0 ? null : lowestOfRange(declared);
|
|
47819
|
+
if (lowest === null) return null;
|
|
47820
|
+
return {
|
|
47821
|
+
sdk: Number.parseInt(lowest, 10),
|
|
47822
|
+
version: lowest,
|
|
47823
|
+
installed: false
|
|
47824
|
+
};
|
|
47825
|
+
};
|
|
47826
|
+
/**
|
|
47827
|
+
* Refuses a project whose installed Expo is below the minimum SDK. A project
|
|
47828
|
+
* without Expo installed passes: nothing could build it yet.
|
|
47829
|
+
*/
|
|
47830
|
+
const assertSupportedExpoSdk = (cwd) => {
|
|
47831
|
+
const expo = readExpoSdk(cwd);
|
|
47832
|
+
if (expo?.installed && expo.sdk < 55) throw new Error(`Hot Updater requires Expo SDK 55 or later. Run:\n${EXPO_SDK_UPGRADE_COMMAND}`);
|
|
47833
|
+
};
|
|
47834
|
+
//#endregion
|
|
47524
47835
|
//#region src/log.ts
|
|
47525
|
-
const mask = (message) => typeof message === "string" ? maskSecretValues(message) : message;
|
|
47836
|
+
const mask = (message) => typeof message === "string" ? fitText(maskSecretValues(message)) : message;
|
|
47526
47837
|
const log = {
|
|
47527
47838
|
normal: (message) => console.log(mask(message)),
|
|
47528
47839
|
success: (message) => console.log(import_picocolors.default.green(mask(message))),
|
|
@@ -47555,6 +47866,10 @@ const formatComment = (comment) => {
|
|
|
47555
47866
|
if (comment.includes("\0") || comment.includes("\r") || comment.includes("\n")) throw new Error("Environment comments cannot contain NUL or newlines.");
|
|
47556
47867
|
return `# ${comment}`;
|
|
47557
47868
|
};
|
|
47869
|
+
/**
|
|
47870
|
+
* Replaces `filePath` whole or not at all, through a new 0600 file beside it,
|
|
47871
|
+
* so the result is 0600 whatever the mode of the file it replaces.
|
|
47872
|
+
*/
|
|
47558
47873
|
const writeEnvAtomically = async (filePath, content) => {
|
|
47559
47874
|
const resolvedPath = path$1.resolve(filePath);
|
|
47560
47875
|
const temporaryPath = path$1.join(path$1.dirname(resolvedPath), `.${path$1.basename(resolvedPath)}.${randomUUID()}.tmp`);
|
|
@@ -47632,11 +47947,96 @@ const makeEnv = async (newEnvVars, filePath = ".env.hotupdater", options) => {
|
|
|
47632
47947
|
await writeEnvAtomically(resolvedFilePath, updatedContent);
|
|
47633
47948
|
return updatedContent;
|
|
47634
47949
|
} catch (error) {
|
|
47635
|
-
|
|
47950
|
+
p.log.error(`Error while updating .env.hotupdater file: ${error instanceof Error ? error.message : String(error)}`);
|
|
47636
47951
|
throw error;
|
|
47637
47952
|
}
|
|
47638
47953
|
};
|
|
47639
47954
|
//#endregion
|
|
47955
|
+
//#region src/mergeEnvFile.ts
|
|
47956
|
+
const BARE_VALUE = /^[A-Za-z0-9_./:@%+,=-]+$/u;
|
|
47957
|
+
/**
|
|
47958
|
+
* `value` as an env file's value, in the first form `parseLoadedEnv` reads
|
|
47959
|
+
* back exactly: bare, single-quoted on one line, double-quoted with its
|
|
47960
|
+
* newlines as `\n`, in backticks, or single-quoted across lines. Undefined
|
|
47961
|
+
* for a value no form holds, such as one with every quote mark and a newline.
|
|
47962
|
+
*/
|
|
47963
|
+
const formatEnvValue = (value) => {
|
|
47964
|
+
return [
|
|
47965
|
+
...BARE_VALUE.test(value) ? [value] : [],
|
|
47966
|
+
...value.includes("\n") ? [] : [`'${value}'`],
|
|
47967
|
+
`"${value.replaceAll("\n", "\\n")}"`,
|
|
47968
|
+
`\`${value}\``,
|
|
47969
|
+
`'${value}'`
|
|
47970
|
+
].find((form) => parseLoadedEnv(`V=${form}\n`)["V"] === value);
|
|
47971
|
+
};
|
|
47972
|
+
const ASSIGNMENT = /^\s*(?:export\s+)?([^\s=#]+)[ \t]*=[ \t]*/u;
|
|
47973
|
+
/** The file's assignments in order, read as `parseLoadedEnv` reads them. */
|
|
47974
|
+
const scanEntries = (content) => {
|
|
47975
|
+
const entries = [];
|
|
47976
|
+
const lineEnd = (from) => {
|
|
47977
|
+
const newline = content.indexOf("\n", from);
|
|
47978
|
+
return newline === -1 ? content.length : newline;
|
|
47979
|
+
};
|
|
47980
|
+
let position = 0;
|
|
47981
|
+
while (position < content.length) {
|
|
47982
|
+
const end = lineEnd(position);
|
|
47983
|
+
const match = ASSIGNMENT.exec(content.slice(position, end));
|
|
47984
|
+
if (!match) {
|
|
47985
|
+
position = end + 1;
|
|
47986
|
+
continue;
|
|
47987
|
+
}
|
|
47988
|
+
const start = position + match[0].length;
|
|
47989
|
+
const quote = content[start];
|
|
47990
|
+
const closing = quote === "\"" || quote === "'" || quote === "`" ? content.indexOf(quote, start + 1) : -1;
|
|
47991
|
+
if (closing !== -1) {
|
|
47992
|
+
entries.push({
|
|
47993
|
+
key: match[1],
|
|
47994
|
+
start,
|
|
47995
|
+
end: closing + 1
|
|
47996
|
+
});
|
|
47997
|
+
position = lineEnd(closing + 1) + 1;
|
|
47998
|
+
continue;
|
|
47999
|
+
}
|
|
48000
|
+
const comment = content.indexOf("#", start);
|
|
48001
|
+
const stop = comment !== -1 && comment < end ? comment : end;
|
|
48002
|
+
entries.push({
|
|
48003
|
+
key: match[1],
|
|
48004
|
+
start,
|
|
48005
|
+
end: start + content.slice(start, stop).trimEnd().length
|
|
48006
|
+
});
|
|
48007
|
+
position = end + 1;
|
|
48008
|
+
}
|
|
48009
|
+
return entries;
|
|
48010
|
+
};
|
|
48011
|
+
const sameEnv = (left, right) => Object.keys(left).length === Object.keys(right).length && Object.keys(left).every((key) => Object.hasOwn(right, key) && left[key] === right[key]);
|
|
48012
|
+
/**
|
|
48013
|
+
* `content`, an env file, with `values` set, already as `formatEnvValue`
|
|
48014
|
+
* gives them: a variable it assigns has only that value replaced, the last
|
|
48015
|
+
* assignment when it repeats, which is the one that counts, and a new one
|
|
48016
|
+
* goes at the end. Every other byte stays: other lines, comments, an
|
|
48017
|
+
* `export`, an inline comment. Undefined when `parseLoadedEnv` wouldn't read
|
|
48018
|
+
* exactly that back, so a file this can't follow is never rewritten.
|
|
48019
|
+
*/
|
|
48020
|
+
const mergeEnvContent = (content, values) => {
|
|
48021
|
+
const entries = scanEntries(content);
|
|
48022
|
+
const last = new Map(entries.map((entry) => [entry.key, entry]));
|
|
48023
|
+
const replaced = [...values.keys()].flatMap((key) => last.get(key) ?? []).sort((left, right) => right.start - left.start);
|
|
48024
|
+
let merged = content;
|
|
48025
|
+
for (const entry of replaced) {
|
|
48026
|
+
const gap = merged[entry.end] === "#" ? " " : "";
|
|
48027
|
+
merged = `${merged.slice(0, entry.start)}${values.get(entry.key)}${gap}${merged.slice(entry.end)}`;
|
|
48028
|
+
}
|
|
48029
|
+
const eol = content.includes("\r\n") ? "\r\n" : "\n";
|
|
48030
|
+
const added = [...values].filter(([key]) => !last.has(key));
|
|
48031
|
+
if (added.length > 0) {
|
|
48032
|
+
const gap = merged === "" || merged.endsWith("\n") ? "" : eol;
|
|
48033
|
+
merged += `${gap}${added.map(([key, value]) => `${key}=${value}${eol}`).join("")}`;
|
|
48034
|
+
}
|
|
48035
|
+
const expected = { ...parseLoadedEnv(content) };
|
|
48036
|
+
for (const [key, value] of values) expected[key] = parseLoadedEnv(`V=${value}\n`)["V"] ?? "";
|
|
48037
|
+
return sameEnv(parseLoadedEnv(merged), expected) ? merged : void 0;
|
|
48038
|
+
};
|
|
48039
|
+
//#endregion
|
|
47640
48040
|
//#region src/minBundleId.ts
|
|
47641
48041
|
/**
|
|
47642
48042
|
* The MIN_BUNDLE_ID a native build compiles in, which is also the ID of its
|
|
@@ -47840,4 +48240,4 @@ function transformTemplate(templateString, values) {
|
|
|
47840
48240
|
return result;
|
|
47841
48241
|
}
|
|
47842
48242
|
//#endregion
|
|
47843
|
-
export { BUNDLE_SIGNING_KEY_SECRET, BuildFailedError, BuildLogger, BundleSigningKeyError, CREDENTIAL_ENV_NAMES, CREDENTIAL_LOGIN_GROUPS, ConfigBuilder, CredentialUnavailableError, HOT_UPDATER_HOME_ENV, HOT_UPDATER_SERVER_PACKAGE_VERSION_ENV, HotUpdateDirUtil, INTERFACE_FINGERPRINT_ENGINE, INVALID_SIGNING_MESSAGE, INVALID_SIGNING_NEXT_STEP, ISSUE_SIGNING_KEY_COMMAND, InitEnvFileError, InitError, LOCAL_SECRETS_NAME, LegacyInfrastructureError, MANAGED_CLIENT_CREDENTIAL_ENV, MESSAGE_WIDTH, MissingInitInputsError, PUT_SIGNING_KEY_COMMAND, ServerDefinitionError, UNSIGNED_BUNDLES_NEXT_STEP, UNSIGNED_BUNDLES_WARNING, addSigningSecretToConfig, addSigningSecretToConfigText, assembleServer, assertInfrastructureGenerationAtUrl, assertInfrastructureGenerationPayload, assertInitInputs, assertInitProviderInputs, assertUnreservedEnv, autolinkingConfigCommand, banner, bundleSigningPutCommand, bundleSigningSecretRef, clientAuthOf, typedColors as colors, configSetsTeamSecrets, confirmInitInputPersistence, copyDirToTmp, createCredentialResolver, createHotUpdaterConfigScaffold, createHotUpdaterConfigScaffoldFromBuilder, createInterfaceFingerprint, createLocalSecretsStore, createLogWriter, createMigrator, createMinBundleId, createStep, createTarBrTargetFiles, createZip, credentialIssueCommand, credentialLoginCommand, credentialPutCommand, credentialStoreCommand, decryptJson, defineInitProvider, describeMissingSecret, describeSecret, describeStoredSecret, displayWidth, encryptJson, ensureInstallPackages, failActiveStep, fitMessage,
|
|
48243
|
+
export { BUNDLE_SIGNING_KEY_SECRET, BuildFailedError, BuildLogger, BundleSigningKeyError, CREDENTIAL_ENV_NAMES, CREDENTIAL_LOGIN_GROUPS, ConfigBuilder, CredentialUnavailableError, EXPO_SDK_UPGRADE_COMMAND, HOT_UPDATER_HOME_ENV, HOT_UPDATER_SERVER_PACKAGE_VERSION_ENV, HotUpdateDirUtil, INTERFACE_FINGERPRINT_ENGINE, INVALID_SIGNING_MESSAGE, INVALID_SIGNING_NEXT_STEP, ISSUE_SIGNING_KEY_COMMAND, InitEnvFileError, InitError, LOCAL_SECRETS_NAME, LegacyInfrastructureError, MANAGED_CLIENT_CREDENTIAL_ENV, MESSAGE_WIDTH, MINIMUM_EXPO_SDK, MissingInitInputsError, PUT_SIGNING_KEY_COMMAND, ServerDefinitionError, UNSIGNED_BUNDLES_NEXT_STEP, UNSIGNED_BUNDLES_WARNING, addSigningSecretToConfig, addSigningSecretToConfigText, assembleServer, assertInfrastructureGenerationAtUrl, assertInfrastructureGenerationPayload, assertInitInputs, assertInitProviderInputs, assertSupportedExpoSdk, assertUnreservedEnv, autolinkingConfigCommand, banner, bundleSigningPutCommand, bundleSigningSecretRef, clientAuthOf, typedColors as colors, configSetsTeamSecrets, confirmInitInputPersistence, copyDirToTmp, createCredentialResolver, createHotUpdaterConfigScaffold, createHotUpdaterConfigScaffoldFromBuilder, createInterfaceFingerprint, createLocalSecretsStore, createLogWriter, createMigrator, createMinBundleId, createStep, createTarBrTargetFiles, createZip, credentialIssueCommand, credentialLoginCommand, credentialPutCommand, credentialStoreCommand, decryptJson, defineInitProvider, describeMissingSecret, describeSecret, describeStoredSecret, displayWidth, encryptJson, ensureInstallPackages, failActiveStep, fitMessage, fitText, formatDuration, formatEnvValue, generateClientCredential, generateSchema, generatesSchema, getAndroidSdkPath, getBuildSecretNames, getBundleSigningPublicKey, getConfiguredAdapter, getConfiguredSecrets, getCwd, getDeploySecretNames, getHotUpdaterEnvValue, getHotUpdaterHome, getHotUpdaterInitInputEnv, getInitProviderEnvVars, getInitProviderTextPromptValues, getLocalKeyFile, getLocalKeyStoreDir, getLocalSecretsStore, getMissingInitInputs, getMissingInitProviderInputs, getPackageManager, getProjectKey, getReactNativeMetadatas, getStorageFileByteSize, isCI, isCodegenSpecFile, isCommandLine, isReservedVariableName, isSecretName, isSecretRef, isServerDefinition, keepsKeysOnThisMachine, link, loadConfig, loadPlatformConfigs, loadTeamSecrets, localSecrets, log, makeEnv, maskSecretValues, mergeEnvContent, moduleSpecifiersOf, nonInteractiveReason, p, parseLoadedEnv, parseSecretReference, prepareBundleSigning, printAppSetup, printBanner, provisionClientCredential, putStorageFile, reactNativeCliFingerprint, reactNativeConfigCommand, readBundleSigningPublicKeyFile, readBundleSigningState, readExpoSdk, readHotUpdaterInitEnv, readLoadedEnvFile, readLoadedHotUpdaterEnv, readLocalKey, readMissingFromTeamSecrets, readPackageUp, readReactNativeInterface, readSecretVariables, readSecretWithSource, readSecrets, registerCredentialValue, registerSecretValue, renderAppImports, renderAppSetup, renderImportStatements, resolveExpoAutolinking, resolveHotUpdaterServerVersion, resolveInitProviderInput, resolveInitProviderInputs, resolveLocalSecretsPath, resolvePackageVersion, resolveSecretsAdapter, secret, secretVariableName, serverDefinitionOf, setConfiguredSecrets, shouldAutoSelectOnlyInitResource, startStep, storeEnvFileSecrets, stripAnsi, toolOutput, transformEnv, transformTemplate, truncateLine, withCredentialResolver, withDuration, withProviderMessages, withSecretsScope, withStep, writeEnvAtomically, 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.13",
|
|
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/
|
|
54
|
-
"@hot-updater/
|
|
52
|
+
"@hot-updater/plugin-core": "1.2.0-rc.13",
|
|
53
|
+
"@hot-updater/server": "1.2.0-rc.13",
|
|
54
|
+
"@hot-updater/protocol": "1.2.0-rc.13"
|
|
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.13"
|
|
69
69
|
},
|
|
70
70
|
"inlinedDependencies": {
|
|
71
71
|
"@babel/code-frame": "7.29.0",
|