@expo/cli 54.0.25 → 54.0.27
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/build/bin/cli +1 -1
- package/build/src/run/android/resolveInstallApkName.js +48 -1
- package/build/src/run/android/resolveInstallApkName.js.map +1 -1
- package/build/src/run/ios/XcodeBuild.js +23 -0
- package/build/src/run/ios/XcodeBuild.js.map +1 -1
- package/build/src/run/ios/appleDevice/AppleDevice.js +4 -1
- package/build/src/run/ios/appleDevice/AppleDevice.js.map +1 -1
- package/build/src/start/platforms/ios/devicectl.js +9 -2
- package/build/src/start/platforms/ios/devicectl.js.map +1 -1
- package/build/src/utils/codesigning.js +3 -0
- package/build/src/utils/codesigning.js.map +1 -1
- package/build/src/utils/findUp.js +17 -19
- package/build/src/utils/findUp.js.map +1 -1
- package/build/src/utils/telemetry/Telemetry.js +5 -0
- package/build/src/utils/telemetry/Telemetry.js.map +1 -1
- package/build/src/utils/telemetry/clients/FetchClient.js +1 -1
- package/build/src/utils/telemetry/utils/agent.js +42 -0
- package/build/src/utils/telemetry/utils/agent.js.map +1 -0
- package/build/src/utils/telemetry/utils/context.js +1 -1
- package/package.json +10 -9
package/build/bin/cli
CHANGED
|
@@ -29,6 +29,52 @@ function _interop_require_default(obj) {
|
|
|
29
29
|
};
|
|
30
30
|
}
|
|
31
31
|
const debug = require('debug')('expo:run:android:resolveInstallApkName');
|
|
32
|
+
function resolveApkFromOutputMetadata(apkVariantDirectory, availableCPUs) {
|
|
33
|
+
const metadataPath = _path().default.join(apkVariantDirectory, 'output-metadata.json');
|
|
34
|
+
let metadata;
|
|
35
|
+
try {
|
|
36
|
+
metadata = JSON.parse(_fs().default.readFileSync(metadataPath, 'utf8'));
|
|
37
|
+
} catch (error) {
|
|
38
|
+
debug('Failed to parse output-metadata.json', error);
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
const { elements } = metadata;
|
|
42
|
+
if (!(elements == null ? void 0 : elements.length)) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
// ABI split: match by device ABI. Exclude DeviceABI.universal — AGP never uses it as an ABI filter.
|
|
46
|
+
const isAbiSplit = elements.some((e)=>{
|
|
47
|
+
var _e_filters;
|
|
48
|
+
return e == null ? void 0 : (_e_filters = e.filters) == null ? void 0 : _e_filters.some((f)=>(f == null ? void 0 : f.filterType) === 'ABI');
|
|
49
|
+
});
|
|
50
|
+
if (isAbiSplit) {
|
|
51
|
+
for (const cpu of availableCPUs){
|
|
52
|
+
if (cpu === _adb.DeviceABI.universal) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const match = elements.find((e)=>{
|
|
56
|
+
var _e_filters;
|
|
57
|
+
return e == null ? void 0 : (_e_filters = e.filters) == null ? void 0 : _e_filters.some((f)=>f.filterType === 'ABI' && f.value === cpu);
|
|
58
|
+
});
|
|
59
|
+
const outputFile = match == null ? void 0 : match.outputFile;
|
|
60
|
+
if (typeof outputFile === 'string' && _fs().default.existsSync(_path().default.join(apkVariantDirectory, outputFile))) {
|
|
61
|
+
debug('Resolved ABI-split APK from output-metadata.json:', outputFile);
|
|
62
|
+
return outputFile;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
if (elements.length === 1) {
|
|
68
|
+
var _elements_;
|
|
69
|
+
const outputFile = (_elements_ = elements[0]) == null ? void 0 : _elements_.outputFile;
|
|
70
|
+
if (typeof outputFile === 'string' && _fs().default.existsSync(_path().default.join(apkVariantDirectory, outputFile))) {
|
|
71
|
+
debug('Resolved APK from output-metadata.json:', outputFile);
|
|
72
|
+
return outputFile;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Density/language splits produce multiple elements without ABI filters
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
32
78
|
async function resolveInstallApkNameAsync(device, { appName, buildType, flavors, apkVariantDirectory }) {
|
|
33
79
|
const availableCPUs = await (0, _adb.getDeviceABIsAsync)(device);
|
|
34
80
|
availableCPUs.push(_adb.DeviceABI.universal);
|
|
@@ -50,7 +96,8 @@ async function resolveInstallApkNameAsync(device, { appName, buildType, flavors,
|
|
|
50
96
|
if (_fs().default.existsSync(apkPath)) {
|
|
51
97
|
return apkName;
|
|
52
98
|
}
|
|
53
|
-
|
|
99
|
+
// Last resort: read AGP's output-metadata.json to handle custom outputFileName overrides.
|
|
100
|
+
return resolveApkFromOutputMetadata(apkVariantDirectory, availableCPUs);
|
|
54
101
|
}
|
|
55
102
|
function getApkFileName(appName, buildType, flavors, cpuArch) {
|
|
56
103
|
let apkName = `${appName}-`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/run/android/resolveInstallApkName.ts"],"sourcesContent":["import fs from 'fs';\nimport path from 'path';\n\nimport { GradleProps } from './resolveGradlePropsAsync';\nimport { Device, DeviceABI, getDeviceABIsAsync } from '../../start/platforms/android/adb';\n\nconst debug = require('debug')('expo:run:android:resolveInstallApkName') as typeof console.log;\n\nexport async function resolveInstallApkNameAsync(\n device: Pick<Device, 'name' | 'pid'>,\n { appName, buildType, flavors, apkVariantDirectory }: GradleProps\n) {\n const availableCPUs = await getDeviceABIsAsync(device);\n availableCPUs.push(DeviceABI.universal);\n\n debug('Supported ABIs: ' + availableCPUs.join(', '));\n debug('Searching for APK: ' + apkVariantDirectory);\n\n // Check for cpu specific builds first\n for (const availableCPU of availableCPUs) {\n const apkName = getApkFileName(appName, buildType, flavors, availableCPU);\n const apkPath = path.join(apkVariantDirectory, apkName);\n debug('Checking for APK at:', apkPath);\n if (fs.existsSync(apkPath)) {\n return apkName;\n }\n }\n\n // Otherwise use the default apk named after the variant: app-debug.apk\n const apkName = getApkFileName(appName, buildType, flavors);\n const apkPath = path.join(apkVariantDirectory, apkName);\n debug('Checking for fallback APK at:', apkPath);\n if (fs.existsSync(apkPath)) {\n return apkName;\n }\n\n return
|
|
1
|
+
{"version":3,"sources":["../../../../src/run/android/resolveInstallApkName.ts"],"sourcesContent":["import fs from 'fs';\nimport path from 'path';\n\nimport { GradleProps } from './resolveGradlePropsAsync';\nimport { Device, DeviceABI, getDeviceABIsAsync } from '../../start/platforms/android/adb';\n\nconst debug = require('debug')('expo:run:android:resolveInstallApkName') as typeof console.log;\n\ntype OutputMetadataElement = {\n filters?: { filterType: string; value: string }[];\n outputFile: string;\n};\n\ntype OutputMetadata = {\n elements: OutputMetadataElement[];\n};\n\nfunction resolveApkFromOutputMetadata(\n apkVariantDirectory: string,\n availableCPUs: DeviceABI[]\n): string | null {\n const metadataPath = path.join(apkVariantDirectory, 'output-metadata.json');\n let metadata: OutputMetadata;\n try {\n metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));\n } catch (error) {\n debug('Failed to parse output-metadata.json', error);\n return null;\n }\n\n const { elements } = metadata;\n if (!elements?.length) {\n return null;\n }\n\n // ABI split: match by device ABI. Exclude DeviceABI.universal — AGP never uses it as an ABI filter.\n const isAbiSplit = elements.some((e) => e?.filters?.some((f) => f?.filterType === 'ABI'));\n if (isAbiSplit) {\n for (const cpu of availableCPUs) {\n if (cpu === DeviceABI.universal) {\n continue;\n }\n const match = elements.find((e) =>\n e?.filters?.some((f) => f.filterType === 'ABI' && f.value === cpu)\n );\n const outputFile = match?.outputFile;\n if (\n typeof outputFile === 'string' &&\n fs.existsSync(path.join(apkVariantDirectory, outputFile))\n ) {\n debug('Resolved ABI-split APK from output-metadata.json:', outputFile);\n return outputFile;\n }\n }\n return null;\n }\n\n if (elements.length === 1) {\n const outputFile = elements[0]?.outputFile;\n if (\n typeof outputFile === 'string' &&\n fs.existsSync(path.join(apkVariantDirectory, outputFile))\n ) {\n debug('Resolved APK from output-metadata.json:', outputFile);\n return outputFile;\n }\n }\n\n // Density/language splits produce multiple elements without ABI filters\n return null;\n}\n\nexport async function resolveInstallApkNameAsync(\n device: Pick<Device, 'name' | 'pid'>,\n { appName, buildType, flavors, apkVariantDirectory }: GradleProps\n) {\n const availableCPUs = await getDeviceABIsAsync(device);\n availableCPUs.push(DeviceABI.universal);\n\n debug('Supported ABIs: ' + availableCPUs.join(', '));\n debug('Searching for APK: ' + apkVariantDirectory);\n\n // Check for cpu specific builds first\n for (const availableCPU of availableCPUs) {\n const apkName = getApkFileName(appName, buildType, flavors, availableCPU);\n const apkPath = path.join(apkVariantDirectory, apkName);\n debug('Checking for APK at:', apkPath);\n if (fs.existsSync(apkPath)) {\n return apkName;\n }\n }\n\n // Otherwise use the default apk named after the variant: app-debug.apk\n const apkName = getApkFileName(appName, buildType, flavors);\n const apkPath = path.join(apkVariantDirectory, apkName);\n debug('Checking for fallback APK at:', apkPath);\n if (fs.existsSync(apkPath)) {\n return apkName;\n }\n\n // Last resort: read AGP's output-metadata.json to handle custom outputFileName overrides.\n return resolveApkFromOutputMetadata(apkVariantDirectory, availableCPUs);\n}\n\nfunction getApkFileName(\n appName: string,\n buildType: string,\n flavors?: string[] | null,\n cpuArch?: string | null\n) {\n let apkName = `${appName}-`;\n if (flavors) {\n apkName += flavors.reduce((rest, flavor) => `${rest}${flavor}-`, '');\n }\n if (cpuArch) {\n apkName += `${cpuArch}-`;\n }\n apkName += `${buildType}.apk`;\n\n return apkName;\n}\n"],"names":["resolveInstallApkNameAsync","debug","require","resolveApkFromOutputMetadata","apkVariantDirectory","availableCPUs","metadataPath","path","join","metadata","JSON","parse","fs","readFileSync","error","elements","length","isAbiSplit","some","e","filters","f","filterType","cpu","DeviceABI","universal","match","find","value","outputFile","existsSync","device","appName","buildType","flavors","getDeviceABIsAsync","push","availableCPU","apkName","getApkFileName","apkPath","cpuArch","reduce","rest","flavor"],"mappings":"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"}
|
|
@@ -12,6 +12,9 @@ _export(exports, {
|
|
|
12
12
|
_assertXcodeBuildResults: function() {
|
|
13
13
|
return _assertXcodeBuildResults;
|
|
14
14
|
},
|
|
15
|
+
_extractXcodeBuildErrorLines: function() {
|
|
16
|
+
return _extractXcodeBuildErrorLines;
|
|
17
|
+
},
|
|
15
18
|
buildAsync: function() {
|
|
16
19
|
return buildAsync;
|
|
17
20
|
},
|
|
@@ -360,10 +363,30 @@ function _assertXcodeBuildResults(code, results, error, xcodeProject, logFilePat
|
|
|
360
363
|
if (localizedError) {
|
|
361
364
|
throwWithMessage(_chalk().default.bold(localizedError) + '\n\n');
|
|
362
365
|
}
|
|
366
|
+
// `@expo/xcpretty` can leave a real failure uncounted (its compile-error
|
|
367
|
+
// matching is stateful and anchored, and stderr never passes through it), so
|
|
368
|
+
// the build summary reads "0 error(s)" and in CI the full log below is
|
|
369
|
+
// truncated before the real error line.
|
|
370
|
+
const errorLines = _extractXcodeBuildErrorLines(results + '\n' + error);
|
|
371
|
+
if (errorLines.length) {
|
|
372
|
+
throwWithMessage(_chalk().default.red(errorLines.join('\n')) + '\n\n' + results + '\n\n' + error);
|
|
373
|
+
}
|
|
363
374
|
// Show all the log info because often times the error is coming from a shell script,
|
|
364
375
|
// that invoked a node script, that started metro, which threw an error.
|
|
365
376
|
throwWithMessage(results + '\n\n' + error);
|
|
366
377
|
}
|
|
378
|
+
function _extractXcodeBuildErrorLines(output) {
|
|
379
|
+
const seen = new Set();
|
|
380
|
+
const errors = [];
|
|
381
|
+
for (const raw of output.split(/\r?\n/)){
|
|
382
|
+
const line = raw.trim();
|
|
383
|
+
if (/(?:^|\s)error:\s/.test(line) && !seen.has(line)) {
|
|
384
|
+
seen.add(line);
|
|
385
|
+
errors.push(line);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return errors;
|
|
389
|
+
}
|
|
367
390
|
function writeBuildLogs(projectRoot, buildOutput, errorOutput) {
|
|
368
391
|
const [logFilePath, errorFilePath] = getErrorLogFilePath(projectRoot);
|
|
369
392
|
_fs().default.writeFileSync(logFilePath, buildOutput);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/run/ios/XcodeBuild.ts"],"sourcesContent":["import { ExpoRunFormatter } from '@expo/xcpretty';\nimport chalk from 'chalk';\nimport { spawn, SpawnOptionsWithoutStdio } from 'child_process';\nimport fs from 'fs';\nimport os from 'os';\nimport path from 'path';\n\nimport { BuildProps, ProjectInfo } from './XcodeBuild.types';\nimport { ensureDeviceIsCodeSignedForDeploymentAsync } from './codeSigning/configureCodeSigning';\nimport { simulatorBuildRequiresCodeSigning } from './codeSigning/simulatorCodeSigning';\nimport * as Log from '../../log';\nimport { ensureDirectory } from '../../utils/dir';\nimport { env } from '../../utils/env';\nimport { AbortCommandError, CommandError } from '../../utils/errors';\nimport { getUserTerminal } from '../../utils/terminal';\nexport function logPrettyItem(message: string) {\n Log.log(chalk`{whiteBright \\u203A} ${message}`);\n}\n\nexport function matchEstimatedBinaryPath(buildOutput: string): string | null {\n // Match the full path that contains `/(.*)/Developer/Xcode/DerivedData/(.*)/Build/Products/(.*)/(.*).app`\n const appBinaryPathMatch = buildOutput.match(\n /(\\/(?:\\\\\\s|[^ ])+\\/Developer\\/Xcode\\/DerivedData\\/(?:\\\\\\s|[^ ])+\\/Build\\/Products\\/(?:Debug|Release)-(?:[^\\s/]+)\\/(?:\\\\\\s|[^ ])+\\.app)/\n );\n if (!appBinaryPathMatch?.length) {\n throw new CommandError(\n 'XCODE_BUILD',\n `Malformed xcodebuild results: app binary path was not generated in build output. Report this issue and run your project with Xcode instead.`\n );\n } else {\n // Sort for the shortest\n const shortestPath = (appBinaryPathMatch.filter((a) => typeof a === 'string' && a) as string[])\n .sort((a: string, b: string) => a.length - b.length)[0]\n .trim();\n\n Log.debug(`Found app binary path: ${shortestPath}`);\n return shortestPath;\n }\n}\n/**\n *\n * @returns '/Users/evanbacon/Library/Developer/Xcode/DerivedData/myapp-gpgjqjodrxtervaufttwnsgimhrx/Build/Products/Debug-iphonesimulator/myapp.app'\n */\nexport function getAppBinaryPath(buildOutput: string) {\n // Matches what's used in \"Bundle React Native code and images\" script.\n // Requires that `-hideShellScriptEnvironment` is not included in the build command (extra logs).\n\n try {\n // Like `\\=/Users/evanbacon/Library/Developer/Xcode/DerivedData/Exponent-anpuosnglkxokahjhfszejloqfvo/Build/Products/Debug-iphonesimulator`\n const CONFIGURATION_BUILD_DIR = extractEnvVariableFromBuild(\n buildOutput,\n 'CONFIGURATION_BUILD_DIR'\n ).sort(\n // Longer name means more suffixes, we want the shortest possible one to be first.\n // Massive projects (like Expo Go) can sometimes print multiple different sets of environment variables.\n // This can become an issue with some\n (a, b) => a.length - b.length\n );\n // Like `Exponent.app`\n const UNLOCALIZED_RESOURCES_FOLDER_PATH = extractEnvVariableFromBuild(\n buildOutput,\n 'UNLOCALIZED_RESOURCES_FOLDER_PATH'\n );\n\n const binaryPath = path.join(\n // Use the shortest defined env variable (usually there's just one).\n CONFIGURATION_BUILD_DIR[0],\n // Use the last defined env variable.\n UNLOCALIZED_RESOURCES_FOLDER_PATH[UNLOCALIZED_RESOURCES_FOLDER_PATH.length - 1]\n );\n\n // If the app has a space in the name it'll fail because it isn't escaped properly by Xcode.\n return getEscapedPath(binaryPath);\n } catch (error) {\n if (error instanceof CommandError && error.code === 'XCODE_BUILD') {\n const possiblePath = matchEstimatedBinaryPath(buildOutput);\n if (possiblePath) {\n return possiblePath;\n }\n }\n throw error;\n }\n}\n\nexport function getEscapedPath(filePath: string): string {\n if (fs.existsSync(filePath)) {\n return filePath;\n }\n const unescapedPath = filePath.split(/\\\\ /).join(' ');\n if (fs.existsSync(unescapedPath)) {\n return unescapedPath;\n }\n throw new CommandError(\n 'XCODE_BUILD',\n `Unexpected: Generated app at path \"${filePath}\" cannot be read, the app cannot be installed. Report this and build onto a simulator.`\n );\n}\n\nexport function extractEnvVariableFromBuild(buildOutput: string, variableName: string) {\n // Xcode can sometimes escape `=` with a backslash or put the value in quotes\n const reg = new RegExp(`export ${variableName}\\\\\\\\?=(.*)$`, 'mg');\n const matched = [...buildOutput.matchAll(reg)];\n\n if (!matched || !matched.length) {\n throw new CommandError(\n 'XCODE_BUILD',\n `Malformed xcodebuild results: \"${variableName}\" variable was not generated in build output. Report this issue and run your project with Xcode instead.`\n );\n }\n return matched.map((value) => value[1]).filter(Boolean) as string[];\n}\n\nexport function getProcessOptions({\n packager,\n shouldSkipInitialBundling,\n terminal,\n port,\n eagerBundleOptions,\n}: {\n packager: boolean;\n shouldSkipInitialBundling?: boolean;\n terminal: string | undefined;\n port: number;\n eagerBundleOptions?: string;\n}): SpawnOptionsWithoutStdio {\n const SKIP_BUNDLING = shouldSkipInitialBundling ? '1' : undefined;\n if (packager) {\n return {\n env: {\n ...process.env,\n RCT_TERMINAL: terminal,\n SKIP_BUNDLING,\n RCT_METRO_PORT: port.toString(),\n __EXPO_EAGER_BUNDLE_OPTIONS: eagerBundleOptions,\n },\n };\n }\n\n return {\n env: {\n ...process.env,\n RCT_TERMINAL: terminal,\n SKIP_BUNDLING,\n __EXPO_EAGER_BUNDLE_OPTIONS: eagerBundleOptions,\n // Always skip launching the packager from a build script.\n // The script is used for people building their project directly from Xcode.\n // This essentially means \"› Running script 'Start Packager'\" does nothing.\n RCT_NO_LAUNCH_PACKAGER: 'true',\n // FORCE_BUNDLING: '0'\n },\n };\n}\n\nexport async function getXcodeBuildArgsAsync(\n props: Pick<\n BuildProps,\n | 'buildCache'\n | 'projectRoot'\n | 'xcodeProject'\n | 'configuration'\n | 'scheme'\n | 'device'\n | 'isSimulator'\n >\n): Promise<string[]> {\n const args = [\n props.xcodeProject.isWorkspace ? '-workspace' : '-project',\n props.xcodeProject.name,\n '-configuration',\n props.configuration,\n '-scheme',\n props.scheme,\n '-destination',\n `id=${props.device.udid}`,\n ];\n\n if (!props.isSimulator || simulatorBuildRequiresCodeSigning(props.projectRoot)) {\n const developmentTeamId = await ensureDeviceIsCodeSignedForDeploymentAsync(props.projectRoot);\n if (developmentTeamId) {\n args.push(\n `DEVELOPMENT_TEAM=${developmentTeamId}`,\n '-allowProvisioningUpdates',\n '-allowProvisioningDeviceRegistration'\n );\n }\n }\n\n // Add last\n if (props.buildCache === false) {\n args.push(\n // Will first clean the derived data folder.\n 'clean',\n // Then build step must be added otherwise the process will simply clean and exit.\n 'build'\n );\n }\n return args;\n}\n\nfunction spawnXcodeBuild(\n args: string[],\n options: SpawnOptionsWithoutStdio,\n { onData }: { onData: (data: string) => void }\n): Promise<{ code: number | null; results: string; error: string }> {\n const buildProcess = spawn('xcodebuild', args, options);\n\n let results = '';\n let error = '';\n\n buildProcess.stdout.on('data', (data: Buffer) => {\n const stringData = data.toString();\n results += stringData;\n onData(stringData);\n });\n\n buildProcess.stderr.on('data', (data: Buffer) => {\n const stringData = data instanceof Buffer ? data.toString() : data;\n error += stringData;\n });\n\n return new Promise(async (resolve, reject) => {\n buildProcess.on('close', (code: number) => {\n resolve({ code, results, error });\n });\n });\n}\n\nasync function spawnXcodeBuildWithFlush(\n args: string[],\n options: SpawnOptionsWithoutStdio,\n { onFlush }: { onFlush: (data: string) => void }\n): Promise<{ code: number | null; results: string; error: string }> {\n let currentBuffer = '';\n\n // Data can be sent in chunks that would have no relevance to our regex\n // this can cause massive slowdowns, so we need to ensure the data is complete before attempting to parse it.\n function flushBuffer() {\n if (!currentBuffer) {\n return;\n }\n\n const data = currentBuffer;\n // Reset buffer.\n currentBuffer = '';\n // Process data.\n onFlush(data);\n }\n\n const data = await spawnXcodeBuild(args, options, {\n onData(stringData) {\n currentBuffer += stringData;\n // Only flush the data if we have a full line.\n if (currentBuffer.endsWith(os.EOL)) {\n flushBuffer();\n }\n },\n });\n\n // Flush log data at the end just in case we missed something.\n flushBuffer();\n return data;\n}\n\nasync function spawnXcodeBuildWithFormat(\n args: string[],\n options: SpawnOptionsWithoutStdio,\n { projectRoot, xcodeProject }: { projectRoot: string; xcodeProject: ProjectInfo }\n): Promise<{ code: number | null; results: string; error: string; formatter: ExpoRunFormatter }> {\n Log.debug(` xcodebuild ${args.join(' ')}`);\n\n logPrettyItem(chalk.bold`Planning build`);\n\n const formatter = ExpoRunFormatter.create(projectRoot, {\n xcodeProject,\n isDebug: env.EXPO_DEBUG,\n });\n\n const results = await spawnXcodeBuildWithFlush(args, options, {\n onFlush(data) {\n // Process data.\n for (const line of formatter.pipe(data)) {\n // Log parsed results.\n Log.log(line);\n }\n },\n });\n\n Log.debug(`Exited with code: ${results.code}`);\n\n if (\n // User cancelled with ctrl-c\n results.code === null ||\n // Build interrupted\n results.code === 75\n ) {\n throw new AbortCommandError();\n }\n\n Log.log(formatter.getBuildSummary());\n\n return { ...results, formatter };\n}\n\nexport async function buildAsync(props: BuildProps): Promise<string> {\n const args = await getXcodeBuildArgsAsync(props);\n\n const { projectRoot, xcodeProject, shouldSkipInitialBundling, port, eagerBundleOptions } = props;\n\n const { code, results, formatter, error } = await spawnXcodeBuildWithFormat(\n args,\n getProcessOptions({\n packager: false,\n terminal: getUserTerminal(),\n shouldSkipInitialBundling,\n port,\n eagerBundleOptions,\n }),\n {\n projectRoot,\n xcodeProject,\n }\n );\n\n const logFilePath = writeBuildLogs(projectRoot, results, error);\n\n if (code !== 0) {\n // Determine if the logger found any errors;\n const wasErrorPresented = !!formatter.errors.length;\n\n if (wasErrorPresented) {\n // This has a flaw, if the user is missing a file, and there is a script error, only the missing file error will be shown.\n // They will only see the script error if they fix the missing file and rerun.\n // The flaw can be fixed by catching script errors in the custom logger.\n throw new CommandError(\n `Failed to build iOS project. \"xcodebuild\" exited with error code ${code}.`\n );\n }\n\n _assertXcodeBuildResults(code, results, error, xcodeProject, logFilePath);\n }\n return results;\n}\n\n// Exposed for testing.\nexport function _assertXcodeBuildResults(\n code: number | null,\n results: string,\n error: string,\n xcodeProject: { name: string },\n logFilePath: string\n): void {\n const errorTitle = `Failed to build iOS project. \"xcodebuild\" exited with error code ${code}.`;\n\n const throwWithMessage = (message: string): never => {\n throw new CommandError(\n `${errorTitle}\\nTo view more error logs, try building the app with Xcode directly, by opening ${xcodeProject.name}.\\n\\n` +\n message +\n `Build logs written to ${chalk.underline(logFilePath)}`\n );\n };\n\n const localizedError = error.match(/NSLocalizedFailure = \"(.*)\"/)?.[1];\n\n if (localizedError) {\n throwWithMessage(chalk.bold(localizedError) + '\\n\\n');\n }\n // Show all the log info because often times the error is coming from a shell script,\n // that invoked a node script, that started metro, which threw an error.\n\n throwWithMessage(results + '\\n\\n' + error);\n}\n\nfunction writeBuildLogs(projectRoot: string, buildOutput: string, errorOutput: string) {\n const [logFilePath, errorFilePath] = getErrorLogFilePath(projectRoot);\n\n fs.writeFileSync(logFilePath, buildOutput);\n fs.writeFileSync(errorFilePath, errorOutput);\n return logFilePath;\n}\n\nfunction getErrorLogFilePath(projectRoot: string): [string, string] {\n const folder = path.join(projectRoot, '.expo');\n ensureDirectory(folder);\n return [path.join(folder, 'xcodebuild.log'), path.join(folder, 'xcodebuild-error.log')];\n}\n"],"names":["_assertXcodeBuildResults","buildAsync","extractEnvVariableFromBuild","getAppBinaryPath","getEscapedPath","getProcessOptions","getXcodeBuildArgsAsync","logPrettyItem","matchEstimatedBinaryPath","message","Log","log","chalk","buildOutput","appBinaryPathMatch","match","length","CommandError","shortestPath","filter","a","sort","b","trim","debug","CONFIGURATION_BUILD_DIR","UNLOCALIZED_RESOURCES_FOLDER_PATH","binaryPath","path","join","error","code","possiblePath","filePath","fs","existsSync","unescapedPath","split","variableName","reg","RegExp","matched","matchAll","map","value","Boolean","packager","shouldSkipInitialBundling","terminal","port","eagerBundleOptions","SKIP_BUNDLING","undefined","env","process","RCT_TERMINAL","RCT_METRO_PORT","toString","__EXPO_EAGER_BUNDLE_OPTIONS","RCT_NO_LAUNCH_PACKAGER","props","args","xcodeProject","isWorkspace","name","configuration","scheme","device","udid","isSimulator","simulatorBuildRequiresCodeSigning","projectRoot","developmentTeamId","ensureDeviceIsCodeSignedForDeploymentAsync","push","buildCache","spawnXcodeBuild","options","onData","buildProcess","spawn","results","stdout","on","data","stringData","stderr","Buffer","Promise","resolve","reject","spawnXcodeBuildWithFlush","onFlush","currentBuffer","flushBuffer","endsWith","os","EOL","spawnXcodeBuildWithFormat","bold","formatter","ExpoRunFormatter","create","isDebug","EXPO_DEBUG","line","pipe","AbortCommandError","getBuildSummary","getUserTerminal","logFilePath","writeBuildLogs","wasErrorPresented","errors","errorTitle","throwWithMessage","underline","localizedError","errorOutput","errorFilePath","getErrorLogFilePath","writeFileSync","folder","ensureDirectory"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../src/run/ios/XcodeBuild.ts"],"sourcesContent":["import { ExpoRunFormatter } from '@expo/xcpretty';\nimport chalk from 'chalk';\nimport { spawn, SpawnOptionsWithoutStdio } from 'child_process';\nimport fs from 'fs';\nimport os from 'os';\nimport path from 'path';\n\nimport { BuildProps, ProjectInfo } from './XcodeBuild.types';\nimport { ensureDeviceIsCodeSignedForDeploymentAsync } from './codeSigning/configureCodeSigning';\nimport { simulatorBuildRequiresCodeSigning } from './codeSigning/simulatorCodeSigning';\nimport * as Log from '../../log';\nimport { ensureDirectory } from '../../utils/dir';\nimport { env } from '../../utils/env';\nimport { AbortCommandError, CommandError } from '../../utils/errors';\nimport { getUserTerminal } from '../../utils/terminal';\nexport function logPrettyItem(message: string) {\n Log.log(chalk`{whiteBright \\u203A} ${message}`);\n}\n\nexport function matchEstimatedBinaryPath(buildOutput: string): string | null {\n // Match the full path that contains `/(.*)/Developer/Xcode/DerivedData/(.*)/Build/Products/(.*)/(.*).app`\n const appBinaryPathMatch = buildOutput.match(\n /(\\/(?:\\\\\\s|[^ ])+\\/Developer\\/Xcode\\/DerivedData\\/(?:\\\\\\s|[^ ])+\\/Build\\/Products\\/(?:Debug|Release)-(?:[^\\s/]+)\\/(?:\\\\\\s|[^ ])+\\.app)/\n );\n if (!appBinaryPathMatch?.length) {\n throw new CommandError(\n 'XCODE_BUILD',\n `Malformed xcodebuild results: app binary path was not generated in build output. Report this issue and run your project with Xcode instead.`\n );\n } else {\n // Sort for the shortest\n const shortestPath = (appBinaryPathMatch.filter((a) => typeof a === 'string' && a) as string[])\n .sort((a: string, b: string) => a.length - b.length)[0]\n .trim();\n\n Log.debug(`Found app binary path: ${shortestPath}`);\n return shortestPath;\n }\n}\n/**\n *\n * @returns '/Users/evanbacon/Library/Developer/Xcode/DerivedData/myapp-gpgjqjodrxtervaufttwnsgimhrx/Build/Products/Debug-iphonesimulator/myapp.app'\n */\nexport function getAppBinaryPath(buildOutput: string) {\n // Matches what's used in \"Bundle React Native code and images\" script.\n // Requires that `-hideShellScriptEnvironment` is not included in the build command (extra logs).\n\n try {\n // Like `\\=/Users/evanbacon/Library/Developer/Xcode/DerivedData/Exponent-anpuosnglkxokahjhfszejloqfvo/Build/Products/Debug-iphonesimulator`\n const CONFIGURATION_BUILD_DIR = extractEnvVariableFromBuild(\n buildOutput,\n 'CONFIGURATION_BUILD_DIR'\n ).sort(\n // Longer name means more suffixes, we want the shortest possible one to be first.\n // Massive projects (like Expo Go) can sometimes print multiple different sets of environment variables.\n // This can become an issue with some\n (a, b) => a.length - b.length\n );\n // Like `Exponent.app`\n const UNLOCALIZED_RESOURCES_FOLDER_PATH = extractEnvVariableFromBuild(\n buildOutput,\n 'UNLOCALIZED_RESOURCES_FOLDER_PATH'\n );\n\n const binaryPath = path.join(\n // Use the shortest defined env variable (usually there's just one).\n CONFIGURATION_BUILD_DIR[0],\n // Use the last defined env variable.\n UNLOCALIZED_RESOURCES_FOLDER_PATH[UNLOCALIZED_RESOURCES_FOLDER_PATH.length - 1]\n );\n\n // If the app has a space in the name it'll fail because it isn't escaped properly by Xcode.\n return getEscapedPath(binaryPath);\n } catch (error) {\n if (error instanceof CommandError && error.code === 'XCODE_BUILD') {\n const possiblePath = matchEstimatedBinaryPath(buildOutput);\n if (possiblePath) {\n return possiblePath;\n }\n }\n throw error;\n }\n}\n\nexport function getEscapedPath(filePath: string): string {\n if (fs.existsSync(filePath)) {\n return filePath;\n }\n const unescapedPath = filePath.split(/\\\\ /).join(' ');\n if (fs.existsSync(unescapedPath)) {\n return unescapedPath;\n }\n throw new CommandError(\n 'XCODE_BUILD',\n `Unexpected: Generated app at path \"${filePath}\" cannot be read, the app cannot be installed. Report this and build onto a simulator.`\n );\n}\n\nexport function extractEnvVariableFromBuild(buildOutput: string, variableName: string) {\n // Xcode can sometimes escape `=` with a backslash or put the value in quotes\n const reg = new RegExp(`export ${variableName}\\\\\\\\?=(.*)$`, 'mg');\n const matched = [...buildOutput.matchAll(reg)];\n\n if (!matched || !matched.length) {\n throw new CommandError(\n 'XCODE_BUILD',\n `Malformed xcodebuild results: \"${variableName}\" variable was not generated in build output. Report this issue and run your project with Xcode instead.`\n );\n }\n return matched.map((value) => value[1]).filter(Boolean) as string[];\n}\n\nexport function getProcessOptions({\n packager,\n shouldSkipInitialBundling,\n terminal,\n port,\n eagerBundleOptions,\n}: {\n packager: boolean;\n shouldSkipInitialBundling?: boolean;\n terminal: string | undefined;\n port: number;\n eagerBundleOptions?: string;\n}): SpawnOptionsWithoutStdio {\n const SKIP_BUNDLING = shouldSkipInitialBundling ? '1' : undefined;\n if (packager) {\n return {\n env: {\n ...process.env,\n RCT_TERMINAL: terminal,\n SKIP_BUNDLING,\n RCT_METRO_PORT: port.toString(),\n __EXPO_EAGER_BUNDLE_OPTIONS: eagerBundleOptions,\n },\n };\n }\n\n return {\n env: {\n ...process.env,\n RCT_TERMINAL: terminal,\n SKIP_BUNDLING,\n __EXPO_EAGER_BUNDLE_OPTIONS: eagerBundleOptions,\n // Always skip launching the packager from a build script.\n // The script is used for people building their project directly from Xcode.\n // This essentially means \"› Running script 'Start Packager'\" does nothing.\n RCT_NO_LAUNCH_PACKAGER: 'true',\n // FORCE_BUNDLING: '0'\n },\n };\n}\n\nexport async function getXcodeBuildArgsAsync(\n props: Pick<\n BuildProps,\n | 'buildCache'\n | 'projectRoot'\n | 'xcodeProject'\n | 'configuration'\n | 'scheme'\n | 'device'\n | 'isSimulator'\n >\n): Promise<string[]> {\n const args = [\n props.xcodeProject.isWorkspace ? '-workspace' : '-project',\n props.xcodeProject.name,\n '-configuration',\n props.configuration,\n '-scheme',\n props.scheme,\n '-destination',\n `id=${props.device.udid}`,\n ];\n\n if (!props.isSimulator || simulatorBuildRequiresCodeSigning(props.projectRoot)) {\n const developmentTeamId = await ensureDeviceIsCodeSignedForDeploymentAsync(props.projectRoot);\n if (developmentTeamId) {\n args.push(\n `DEVELOPMENT_TEAM=${developmentTeamId}`,\n '-allowProvisioningUpdates',\n '-allowProvisioningDeviceRegistration'\n );\n }\n }\n\n // Add last\n if (props.buildCache === false) {\n args.push(\n // Will first clean the derived data folder.\n 'clean',\n // Then build step must be added otherwise the process will simply clean and exit.\n 'build'\n );\n }\n return args;\n}\n\nfunction spawnXcodeBuild(\n args: string[],\n options: SpawnOptionsWithoutStdio,\n { onData }: { onData: (data: string) => void }\n): Promise<{ code: number | null; results: string; error: string }> {\n const buildProcess = spawn('xcodebuild', args, options);\n\n let results = '';\n let error = '';\n\n buildProcess.stdout.on('data', (data: Buffer) => {\n const stringData = data.toString();\n results += stringData;\n onData(stringData);\n });\n\n buildProcess.stderr.on('data', (data: Buffer) => {\n const stringData = data instanceof Buffer ? data.toString() : data;\n error += stringData;\n });\n\n return new Promise(async (resolve, reject) => {\n buildProcess.on('close', (code: number) => {\n resolve({ code, results, error });\n });\n });\n}\n\nasync function spawnXcodeBuildWithFlush(\n args: string[],\n options: SpawnOptionsWithoutStdio,\n { onFlush }: { onFlush: (data: string) => void }\n): Promise<{ code: number | null; results: string; error: string }> {\n let currentBuffer = '';\n\n // Data can be sent in chunks that would have no relevance to our regex\n // this can cause massive slowdowns, so we need to ensure the data is complete before attempting to parse it.\n function flushBuffer() {\n if (!currentBuffer) {\n return;\n }\n\n const data = currentBuffer;\n // Reset buffer.\n currentBuffer = '';\n // Process data.\n onFlush(data);\n }\n\n const data = await spawnXcodeBuild(args, options, {\n onData(stringData) {\n currentBuffer += stringData;\n // Only flush the data if we have a full line.\n if (currentBuffer.endsWith(os.EOL)) {\n flushBuffer();\n }\n },\n });\n\n // Flush log data at the end just in case we missed something.\n flushBuffer();\n return data;\n}\n\nasync function spawnXcodeBuildWithFormat(\n args: string[],\n options: SpawnOptionsWithoutStdio,\n { projectRoot, xcodeProject }: { projectRoot: string; xcodeProject: ProjectInfo }\n): Promise<{ code: number | null; results: string; error: string; formatter: ExpoRunFormatter }> {\n Log.debug(` xcodebuild ${args.join(' ')}`);\n\n logPrettyItem(chalk.bold`Planning build`);\n\n const formatter = ExpoRunFormatter.create(projectRoot, {\n xcodeProject,\n isDebug: env.EXPO_DEBUG,\n });\n\n const results = await spawnXcodeBuildWithFlush(args, options, {\n onFlush(data) {\n // Process data.\n for (const line of formatter.pipe(data)) {\n // Log parsed results.\n Log.log(line);\n }\n },\n });\n\n Log.debug(`Exited with code: ${results.code}`);\n\n if (\n // User cancelled with ctrl-c\n results.code === null ||\n // Build interrupted\n results.code === 75\n ) {\n throw new AbortCommandError();\n }\n\n Log.log(formatter.getBuildSummary());\n\n return { ...results, formatter };\n}\n\nexport async function buildAsync(props: BuildProps): Promise<string> {\n const args = await getXcodeBuildArgsAsync(props);\n\n const { projectRoot, xcodeProject, shouldSkipInitialBundling, port, eagerBundleOptions } = props;\n\n const { code, results, formatter, error } = await spawnXcodeBuildWithFormat(\n args,\n getProcessOptions({\n packager: false,\n terminal: getUserTerminal(),\n shouldSkipInitialBundling,\n port,\n eagerBundleOptions,\n }),\n {\n projectRoot,\n xcodeProject,\n }\n );\n\n const logFilePath = writeBuildLogs(projectRoot, results, error);\n\n if (code !== 0) {\n // Determine if the logger found any errors;\n const wasErrorPresented = !!formatter.errors.length;\n\n if (wasErrorPresented) {\n // This has a flaw, if the user is missing a file, and there is a script error, only the missing file error will be shown.\n // They will only see the script error if they fix the missing file and rerun.\n // The flaw can be fixed by catching script errors in the custom logger.\n throw new CommandError(\n `Failed to build iOS project. \"xcodebuild\" exited with error code ${code}.`\n );\n }\n\n _assertXcodeBuildResults(code, results, error, xcodeProject, logFilePath);\n }\n return results;\n}\n\n// Exposed for testing.\nexport function _assertXcodeBuildResults(\n code: number | null,\n results: string,\n error: string,\n xcodeProject: { name: string },\n logFilePath: string\n): void {\n const errorTitle = `Failed to build iOS project. \"xcodebuild\" exited with error code ${code}.`;\n\n const throwWithMessage = (message: string): never => {\n throw new CommandError(\n `${errorTitle}\\nTo view more error logs, try building the app with Xcode directly, by opening ${xcodeProject.name}.\\n\\n` +\n message +\n `Build logs written to ${chalk.underline(logFilePath)}`\n );\n };\n\n const localizedError = error.match(/NSLocalizedFailure = \"(.*)\"/)?.[1];\n\n if (localizedError) {\n throwWithMessage(chalk.bold(localizedError) + '\\n\\n');\n }\n\n // `@expo/xcpretty` can leave a real failure uncounted (its compile-error\n // matching is stateful and anchored, and stderr never passes through it), so\n // the build summary reads \"0 error(s)\" and in CI the full log below is\n // truncated before the real error line.\n const errorLines = _extractXcodeBuildErrorLines(results + '\\n' + error);\n if (errorLines.length) {\n throwWithMessage(chalk.red(errorLines.join('\\n')) + '\\n\\n' + results + '\\n\\n' + error);\n }\n\n // Show all the log info because often times the error is coming from a shell script,\n // that invoked a node script, that started metro, which threw an error.\n\n throwWithMessage(results + '\\n\\n' + error);\n}\n\n// Exposed for testing.\nexport function _extractXcodeBuildErrorLines(output: string): string[] {\n const seen = new Set<string>();\n const errors: string[] = [];\n for (const raw of output.split(/\\r?\\n/)) {\n const line = raw.trim();\n if (/(?:^|\\s)error:\\s/.test(line) && !seen.has(line)) {\n seen.add(line);\n errors.push(line);\n }\n }\n return errors;\n}\n\nfunction writeBuildLogs(projectRoot: string, buildOutput: string, errorOutput: string) {\n const [logFilePath, errorFilePath] = getErrorLogFilePath(projectRoot);\n\n fs.writeFileSync(logFilePath, buildOutput);\n fs.writeFileSync(errorFilePath, errorOutput);\n return logFilePath;\n}\n\nfunction getErrorLogFilePath(projectRoot: string): [string, string] {\n const folder = path.join(projectRoot, '.expo');\n ensureDirectory(folder);\n return [path.join(folder, 'xcodebuild.log'), path.join(folder, 'xcodebuild-error.log')];\n}\n"],"names":["_assertXcodeBuildResults","_extractXcodeBuildErrorLines","buildAsync","extractEnvVariableFromBuild","getAppBinaryPath","getEscapedPath","getProcessOptions","getXcodeBuildArgsAsync","logPrettyItem","matchEstimatedBinaryPath","message","Log","log","chalk","buildOutput","appBinaryPathMatch","match","length","CommandError","shortestPath","filter","a","sort","b","trim","debug","CONFIGURATION_BUILD_DIR","UNLOCALIZED_RESOURCES_FOLDER_PATH","binaryPath","path","join","error","code","possiblePath","filePath","fs","existsSync","unescapedPath","split","variableName","reg","RegExp","matched","matchAll","map","value","Boolean","packager","shouldSkipInitialBundling","terminal","port","eagerBundleOptions","SKIP_BUNDLING","undefined","env","process","RCT_TERMINAL","RCT_METRO_PORT","toString","__EXPO_EAGER_BUNDLE_OPTIONS","RCT_NO_LAUNCH_PACKAGER","props","args","xcodeProject","isWorkspace","name","configuration","scheme","device","udid","isSimulator","simulatorBuildRequiresCodeSigning","projectRoot","developmentTeamId","ensureDeviceIsCodeSignedForDeploymentAsync","push","buildCache","spawnXcodeBuild","options","onData","buildProcess","spawn","results","stdout","on","data","stringData","stderr","Buffer","Promise","resolve","reject","spawnXcodeBuildWithFlush","onFlush","currentBuffer","flushBuffer","endsWith","os","EOL","spawnXcodeBuildWithFormat","bold","formatter","ExpoRunFormatter","create","isDebug","EXPO_DEBUG","line","pipe","AbortCommandError","getBuildSummary","getUserTerminal","logFilePath","writeBuildLogs","wasErrorPresented","errors","errorTitle","throwWithMessage","underline","localizedError","errorLines","red","output","seen","Set","raw","test","has","add","errorOutput","errorFilePath","getErrorLogFilePath","writeFileSync","folder","ensureDirectory"],"mappings":"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"}
|
|
@@ -165,7 +165,10 @@ async function getConnectedDevicesAsync() {
|
|
|
165
165
|
deviceType: 'device',
|
|
166
166
|
connectionType: device.Properties.ConnectionType,
|
|
167
167
|
udid: device.Properties.SerialNumber,
|
|
168
|
-
|
|
168
|
+
// Lockdownd returns a reduced property set (without `DeviceClass`) for devices
|
|
169
|
+
// connected over the network ("Connect via network" on iOS <= 16), so fall back
|
|
170
|
+
// to `ProductName` ("iPhone OS") to avoid dropping wireless devices from the list.
|
|
171
|
+
osType: coerceUsbmuxdPlatformToOsType(deviceValues.DeviceClass ?? deviceValues.ProductName)
|
|
169
172
|
};
|
|
170
173
|
}));
|
|
171
174
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../src/run/ios/appleDevice/AppleDevice.ts"],"sourcesContent":["import Debug from 'debug';\nimport fs from 'fs';\nimport path from 'path';\n\nimport { ClientManager } from './ClientManager';\nimport { IPLookupResult, OnInstallProgressCallback } from './client/InstallationProxyClient';\nimport { LockdowndClient } from './client/LockdowndClient';\nimport { UsbmuxdClient } from './client/UsbmuxdClient';\nimport { AFC_STATUS, AFCError } from './protocol/AFCProtocol';\nimport { Log } from '../../../log';\nimport { XcodeDeveloperDiskImagePrerequisite } from '../../../start/doctor/apple/XcodeDeveloperDiskImagePrerequisite';\nimport * as devicectl from '../../../start/platforms/ios/devicectl';\nimport { launchAppWithDeviceCtl } from '../../../start/platforms/ios/devicectl';\nimport { OSType } from '../../../start/platforms/ios/simctl';\nimport { uniqBy } from '../../../utils/array';\nimport { delayAsync } from '../../../utils/delay';\nimport { CommandError } from '../../../utils/errors';\nimport { installExitHooks } from '../../../utils/exit';\nimport { profile } from '../../../utils/profile';\n\nconst debug = Debug('expo:apple-device');\n\n// NOTE(EvanBacon): I have a feeling this shape will change with new iOS versions (tested against iOS 15).\nexport interface ConnectedDevice {\n /** @example `00008101-001964A22629003A` */\n udid: string;\n /** @example `Evan's phone` */\n name: string;\n /** @example `iPhone13,4` */\n model: string;\n /** @example `device` */\n deviceType: 'device' | 'catalyst';\n /** @example `USB` */\n connectionType: 'USB' | 'Network';\n /** @example `15.4.1` */\n osVersion: string;\n\n osType: OSType;\n}\n\nasync function getConnectedDevicesUsingNativeToolsAsync(): Promise<ConnectedDevice[]> {\n return (\n (await devicectl.getConnectedAppleDevicesAsync())\n // Filter out unpaired and unavailable devices.\n // TODO: We could improve this logic in the future to attempt pairing if specified.\n .filter(\n (device) =>\n device.connectionProperties.pairingState === 'paired' &&\n device.connectionProperties.tunnelState !== 'unavailable'\n )\n .map((device) => {\n return {\n name: device.deviceProperties.name,\n model: device.hardwareProperties.productType,\n osVersion: device.deviceProperties.osVersionNumber,\n udid: device.hardwareProperties.udid,\n deviceType: 'device',\n connectionType:\n device.connectionProperties.transportType === 'localNetwork' ? 'Network' : 'USB',\n osType: coercePlatformToOsType(device.hardwareProperties.platform),\n };\n })\n );\n}\n\nfunction coercePlatformToOsType(platform: string): OSType {\n // The only two devices I have to test against...\n switch (platform) {\n case 'iOS':\n return 'iOS';\n case 'xrOS':\n return 'xrOS';\n default:\n debug('Unknown devicectl platform (needs to be added to Expo CLI):', platform);\n return platform as OSType;\n }\n}\nfunction coerceUsbmuxdPlatformToOsType(platform: string): OSType {\n // The only connectable device I have to test against...\n switch (platform) {\n case 'iPhone':\n case 'iPad':\n case 'iPhone OS':\n return 'iOS';\n default:\n debug('Unknown usbmuxd platform (needs to be added to Expo CLI):', platform);\n return platform as OSType;\n }\n}\n\n/** @returns a list of connected Apple devices. */\nexport async function getConnectedDevicesAsync(): Promise<ConnectedDevice[]> {\n const devices = await Promise.all([\n // Prioritize native tools since they can provide more accurate information.\n // NOTE: xcrun is substantially slower than custom tooling. +1.5s vs 9ms.\n profile(getConnectedDevicesUsingNativeToolsAsync)(),\n profile(getConnectedDevicesUsingCustomToolingAsync)(),\n ]);\n\n return uniqBy(devices.flat(), (device) => device.udid);\n}\n\n/**\n * This supports devices that are running OS versions older than iOS 17.\n *\n * @returns a list of connected Apple devices.\n */\nasync function getConnectedDevicesUsingCustomToolingAsync(): Promise<ConnectedDevice[]> {\n const client = new UsbmuxdClient(UsbmuxdClient.connectUsbmuxdSocket());\n const devices = await client.getDevices();\n client.socket.end();\n\n return Promise.all(\n devices.map(async (device): Promise<ConnectedDevice> => {\n const socket = await new UsbmuxdClient(UsbmuxdClient.connectUsbmuxdSocket()).connect(\n device,\n 62078\n );\n const deviceValues = await new LockdowndClient(socket).getAllValues();\n socket.end();\n // TODO(EvanBacon): Add support for osType (ipad, watchos, etc)\n return {\n // TODO(EvanBacon): Better name\n name: deviceValues.DeviceName ?? deviceValues.ProductType ?? 'unknown iOS device',\n model: deviceValues.ProductType,\n osVersion: deviceValues.ProductVersion,\n deviceType: 'device',\n connectionType: device.Properties.ConnectionType,\n udid: device.Properties.SerialNumber,\n osType: coerceUsbmuxdPlatformToOsType(deviceValues.DeviceClass),\n };\n })\n );\n}\n\n/** Install and run an Apple app binary on a connected Apple device. */\nexport async function runOnDevice({\n udid,\n appPath,\n bundleId,\n waitForApp,\n deltaPath,\n onProgress,\n}: {\n /** Apple device UDID */\n udid: string;\n /** File path to the app binary (ipa) */\n appPath: string;\n /** Bundle identifier for the app at `appPath` */\n bundleId: string;\n /** Wait for the app to launch before returning */\n waitForApp: boolean;\n /** File path to the app deltas folder to use for faster subsequent installs */\n deltaPath: string;\n /** Callback to be called with progress updates */\n onProgress: OnInstallProgressCallback;\n}) {\n debug('Running on device:', { udid, appPath, bundleId, waitForApp, deltaPath });\n\n const clientManager = await ClientManager.create(udid);\n\n try {\n await mountDeveloperDiskImage(clientManager);\n\n const packageName = path.basename(appPath);\n const destPackagePath = path.join('PublicStaging', packageName);\n\n await uploadApp(clientManager, { appBinaryPath: appPath, destinationPath: destPackagePath });\n\n const installer = await clientManager.getInstallationProxyClient();\n await installer.installApp(\n destPackagePath,\n bundleId,\n {\n // https://github.com/ios-control/ios-deploy/blob/0f2ffb1e564aa67a2dfca7cdf13de47ce489d835/src/ios-deploy/ios-deploy.m#L2491-L2508\n ApplicationsType: 'Any',\n\n CFBundleIdentifier: bundleId,\n CloseOnInvalidate: '1',\n InvalidateOnDetach: '1',\n IsUserInitiated: '1',\n // Disable checking for wifi devices, this is nominally faster.\n PreferWifi: '0',\n // Only info I could find on these:\n // https://github.com/wwxxyx/Quectel_BG96/blob/310876f90fc1093a59e45e381160eddcc31697d0/Apple_Homekit/homekit_certification_tools/ATS%206/ATS%206/ATS.app/Contents/Frameworks/CaptureKit.framework/Versions/A/Resources/MobileDevice/MobileInstallation.h#L112-L121\n PackageType: 'Developer',\n ShadowParentKey: deltaPath,\n // SkipUninstall: '1'\n },\n onProgress\n );\n\n const {\n // TODO(EvanBacon): This can be undefined when querying App Clips.\n [bundleId]: appInfo,\n } = await installer.lookupApp([bundleId]);\n\n if (appInfo) {\n // launch fails with EBusy or ENotFound if you try to launch immediately after install\n await delayAsync(200);\n const debugServerClient = await launchApp(clientManager, {\n bundleId,\n appInfo,\n detach: !waitForApp,\n });\n\n if (waitForApp && debugServerClient) {\n installExitHooks(async () => {\n // causes continue() to return\n debugServerClient.halt();\n // give continue() time to return response\n await delayAsync(64);\n });\n\n debug(`Waiting for app to close...\\n`);\n const result = await debugServerClient.continue();\n // TODO: I have no idea what this packet means yet (successful close?)\n // if not a close (ie, most likely due to halt from onBeforeExit), then kill the app\n if (result !== 'W00') {\n await debugServerClient.kill();\n }\n }\n } else {\n Log.warn(`App \"${bundleId}\" installed but couldn't be launched. Open on device manually.`);\n }\n } finally {\n clientManager.end();\n }\n}\n\n/** Mount the developer disk image for Xcode. */\nasync function mountDeveloperDiskImage(clientManager: ClientManager) {\n const imageMounter = await clientManager.getMobileImageMounterClient();\n // Check if already mounted. If not, mount.\n if (!(await imageMounter.lookupImage()).ImageSignature) {\n // verify DeveloperDiskImage exists (TODO: how does this work on Windows/Linux?)\n // TODO: if windows/linux, download?\n const version = await (await clientManager.getLockdowndClient()).getValue('ProductVersion');\n const developerDiskImagePath = await XcodeDeveloperDiskImagePrerequisite.instance.assertAsync({\n version,\n });\n const developerDiskImageSig = fs.readFileSync(`${developerDiskImagePath}.signature`);\n await imageMounter.uploadImage(developerDiskImagePath, developerDiskImageSig);\n await imageMounter.mountImage(developerDiskImagePath, developerDiskImageSig);\n }\n}\n\nasync function uploadApp(\n clientManager: ClientManager,\n { appBinaryPath, destinationPath }: { appBinaryPath: string; destinationPath: string }\n) {\n const afcClient = await clientManager.getAFCClient();\n try {\n await afcClient.getFileInfo('PublicStaging');\n } catch (err: any) {\n if (err instanceof AFCError && err.status === AFC_STATUS.OBJECT_NOT_FOUND) {\n await afcClient.makeDirectory('PublicStaging');\n } else {\n throw err;\n }\n }\n await afcClient.uploadDirectory(appBinaryPath, destinationPath);\n}\n\nasync function launchAppWithUsbmux(\n clientManager: ClientManager,\n { appInfo, detach }: { appInfo: IPLookupResult[string]; detach?: boolean }\n) {\n let tries = 0;\n while (tries < 3) {\n const debugServerClient = await clientManager.getDebugserverClient();\n await debugServerClient.setMaxPacketSize(1024);\n await debugServerClient.setWorkingDir(appInfo.Container);\n await debugServerClient.launchApp(appInfo.Path, appInfo.CFBundleExecutable);\n\n const result = await debugServerClient.checkLaunchSuccess();\n if (result === 'OK') {\n if (detach) {\n // https://github.com/libimobiledevice/libimobiledevice/blob/25059d4c7d75e03aab516af2929d7c6e6d4c17de/tools/idevicedebug.c#L455-L464\n const res = await debugServerClient.sendCommand('D', []);\n debug('Disconnect from debug server request:', res);\n if (res !== 'OK') {\n console.warn(\n 'Something went wrong while attempting to disconnect from iOS debug server, you may need to reopen the app manually.'\n );\n }\n }\n\n return debugServerClient;\n } else if (result === 'EBusy' || result === 'ENotFound') {\n debug('Device busy or app not found, trying to launch again in .5s...');\n tries++;\n debugServerClient.socket.end();\n await delayAsync(500);\n } else {\n throw new CommandError(`There was an error launching app: ${result}`);\n }\n }\n throw new CommandError('Unable to launch app, number of tries exceeded');\n}\n\n/**\n * iOS 17 introduces a new protocol called RemoteXPC.\n * This is not yet implemented, so we fallback to devicectl.\n *\n * @see https://github.com/doronz88/pymobiledevice3/blob/master/misc/RemoteXPC.md#process-remoted\n */\nasync function launchApp(\n clientManager: ClientManager,\n {\n bundleId,\n appInfo,\n detach,\n }: { bundleId: string; appInfo: IPLookupResult[string]; detach?: boolean }\n) {\n try {\n return await launchAppWithUsbmux(clientManager, { appInfo, detach });\n } catch (error) {\n debug('Failed to launch app with Usbmuxd, falling back to xcrun...', error);\n\n // Get the device UDID and close the connection, to allow `xcrun devicectl` to connect\n const deviceId = clientManager.device.Properties.SerialNumber;\n clientManager.end();\n\n // Fallback to devicectl for iOS 17 support\n return await launchAppWithDeviceCtl(deviceId, bundleId);\n }\n}\n"],"names":["getConnectedDevicesAsync","runOnDevice","debug","Debug","getConnectedDevicesUsingNativeToolsAsync","devicectl","getConnectedAppleDevicesAsync","filter","device","connectionProperties","pairingState","tunnelState","map","name","deviceProperties","model","hardwareProperties","productType","osVersion","osVersionNumber","udid","deviceType","connectionType","transportType","osType","coercePlatformToOsType","platform","coerceUsbmuxdPlatformToOsType","devices","Promise","all","profile","getConnectedDevicesUsingCustomToolingAsync","uniqBy","flat","client","UsbmuxdClient","connectUsbmuxdSocket","getDevices","socket","end","connect","deviceValues","LockdowndClient","getAllValues","DeviceName","ProductType","ProductVersion","Properties","ConnectionType","SerialNumber","DeviceClass","appPath","bundleId","waitForApp","deltaPath","onProgress","clientManager","ClientManager","create","mountDeveloperDiskImage","packageName","path","basename","destPackagePath","join","uploadApp","appBinaryPath","destinationPath","installer","getInstallationProxyClient","installApp","ApplicationsType","CFBundleIdentifier","CloseOnInvalidate","InvalidateOnDetach","IsUserInitiated","PreferWifi","PackageType","ShadowParentKey","appInfo","lookupApp","delayAsync","debugServerClient","launchApp","detach","installExitHooks","halt","result","continue","kill","Log","warn","imageMounter","getMobileImageMounterClient","lookupImage","ImageSignature","version","getLockdowndClient","getValue","developerDiskImagePath","XcodeDeveloperDiskImagePrerequisite","instance","assertAsync","developerDiskImageSig","fs","readFileSync","uploadImage","mountImage","afcClient","getAFCClient","getFileInfo","err","AFCError","status","AFC_STATUS","OBJECT_NOT_FOUND","makeDirectory","uploadDirectory","launchAppWithUsbmux","tries","getDebugserverClient","setMaxPacketSize","setWorkingDir","Container","Path","CFBundleExecutable","checkLaunchSuccess","res","sendCommand","console","CommandError","error","deviceId","launchAppWithDeviceCtl"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../src/run/ios/appleDevice/AppleDevice.ts"],"sourcesContent":["import Debug from 'debug';\nimport fs from 'fs';\nimport path from 'path';\n\nimport { ClientManager } from './ClientManager';\nimport { IPLookupResult, OnInstallProgressCallback } from './client/InstallationProxyClient';\nimport { LockdowndClient } from './client/LockdowndClient';\nimport { UsbmuxdClient } from './client/UsbmuxdClient';\nimport { AFC_STATUS, AFCError } from './protocol/AFCProtocol';\nimport { Log } from '../../../log';\nimport { XcodeDeveloperDiskImagePrerequisite } from '../../../start/doctor/apple/XcodeDeveloperDiskImagePrerequisite';\nimport * as devicectl from '../../../start/platforms/ios/devicectl';\nimport { launchAppWithDeviceCtl } from '../../../start/platforms/ios/devicectl';\nimport { OSType } from '../../../start/platforms/ios/simctl';\nimport { uniqBy } from '../../../utils/array';\nimport { delayAsync } from '../../../utils/delay';\nimport { CommandError } from '../../../utils/errors';\nimport { installExitHooks } from '../../../utils/exit';\nimport { profile } from '../../../utils/profile';\n\nconst debug = Debug('expo:apple-device');\n\n// NOTE(EvanBacon): I have a feeling this shape will change with new iOS versions (tested against iOS 15).\nexport interface ConnectedDevice {\n /** @example `00008101-001964A22629003A` */\n udid: string;\n /** @example `Evan's phone` */\n name: string;\n /** @example `iPhone13,4` */\n model: string;\n /** @example `device` */\n deviceType: 'device' | 'catalyst';\n /** @example `USB` */\n connectionType: 'USB' | 'Network';\n /** @example `15.4.1` */\n osVersion: string;\n\n osType: OSType;\n}\n\nasync function getConnectedDevicesUsingNativeToolsAsync(): Promise<ConnectedDevice[]> {\n return (\n (await devicectl.getConnectedAppleDevicesAsync())\n // Filter out unpaired and unavailable devices.\n // TODO: We could improve this logic in the future to attempt pairing if specified.\n .filter(\n (device) =>\n device.connectionProperties.pairingState === 'paired' &&\n device.connectionProperties.tunnelState !== 'unavailable'\n )\n .map((device) => {\n return {\n name: device.deviceProperties.name,\n model: device.hardwareProperties.productType,\n osVersion: device.deviceProperties.osVersionNumber,\n udid: device.hardwareProperties.udid,\n deviceType: 'device',\n connectionType:\n device.connectionProperties.transportType === 'localNetwork' ? 'Network' : 'USB',\n osType: coercePlatformToOsType(device.hardwareProperties.platform),\n };\n })\n );\n}\n\nfunction coercePlatformToOsType(platform: string): OSType {\n // The only two devices I have to test against...\n switch (platform) {\n case 'iOS':\n return 'iOS';\n case 'xrOS':\n return 'xrOS';\n default:\n debug('Unknown devicectl platform (needs to be added to Expo CLI):', platform);\n return platform as OSType;\n }\n}\nfunction coerceUsbmuxdPlatformToOsType(platform: string): OSType {\n // The only connectable device I have to test against...\n switch (platform) {\n case 'iPhone':\n case 'iPad':\n case 'iPhone OS':\n return 'iOS';\n default:\n debug('Unknown usbmuxd platform (needs to be added to Expo CLI):', platform);\n return platform as OSType;\n }\n}\n\n/** @returns a list of connected Apple devices. */\nexport async function getConnectedDevicesAsync(): Promise<ConnectedDevice[]> {\n const devices = await Promise.all([\n // Prioritize native tools since they can provide more accurate information.\n // NOTE: xcrun is substantially slower than custom tooling. +1.5s vs 9ms.\n profile(getConnectedDevicesUsingNativeToolsAsync)(),\n profile(getConnectedDevicesUsingCustomToolingAsync)(),\n ]);\n\n return uniqBy(devices.flat(), (device) => device.udid);\n}\n\n/**\n * This supports devices that are running OS versions older than iOS 17.\n *\n * @returns a list of connected Apple devices.\n */\nasync function getConnectedDevicesUsingCustomToolingAsync(): Promise<ConnectedDevice[]> {\n const client = new UsbmuxdClient(UsbmuxdClient.connectUsbmuxdSocket());\n const devices = await client.getDevices();\n client.socket.end();\n\n return Promise.all(\n devices.map(async (device): Promise<ConnectedDevice> => {\n const socket = await new UsbmuxdClient(UsbmuxdClient.connectUsbmuxdSocket()).connect(\n device,\n 62078\n );\n const deviceValues = await new LockdowndClient(socket).getAllValues();\n socket.end();\n // TODO(EvanBacon): Add support for osType (ipad, watchos, etc)\n return {\n // TODO(EvanBacon): Better name\n name: deviceValues.DeviceName ?? deviceValues.ProductType ?? 'unknown iOS device',\n model: deviceValues.ProductType,\n osVersion: deviceValues.ProductVersion,\n deviceType: 'device',\n connectionType: device.Properties.ConnectionType,\n udid: device.Properties.SerialNumber,\n // Lockdownd returns a reduced property set (without `DeviceClass`) for devices\n // connected over the network (\"Connect via network\" on iOS <= 16), so fall back\n // to `ProductName` (\"iPhone OS\") to avoid dropping wireless devices from the list.\n osType: coerceUsbmuxdPlatformToOsType(deviceValues.DeviceClass ?? deviceValues.ProductName),\n };\n })\n );\n}\n\n/** Install and run an Apple app binary on a connected Apple device. */\nexport async function runOnDevice({\n udid,\n appPath,\n bundleId,\n waitForApp,\n deltaPath,\n onProgress,\n}: {\n /** Apple device UDID */\n udid: string;\n /** File path to the app binary (ipa) */\n appPath: string;\n /** Bundle identifier for the app at `appPath` */\n bundleId: string;\n /** Wait for the app to launch before returning */\n waitForApp: boolean;\n /** File path to the app deltas folder to use for faster subsequent installs */\n deltaPath: string;\n /** Callback to be called with progress updates */\n onProgress: OnInstallProgressCallback;\n}) {\n debug('Running on device:', { udid, appPath, bundleId, waitForApp, deltaPath });\n\n const clientManager = await ClientManager.create(udid);\n\n try {\n await mountDeveloperDiskImage(clientManager);\n\n const packageName = path.basename(appPath);\n const destPackagePath = path.join('PublicStaging', packageName);\n\n await uploadApp(clientManager, { appBinaryPath: appPath, destinationPath: destPackagePath });\n\n const installer = await clientManager.getInstallationProxyClient();\n await installer.installApp(\n destPackagePath,\n bundleId,\n {\n // https://github.com/ios-control/ios-deploy/blob/0f2ffb1e564aa67a2dfca7cdf13de47ce489d835/src/ios-deploy/ios-deploy.m#L2491-L2508\n ApplicationsType: 'Any',\n\n CFBundleIdentifier: bundleId,\n CloseOnInvalidate: '1',\n InvalidateOnDetach: '1',\n IsUserInitiated: '1',\n // Disable checking for wifi devices, this is nominally faster.\n PreferWifi: '0',\n // Only info I could find on these:\n // https://github.com/wwxxyx/Quectel_BG96/blob/310876f90fc1093a59e45e381160eddcc31697d0/Apple_Homekit/homekit_certification_tools/ATS%206/ATS%206/ATS.app/Contents/Frameworks/CaptureKit.framework/Versions/A/Resources/MobileDevice/MobileInstallation.h#L112-L121\n PackageType: 'Developer',\n ShadowParentKey: deltaPath,\n // SkipUninstall: '1'\n },\n onProgress\n );\n\n const {\n // TODO(EvanBacon): This can be undefined when querying App Clips.\n [bundleId]: appInfo,\n } = await installer.lookupApp([bundleId]);\n\n if (appInfo) {\n // launch fails with EBusy or ENotFound if you try to launch immediately after install\n await delayAsync(200);\n const debugServerClient = await launchApp(clientManager, {\n bundleId,\n appInfo,\n detach: !waitForApp,\n });\n\n if (waitForApp && debugServerClient) {\n installExitHooks(async () => {\n // causes continue() to return\n debugServerClient.halt();\n // give continue() time to return response\n await delayAsync(64);\n });\n\n debug(`Waiting for app to close...\\n`);\n const result = await debugServerClient.continue();\n // TODO: I have no idea what this packet means yet (successful close?)\n // if not a close (ie, most likely due to halt from onBeforeExit), then kill the app\n if (result !== 'W00') {\n await debugServerClient.kill();\n }\n }\n } else {\n Log.warn(`App \"${bundleId}\" installed but couldn't be launched. Open on device manually.`);\n }\n } finally {\n clientManager.end();\n }\n}\n\n/** Mount the developer disk image for Xcode. */\nasync function mountDeveloperDiskImage(clientManager: ClientManager) {\n const imageMounter = await clientManager.getMobileImageMounterClient();\n // Check if already mounted. If not, mount.\n if (!(await imageMounter.lookupImage()).ImageSignature) {\n // verify DeveloperDiskImage exists (TODO: how does this work on Windows/Linux?)\n // TODO: if windows/linux, download?\n const version = await (await clientManager.getLockdowndClient()).getValue('ProductVersion');\n const developerDiskImagePath = await XcodeDeveloperDiskImagePrerequisite.instance.assertAsync({\n version,\n });\n const developerDiskImageSig = fs.readFileSync(`${developerDiskImagePath}.signature`);\n await imageMounter.uploadImage(developerDiskImagePath, developerDiskImageSig);\n await imageMounter.mountImage(developerDiskImagePath, developerDiskImageSig);\n }\n}\n\nasync function uploadApp(\n clientManager: ClientManager,\n { appBinaryPath, destinationPath }: { appBinaryPath: string; destinationPath: string }\n) {\n const afcClient = await clientManager.getAFCClient();\n try {\n await afcClient.getFileInfo('PublicStaging');\n } catch (err: any) {\n if (err instanceof AFCError && err.status === AFC_STATUS.OBJECT_NOT_FOUND) {\n await afcClient.makeDirectory('PublicStaging');\n } else {\n throw err;\n }\n }\n await afcClient.uploadDirectory(appBinaryPath, destinationPath);\n}\n\nasync function launchAppWithUsbmux(\n clientManager: ClientManager,\n { appInfo, detach }: { appInfo: IPLookupResult[string]; detach?: boolean }\n) {\n let tries = 0;\n while (tries < 3) {\n const debugServerClient = await clientManager.getDebugserverClient();\n await debugServerClient.setMaxPacketSize(1024);\n await debugServerClient.setWorkingDir(appInfo.Container);\n await debugServerClient.launchApp(appInfo.Path, appInfo.CFBundleExecutable);\n\n const result = await debugServerClient.checkLaunchSuccess();\n if (result === 'OK') {\n if (detach) {\n // https://github.com/libimobiledevice/libimobiledevice/blob/25059d4c7d75e03aab516af2929d7c6e6d4c17de/tools/idevicedebug.c#L455-L464\n const res = await debugServerClient.sendCommand('D', []);\n debug('Disconnect from debug server request:', res);\n if (res !== 'OK') {\n console.warn(\n 'Something went wrong while attempting to disconnect from iOS debug server, you may need to reopen the app manually.'\n );\n }\n }\n\n return debugServerClient;\n } else if (result === 'EBusy' || result === 'ENotFound') {\n debug('Device busy or app not found, trying to launch again in .5s...');\n tries++;\n debugServerClient.socket.end();\n await delayAsync(500);\n } else {\n throw new CommandError(`There was an error launching app: ${result}`);\n }\n }\n throw new CommandError('Unable to launch app, number of tries exceeded');\n}\n\n/**\n * iOS 17 introduces a new protocol called RemoteXPC.\n * This is not yet implemented, so we fallback to devicectl.\n *\n * @see https://github.com/doronz88/pymobiledevice3/blob/master/misc/RemoteXPC.md#process-remoted\n */\nasync function launchApp(\n clientManager: ClientManager,\n {\n bundleId,\n appInfo,\n detach,\n }: { bundleId: string; appInfo: IPLookupResult[string]; detach?: boolean }\n) {\n try {\n return await launchAppWithUsbmux(clientManager, { appInfo, detach });\n } catch (error) {\n debug('Failed to launch app with Usbmuxd, falling back to xcrun...', error);\n\n // Get the device UDID and close the connection, to allow `xcrun devicectl` to connect\n const deviceId = clientManager.device.Properties.SerialNumber;\n clientManager.end();\n\n // Fallback to devicectl for iOS 17 support\n return await launchAppWithDeviceCtl(deviceId, bundleId);\n }\n}\n"],"names":["getConnectedDevicesAsync","runOnDevice","debug","Debug","getConnectedDevicesUsingNativeToolsAsync","devicectl","getConnectedAppleDevicesAsync","filter","device","connectionProperties","pairingState","tunnelState","map","name","deviceProperties","model","hardwareProperties","productType","osVersion","osVersionNumber","udid","deviceType","connectionType","transportType","osType","coercePlatformToOsType","platform","coerceUsbmuxdPlatformToOsType","devices","Promise","all","profile","getConnectedDevicesUsingCustomToolingAsync","uniqBy","flat","client","UsbmuxdClient","connectUsbmuxdSocket","getDevices","socket","end","connect","deviceValues","LockdowndClient","getAllValues","DeviceName","ProductType","ProductVersion","Properties","ConnectionType","SerialNumber","DeviceClass","ProductName","appPath","bundleId","waitForApp","deltaPath","onProgress","clientManager","ClientManager","create","mountDeveloperDiskImage","packageName","path","basename","destPackagePath","join","uploadApp","appBinaryPath","destinationPath","installer","getInstallationProxyClient","installApp","ApplicationsType","CFBundleIdentifier","CloseOnInvalidate","InvalidateOnDetach","IsUserInitiated","PreferWifi","PackageType","ShadowParentKey","appInfo","lookupApp","delayAsync","debugServerClient","launchApp","detach","installExitHooks","halt","result","continue","kill","Log","warn","imageMounter","getMobileImageMounterClient","lookupImage","ImageSignature","version","getLockdowndClient","getValue","developerDiskImagePath","XcodeDeveloperDiskImagePrerequisite","instance","assertAsync","developerDiskImageSig","fs","readFileSync","uploadImage","mountImage","afcClient","getAFCClient","getFileInfo","err","AFCError","status","AFC_STATUS","OBJECT_NOT_FOUND","makeDirectory","uploadDirectory","launchAppWithUsbmux","tries","getDebugserverClient","setMaxPacketSize","setWorkingDir","Container","Path","CFBundleExecutable","checkLaunchSuccess","res","sendCommand","console","CommandError","error","deviceId","launchAppWithDeviceCtl"],"mappings":"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"}
|
|
@@ -183,11 +183,18 @@ async function getConnectedAppleDevicesAsync() {
|
|
|
183
183
|
]);
|
|
184
184
|
debug(devices.stdout);
|
|
185
185
|
const devicesJson = await _jsonfile().default.readAsync(tmpPath);
|
|
186
|
-
if (
|
|
186
|
+
if (![
|
|
187
|
+
2,
|
|
188
|
+
3,
|
|
189
|
+
5
|
|
190
|
+
].includes(devicesJson == null ? void 0 : (_devicesJson_info = devicesJson.info) == null ? void 0 : _devicesJson_info.jsonVersion)) {
|
|
187
191
|
_log.warn('Unexpected devicectl JSON version output from devicectl. Connecting to physical Apple devices may not work as expected.');
|
|
188
192
|
}
|
|
189
193
|
assertDevicesJson(devicesJson);
|
|
190
|
-
return devicesJson.result.devices
|
|
194
|
+
return devicesJson.result.devices.filter((device)=>{
|
|
195
|
+
var _device_hardwareProperties;
|
|
196
|
+
return (device == null ? void 0 : (_device_hardwareProperties = device.hardwareProperties) == null ? void 0 : _device_hardwareProperties.reality) !== 'simulated';
|
|
197
|
+
});
|
|
191
198
|
}
|
|
192
199
|
function assertDevicesJson(results) {
|
|
193
200
|
var _results_result;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../src/start/platforms/ios/devicectl.ts"],"sourcesContent":["/**\n * Copyright © 2024 650 Industries.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\nimport JsonFile from '@expo/json-file';\nimport spawnAsync, { SpawnOptions, SpawnResult } from '@expo/spawn-async';\nimport chalk from 'chalk';\nimport { spawn, execSync } from 'child_process';\nimport fs from 'fs';\nimport assert from 'node:assert';\nimport { Ora } from 'ora';\nimport { EOL } from 'os';\nimport path from 'path';\n\nimport { xcrunAsync } from './xcrun';\nimport { getExpoHomeDirectory } from '../../../api/user/UserSettings';\nimport * as Log from '../../../log';\nimport { createTempFilePath } from '../../../utils/createTempPath';\nimport { CommandError } from '../../../utils/errors';\nimport { installExitHooks } from '../../../utils/exit';\nimport { isInteractive } from '../../../utils/interactive';\nimport { ora } from '../../../utils/ora';\nimport { confirmAsync } from '../../../utils/prompts';\n\nconst DEVICE_CTL_EXISTS_PATH = path.join(getExpoHomeDirectory(), 'devicectl-exists');\n\nconst debug = require('debug')('expo:devicectl') as typeof console.log;\n\ntype AnyEnum<T extends string = string> = T | (string & object);\n\ntype DeviceCtlDevice = {\n capabilities: DeviceCtlDeviceCapability[];\n connectionProperties: DeviceCtlConnectionProperties;\n deviceProperties: DeviceCtlDeviceProperties;\n hardwareProperties: DeviceCtlHardwareProperties;\n /** \"A1A1AAA1-0011-1AA1-11A1-10A1111AA11A\" */\n identifier: string;\n visibilityClass: AnyEnum<'default'>;\n};\n\ntype DeviceCtlHardwareProperties = {\n cpuType: DeviceCtlCpuType;\n deviceType: AnyEnum<'iPhone'>;\n /** 1114404411111111 */\n ecid: number;\n /** \"D74AP\" */\n hardwareModel: string;\n /** 512000000000 */\n internalStorageCapacity: number;\n /** true */\n isProductionFused: boolean;\n /** \"iPhone 14 Pro Max\" */\n marketingName: string;\n /** \"iOS\" */\n platform: AnyEnum<'iOS' | 'xrOS'>;\n /** \"iPhone15,3\" */\n productType: AnyEnum<'iPhone13,4' | 'iPhone15,3'>;\n reality: AnyEnum<'physical'>;\n /** \"X2X1CC1XXX\" */\n serialNumber: string;\n supportedCPUTypes: DeviceCtlCpuType[];\n /** [1] */\n supportedDeviceFamilies: number[];\n thinningProductType: AnyEnum<'iPhone15,3'>;\n /** \"00001110-001111110110101A\" */\n udid: string;\n};\n\ntype DeviceCtlDeviceProperties = {\n /** true */\n bootedFromSnapshot: boolean;\n /** \"com.apple.os.update-AD0CF111ACFF11A11111A76A3D1262AE42A3F56F305AF5AE1135393A7A14A7D1\" */\n bootedSnapshotName: string;\n /** false */\n ddiServicesAvailable: boolean;\n\n developerModeStatus: AnyEnum<'enabled'>;\n /** false */\n hasInternalOSBuild: boolean;\n /** \"Evan's phone\" */\n name: string;\n /** \"21E236\" */\n osBuildUpdate: string;\n /** \"17.4.1\" */\n osVersionNumber: string;\n /** false */\n rootFileSystemIsWritable: boolean;\n};\n\ntype DeviceCtlDeviceCapability =\n | {\n name: AnyEnum;\n featureIdentifier: AnyEnum;\n }\n | {\n featureIdentifier: 'com.apple.coredevice.feature.connectdevice';\n name: 'Connect to Device';\n }\n | {\n featureIdentifier: 'com.apple.coredevice.feature.unpairdevice';\n name: 'Unpair Device';\n }\n | {\n featureIdentifier: 'com.apple.coredevice.feature.acquireusageassertion';\n name: 'Acquire Usage Assertion';\n };\n\ntype DeviceCtlConnectionProperties = {\n authenticationType: AnyEnum<'manualPairing'>;\n isMobileDeviceOnly: boolean;\n /** \"2024-04-20T22:50:04.244Z\" */\n lastConnectionDate: string;\n pairingState: AnyEnum<'paired'>;\n /** [\"00001111-001111110110101A.coredevice.local\", \"A1A1AAA1-0011-1AA1-11A1-10A1111AA11A.coredevice.local\"] */\n potentialHostnames: string[];\n transportType: AnyEnum<'localNetwork' | 'wired'>;\n tunnelState: AnyEnum<'disconnected' | 'unavailable'>;\n tunnelTransportProtocol: AnyEnum<'tcp'>;\n};\n\ntype DeviceCtlCpuType = {\n name: AnyEnum<'arm64e' | 'arm64' | 'arm64_32'>;\n subType: number;\n /** 16777228 */\n type: number;\n};\n\n/** Run a `devicectl` command. */\nexport async function devicectlAsync(\n args: (string | undefined)[],\n options?: SpawnOptions\n): Promise<SpawnResult> {\n try {\n return await xcrunAsync(['devicectl', ...args], options);\n } catch (error: any) {\n if (error instanceof CommandError) {\n throw error;\n }\n if ('stderr' in error) {\n const errorCodes = getDeviceCtlErrorCodes(error.stderr);\n if (errorCodes.includes('Locked')) {\n throw new CommandError('APPLE_DEVICE_LOCKED', 'Device is locked, unlock and try again.');\n }\n }\n throw error;\n }\n}\n\nexport async function getConnectedAppleDevicesAsync() {\n if (!hasDevicectlEverBeenInstalled()) {\n debug('devicectl not found, skipping remote Apple devices.');\n return [];\n }\n\n const tmpPath = createTempFilePath();\n const devices = await devicectlAsync([\n 'list',\n 'devices',\n '--json-output',\n tmpPath,\n // Give two seconds before timing out: between 5 and 9223372036854775807\n '--timeout',\n '5',\n ]);\n debug(devices.stdout);\n const devicesJson = await JsonFile.readAsync(tmpPath);\n\n if ((devicesJson as any)?.info?.jsonVersion !== 2) {\n Log.warn(\n 'Unexpected devicectl JSON version output from devicectl. Connecting to physical Apple devices may not work as expected.'\n );\n }\n\n assertDevicesJson(devicesJson);\n\n return devicesJson.result.devices as DeviceCtlDevice[];\n}\n\nfunction assertDevicesJson(\n results: any\n): asserts results is { result: { devices: DeviceCtlDevice[] } } {\n assert(\n results != null && 'result' in results && Array.isArray(results?.result?.devices),\n 'Malformed JSON output from devicectl: ' + JSON.stringify(results, null, 2)\n );\n}\n\nexport async function launchBinaryOnMacAsync(\n bundleId: string,\n appBinaryPath: string\n): Promise<void> {\n const args = ['-b', bundleId, appBinaryPath];\n try {\n await spawnAsync('open', args);\n } catch (error: any) {\n if ('code' in error) {\n if (error.code === 1) {\n throw new CommandError(\n 'MACOS_LAUNCH',\n 'Failed to launch the compatible binary on macOS: open ' +\n args.join(' ') +\n '\\n\\n' +\n error.message\n );\n }\n }\n throw error;\n }\n}\n\nasync function installAppWithDeviceCtlAsync(\n uuid: string,\n bundleIdOrAppPath: string,\n onProgress: (event: { status: string; isComplete: boolean; progress: number }) => void\n): Promise<void> {\n // 𝝠 xcrun devicectl device install app --device 00001110-001111110110101A /Users/evanbacon/Library/Developer/Xcode/DerivedData/Router-hgbqaxzhrhkiftfweydvhgttadvn/Build/Products/Debug-iphoneos/Router.app --verbose\n return new Promise((resolve, reject) => {\n const args: string[] = [\n 'devicectl',\n 'device',\n 'install',\n 'app',\n '--device',\n uuid,\n bundleIdOrAppPath,\n ];\n const childProcess = spawn('xcrun', args);\n debug('xcrun ' + args.join(' '));\n\n let currentProgress = 0;\n let hasStarted = false;\n\n function updateProgress(progress: number) {\n hasStarted = true;\n if (progress <= currentProgress) {\n return;\n }\n currentProgress = progress;\n onProgress({\n progress,\n isComplete: progress === 100,\n status: 'Installing',\n });\n }\n\n childProcess.stdout.on('data', (data: Buffer) => {\n // Sometimes more than one chunk comes at a time, here we split by system newline,\n // then trim and filter.\n const strings = data\n .toString()\n .split(EOL)\n .map((value) => value.trim());\n\n strings.forEach((str) => {\n // Match the progress percentage:\n // - '34%... 35%...' -> 34\n // - '31%...' -> 31\n // - 'Complete!' -> 100\n\n const match = str.match(/(\\d+)%\\.\\.\\./);\n if (match) {\n updateProgress(parseInt(match[1], 10));\n } else if (hasStarted) {\n updateProgress(100);\n }\n });\n\n debug('[stdout]:', strings);\n });\n\n childProcess.on('close', (code) => {\n debug('[close]: ' + code);\n if (code === 0) {\n resolve();\n } else {\n const stderr = childProcess.stderr.read();\n const err = new Error(stderr);\n (err as any).code = code;\n detach(err);\n }\n });\n\n const detach = async (err?: Error) => {\n off?.();\n if (childProcess) {\n return new Promise<void>((resolve) => {\n childProcess?.on('close', resolve);\n childProcess?.kill();\n // childProcess = null;\n reject(err ?? new CommandError('detached'));\n });\n }\n };\n\n const off = installExitHooks(() => detach());\n });\n}\n\nexport async function launchAppWithDeviceCtl(deviceId: string, bundleId: string) {\n await devicectlAsync(['device', 'process', 'launch', '--device', deviceId, bundleId]);\n}\n\n/** Find all error codes from the output log */\nfunction getDeviceCtlErrorCodes(log: string): string[] {\n return [...log.matchAll(/BSErrorCodeDescription\\s+=\\s+(.*)$/gim)].map(([_line, code]) => code);\n}\n\nlet hasEverBeenInstalled: boolean | undefined;\n\nexport function hasDevicectlEverBeenInstalled() {\n if (hasEverBeenInstalled) return hasEverBeenInstalled;\n // It doesn't appear possible for devicectl to ever be uninstalled. We can just check once and store this result forever\n // to prevent cold boots of devicectl from slowing down all invocations of `expo run ios`\n if (fs.existsSync(DEVICE_CTL_EXISTS_PATH)) {\n hasEverBeenInstalled = true;\n return true;\n }\n\n const isInstalled = isDevicectlInstalled();\n\n if (isInstalled) {\n fs.writeFileSync(DEVICE_CTL_EXISTS_PATH, '1');\n }\n hasEverBeenInstalled = isInstalled;\n return isInstalled;\n}\n\nfunction isDevicectlInstalled() {\n try {\n execSync('xcrun devicectl --version', { stdio: 'ignore' });\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Wraps the apple device method for installing and running an app,\n * adds indicator and retry loop for when the device is locked.\n */\nexport async function installAndLaunchAppAsync(props: {\n bundle: string;\n bundleIdentifier: string;\n udid: string;\n deviceName: string;\n}): Promise<void> {\n debug('Running on device:', props);\n const { bundle, bundleIdentifier, udid, deviceName } = props;\n let indicator: Ora | undefined;\n\n try {\n if (!indicator) {\n indicator = ora(`Connecting to: ${props.deviceName}`).start();\n }\n\n await installAppWithDeviceCtlAsync(\n udid,\n bundle,\n ({\n status,\n isComplete,\n progress,\n }: {\n status: string;\n isComplete: boolean;\n progress: number;\n }) => {\n if (!indicator) {\n indicator = ora(status).start();\n }\n indicator.text = `${chalk.bold(status)} ${progress}%`;\n if (isComplete) {\n indicator.succeed();\n }\n }\n );\n } catch (error: any) {\n if (indicator) {\n indicator.fail();\n }\n throw error;\n }\n\n async function launchAppOptionally() {\n try {\n await launchAppWithDeviceCtl(udid, bundleIdentifier);\n } catch (error: any) {\n if (indicator) {\n indicator.fail();\n }\n if (error.code === 'APPLE_DEVICE_LOCKED') {\n // Get the app name from the binary path.\n const appName = path.basename(bundle).split('.')[0] ?? 'app';\n if (\n isInteractive() &&\n (await confirmAsync({\n message: `Cannot launch ${appName} because the device is locked. Unlock ${deviceName} to continue...`,\n initial: true,\n }))\n ) {\n return launchAppOptionally();\n }\n throw new CommandError(\n `Cannot launch ${appName} on ${deviceName} because the device is locked.`\n );\n }\n throw error;\n }\n }\n\n await launchAppOptionally();\n}\n"],"names":["devicectlAsync","getConnectedAppleDevicesAsync","hasDevicectlEverBeenInstalled","installAndLaunchAppAsync","launchAppWithDeviceCtl","launchBinaryOnMacAsync","DEVICE_CTL_EXISTS_PATH","path","join","getExpoHomeDirectory","debug","require","args","options","xcrunAsync","error","CommandError","errorCodes","getDeviceCtlErrorCodes","stderr","includes","tmpPath","createTempFilePath","devices","stdout","devicesJson","JsonFile","readAsync","info","jsonVersion","Log","warn","assertDevicesJson","result","results","assert","Array","isArray","JSON","stringify","bundleId","appBinaryPath","spawnAsync","code","message","installAppWithDeviceCtlAsync","uuid","bundleIdOrAppPath","onProgress","Promise","resolve","reject","childProcess","spawn","currentProgress","hasStarted","updateProgress","progress","isComplete","status","on","data","strings","toString","split","EOL","map","value","trim","forEach","str","match","parseInt","read","err","Error","detach","off","kill","installExitHooks","deviceId","log","matchAll","_line","hasEverBeenInstalled","fs","existsSync","isInstalled","isDevicectlInstalled","writeFileSync","execSync","stdio","props","bundle","bundleIdentifier","udid","deviceName","indicator","ora","start","text","chalk","bold","succeed","fail","launchAppOptionally","appName","basename","isInteractive","confirmAsync","initial"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../src/start/platforms/ios/devicectl.ts"],"sourcesContent":["/**\n * Copyright © 2024 650 Industries.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\nimport JsonFile from '@expo/json-file';\nimport spawnAsync, { SpawnOptions, SpawnResult } from '@expo/spawn-async';\nimport chalk from 'chalk';\nimport { spawn, execSync } from 'child_process';\nimport fs from 'fs';\nimport assert from 'node:assert';\nimport { Ora } from 'ora';\nimport { EOL } from 'os';\nimport path from 'path';\n\nimport { xcrunAsync } from './xcrun';\nimport { getExpoHomeDirectory } from '../../../api/user/UserSettings';\nimport * as Log from '../../../log';\nimport { createTempFilePath } from '../../../utils/createTempPath';\nimport { CommandError } from '../../../utils/errors';\nimport { installExitHooks } from '../../../utils/exit';\nimport { isInteractive } from '../../../utils/interactive';\nimport { ora } from '../../../utils/ora';\nimport { confirmAsync } from '../../../utils/prompts';\n\nconst DEVICE_CTL_EXISTS_PATH = path.join(getExpoHomeDirectory(), 'devicectl-exists');\n\nconst debug = require('debug')('expo:devicectl') as typeof console.log;\n\ntype AnyEnum<T extends string = string> = T | (string & object);\n\ntype DeviceCtlDevice = {\n capabilities: DeviceCtlDeviceCapability[];\n connectionProperties: DeviceCtlConnectionProperties;\n deviceProperties: DeviceCtlDeviceProperties;\n hardwareProperties: DeviceCtlHardwareProperties;\n /** \"A1A1AAA1-0011-1AA1-11A1-10A1111AA11A\" */\n identifier: string;\n visibilityClass: AnyEnum<'default'>;\n};\n\ntype DeviceCtlHardwareProperties = {\n cpuType: DeviceCtlCpuType;\n deviceType: AnyEnum<'iPhone'>;\n /** 1114404411111111 */\n ecid: number;\n /** \"D74AP\" */\n hardwareModel: string;\n /** 512000000000 */\n internalStorageCapacity: number;\n /** true */\n isProductionFused: boolean;\n /** \"iPhone 14 Pro Max\" */\n marketingName: string;\n /** \"iOS\" */\n platform: AnyEnum<'iOS' | 'xrOS'>;\n /** \"iPhone15,3\" */\n productType: AnyEnum<'iPhone13,4' | 'iPhone15,3'>;\n reality: AnyEnum<'physical' | 'simulated'>;\n /** \"X2X1CC1XXX\" */\n serialNumber: string;\n supportedCPUTypes: DeviceCtlCpuType[];\n /** [1] */\n supportedDeviceFamilies: number[];\n thinningProductType: AnyEnum<'iPhone15,3'>;\n /** \"00001110-001111110110101A\" */\n udid: string;\n};\n\ntype DeviceCtlDeviceProperties = {\n /** true */\n bootedFromSnapshot: boolean;\n /** \"com.apple.os.update-AD0CF111ACFF11A11111A76A3D1262AE42A3F56F305AF5AE1135393A7A14A7D1\" */\n bootedSnapshotName: string;\n /** false */\n ddiServicesAvailable: boolean;\n\n developerModeStatus: AnyEnum<'enabled'>;\n /** false */\n hasInternalOSBuild: boolean;\n /** \"Evan's phone\" */\n name: string;\n /** \"21E236\" */\n osBuildUpdate: string;\n /** \"17.4.1\" */\n osVersionNumber: string;\n /** false */\n rootFileSystemIsWritable: boolean;\n};\n\ntype DeviceCtlDeviceCapability =\n | {\n name: AnyEnum;\n featureIdentifier: AnyEnum;\n }\n | {\n featureIdentifier: 'com.apple.coredevice.feature.connectdevice';\n name: 'Connect to Device';\n }\n | {\n featureIdentifier: 'com.apple.coredevice.feature.unpairdevice';\n name: 'Unpair Device';\n }\n | {\n featureIdentifier: 'com.apple.coredevice.feature.acquireusageassertion';\n name: 'Acquire Usage Assertion';\n };\n\ntype DeviceCtlConnectionProperties = {\n authenticationType: AnyEnum<'manualPairing'>;\n isMobileDeviceOnly: boolean;\n /** \"2024-04-20T22:50:04.244Z\" */\n lastConnectionDate: string;\n pairingState: AnyEnum<'paired'>;\n /** [\"00001111-001111110110101A.coredevice.local\", \"A1A1AAA1-0011-1AA1-11A1-10A1111AA11A.coredevice.local\"] */\n potentialHostnames: string[];\n transportType: AnyEnum<'localNetwork' | 'wired'>;\n tunnelState: AnyEnum<'disconnected' | 'unavailable'>;\n tunnelTransportProtocol: AnyEnum<'tcp'>;\n};\n\ntype DeviceCtlCpuType = {\n name: AnyEnum<'arm64e' | 'arm64' | 'arm64_32'>;\n subType: number;\n /** 16777228 */\n type: number;\n};\n\n/** Run a `devicectl` command. */\nexport async function devicectlAsync(\n args: (string | undefined)[],\n options?: SpawnOptions\n): Promise<SpawnResult> {\n try {\n return await xcrunAsync(['devicectl', ...args], options);\n } catch (error: any) {\n if (error instanceof CommandError) {\n throw error;\n }\n if ('stderr' in error) {\n const errorCodes = getDeviceCtlErrorCodes(error.stderr);\n if (errorCodes.includes('Locked')) {\n throw new CommandError('APPLE_DEVICE_LOCKED', 'Device is locked, unlock and try again.');\n }\n }\n throw error;\n }\n}\n\nexport async function getConnectedAppleDevicesAsync() {\n if (!hasDevicectlEverBeenInstalled()) {\n debug('devicectl not found, skipping remote Apple devices.');\n return [];\n }\n\n const tmpPath = createTempFilePath();\n const devices = await devicectlAsync([\n 'list',\n 'devices',\n '--json-output',\n tmpPath,\n // Give two seconds before timing out: between 5 and 9223372036854775807\n '--timeout',\n '5',\n ]);\n debug(devices.stdout);\n const devicesJson = await JsonFile.readAsync(tmpPath);\n\n if (![2, 3, 5].includes((devicesJson as any)?.info?.jsonVersion)) {\n Log.warn(\n 'Unexpected devicectl JSON version output from devicectl. Connecting to physical Apple devices may not work as expected.'\n );\n }\n\n assertDevicesJson(devicesJson);\n\n return (devicesJson.result.devices as DeviceCtlDevice[]).filter(\n (device) => device?.hardwareProperties?.reality !== 'simulated'\n );\n}\n\nfunction assertDevicesJson(\n results: any\n): asserts results is { result: { devices: DeviceCtlDevice[] } } {\n assert(\n results != null && 'result' in results && Array.isArray(results?.result?.devices),\n 'Malformed JSON output from devicectl: ' + JSON.stringify(results, null, 2)\n );\n}\n\nexport async function launchBinaryOnMacAsync(\n bundleId: string,\n appBinaryPath: string\n): Promise<void> {\n const args = ['-b', bundleId, appBinaryPath];\n try {\n await spawnAsync('open', args);\n } catch (error: any) {\n if ('code' in error) {\n if (error.code === 1) {\n throw new CommandError(\n 'MACOS_LAUNCH',\n 'Failed to launch the compatible binary on macOS: open ' +\n args.join(' ') +\n '\\n\\n' +\n error.message\n );\n }\n }\n throw error;\n }\n}\n\nasync function installAppWithDeviceCtlAsync(\n uuid: string,\n bundleIdOrAppPath: string,\n onProgress: (event: { status: string; isComplete: boolean; progress: number }) => void\n): Promise<void> {\n // 𝝠 xcrun devicectl device install app --device 00001110-001111110110101A /Users/evanbacon/Library/Developer/Xcode/DerivedData/Router-hgbqaxzhrhkiftfweydvhgttadvn/Build/Products/Debug-iphoneos/Router.app --verbose\n return new Promise((resolve, reject) => {\n const args: string[] = [\n 'devicectl',\n 'device',\n 'install',\n 'app',\n '--device',\n uuid,\n bundleIdOrAppPath,\n ];\n const childProcess = spawn('xcrun', args);\n debug('xcrun ' + args.join(' '));\n\n let currentProgress = 0;\n let hasStarted = false;\n\n function updateProgress(progress: number) {\n hasStarted = true;\n if (progress <= currentProgress) {\n return;\n }\n currentProgress = progress;\n onProgress({\n progress,\n isComplete: progress === 100,\n status: 'Installing',\n });\n }\n\n childProcess.stdout.on('data', (data: Buffer) => {\n // Sometimes more than one chunk comes at a time, here we split by system newline,\n // then trim and filter.\n const strings = data\n .toString()\n .split(EOL)\n .map((value) => value.trim());\n\n strings.forEach((str) => {\n // Match the progress percentage:\n // - '34%... 35%...' -> 34\n // - '31%...' -> 31\n // - 'Complete!' -> 100\n\n const match = str.match(/(\\d+)%\\.\\.\\./);\n if (match) {\n updateProgress(parseInt(match[1], 10));\n } else if (hasStarted) {\n updateProgress(100);\n }\n });\n\n debug('[stdout]:', strings);\n });\n\n childProcess.on('close', (code) => {\n debug('[close]: ' + code);\n if (code === 0) {\n resolve();\n } else {\n const stderr = childProcess.stderr.read();\n const err = new Error(stderr);\n (err as any).code = code;\n detach(err);\n }\n });\n\n const detach = async (err?: Error) => {\n off?.();\n if (childProcess) {\n return new Promise<void>((resolve) => {\n childProcess?.on('close', resolve);\n childProcess?.kill();\n // childProcess = null;\n reject(err ?? new CommandError('detached'));\n });\n }\n };\n\n const off = installExitHooks(() => detach());\n });\n}\n\nexport async function launchAppWithDeviceCtl(deviceId: string, bundleId: string) {\n await devicectlAsync(['device', 'process', 'launch', '--device', deviceId, bundleId]);\n}\n\n/** Find all error codes from the output log */\nfunction getDeviceCtlErrorCodes(log: string): string[] {\n return [...log.matchAll(/BSErrorCodeDescription\\s+=\\s+(.*)$/gim)].map(([_line, code]) => code);\n}\n\nlet hasEverBeenInstalled: boolean | undefined;\n\nexport function hasDevicectlEverBeenInstalled() {\n if (hasEverBeenInstalled) return hasEverBeenInstalled;\n // It doesn't appear possible for devicectl to ever be uninstalled. We can just check once and store this result forever\n // to prevent cold boots of devicectl from slowing down all invocations of `expo run ios`\n if (fs.existsSync(DEVICE_CTL_EXISTS_PATH)) {\n hasEverBeenInstalled = true;\n return true;\n }\n\n const isInstalled = isDevicectlInstalled();\n\n if (isInstalled) {\n fs.writeFileSync(DEVICE_CTL_EXISTS_PATH, '1');\n }\n hasEverBeenInstalled = isInstalled;\n return isInstalled;\n}\n\nfunction isDevicectlInstalled() {\n try {\n execSync('xcrun devicectl --version', { stdio: 'ignore' });\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Wraps the apple device method for installing and running an app,\n * adds indicator and retry loop for when the device is locked.\n */\nexport async function installAndLaunchAppAsync(props: {\n bundle: string;\n bundleIdentifier: string;\n udid: string;\n deviceName: string;\n}): Promise<void> {\n debug('Running on device:', props);\n const { bundle, bundleIdentifier, udid, deviceName } = props;\n let indicator: Ora | undefined;\n\n try {\n if (!indicator) {\n indicator = ora(`Connecting to: ${props.deviceName}`).start();\n }\n\n await installAppWithDeviceCtlAsync(\n udid,\n bundle,\n ({\n status,\n isComplete,\n progress,\n }: {\n status: string;\n isComplete: boolean;\n progress: number;\n }) => {\n if (!indicator) {\n indicator = ora(status).start();\n }\n indicator.text = `${chalk.bold(status)} ${progress}%`;\n if (isComplete) {\n indicator.succeed();\n }\n }\n );\n } catch (error: any) {\n if (indicator) {\n indicator.fail();\n }\n throw error;\n }\n\n async function launchAppOptionally() {\n try {\n await launchAppWithDeviceCtl(udid, bundleIdentifier);\n } catch (error: any) {\n if (indicator) {\n indicator.fail();\n }\n if (error.code === 'APPLE_DEVICE_LOCKED') {\n // Get the app name from the binary path.\n const appName = path.basename(bundle).split('.')[0] ?? 'app';\n if (\n isInteractive() &&\n (await confirmAsync({\n message: `Cannot launch ${appName} because the device is locked. Unlock ${deviceName} to continue...`,\n initial: true,\n }))\n ) {\n return launchAppOptionally();\n }\n throw new CommandError(\n `Cannot launch ${appName} on ${deviceName} because the device is locked.`\n );\n }\n throw error;\n }\n }\n\n await launchAppOptionally();\n}\n"],"names":["devicectlAsync","getConnectedAppleDevicesAsync","hasDevicectlEverBeenInstalled","installAndLaunchAppAsync","launchAppWithDeviceCtl","launchBinaryOnMacAsync","DEVICE_CTL_EXISTS_PATH","path","join","getExpoHomeDirectory","debug","require","args","options","xcrunAsync","error","CommandError","errorCodes","getDeviceCtlErrorCodes","stderr","includes","tmpPath","createTempFilePath","devices","stdout","devicesJson","JsonFile","readAsync","info","jsonVersion","Log","warn","assertDevicesJson","result","filter","device","hardwareProperties","reality","results","assert","Array","isArray","JSON","stringify","bundleId","appBinaryPath","spawnAsync","code","message","installAppWithDeviceCtlAsync","uuid","bundleIdOrAppPath","onProgress","Promise","resolve","reject","childProcess","spawn","currentProgress","hasStarted","updateProgress","progress","isComplete","status","on","data","strings","toString","split","EOL","map","value","trim","forEach","str","match","parseInt","read","err","Error","detach","off","kill","installExitHooks","deviceId","log","matchAll","_line","hasEverBeenInstalled","fs","existsSync","isInstalled","isDevicectlInstalled","writeFileSync","execSync","stdio","props","bundle","bundleIdentifier","udid","deviceName","indicator","ora","start","text","chalk","bold","succeed","fail","launchAppOptionally","appName","basename","isInteractive","confirmAsync","initial"],"mappings":"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"}
|
|
@@ -271,6 +271,9 @@ async function getCodeSigningInfoAsync(exp, expectSignatureHeader, privateKeyPat
|
|
|
271
271
|
var _exp_updates, _exp_updates1;
|
|
272
272
|
const codeSigningCertificatePath = (_exp_updates = exp.updates) == null ? void 0 : _exp_updates.codeSigningCertificate;
|
|
273
273
|
if (!codeSigningCertificatePath) {
|
|
274
|
+
if (privateKeyPath) {
|
|
275
|
+
throw new _errors.CommandError('--private-key-path was specified, but updates.codeSigningCertificate is not set in the resolved app config. Code signing requires both the certificate in app config and the private key. Ensure codeSigningCertificate (and codeSigningMetadata) are present, or omit --private-key-path if you do not intend to sign.\nLearn more: https://docs.expo.dev/eas-update/code-signing/');
|
|
276
|
+
}
|
|
274
277
|
return null;
|
|
275
278
|
}
|
|
276
279
|
if (!privateKeyPath) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/utils/codesigning.ts"],"sourcesContent":["import { GraphQLError } from '@0no-co/graphql.web';\nimport {\n convertCertificatePEMToCertificate,\n convertKeyPairToPEM,\n convertCSRToCSRPEM,\n generateKeyPair,\n generateCSR,\n convertPrivateKeyPEMToPrivateKey,\n validateSelfSignedCertificate,\n signBufferRSASHA256AndVerify,\n} from '@expo/code-signing-certificates';\nimport { ExpoConfig } from '@expo/config';\nimport JsonFile, { JSONObject } from '@expo/json-file';\nimport { CombinedError } from '@urql/core';\nimport { promises as fs } from 'fs';\nimport { pki as PKI } from 'node-forge';\nimport path from 'path';\nimport { Dictionary, parseDictionary } from 'structured-headers';\n\nimport { env } from './env';\nimport { CommandError } from './errors';\nimport { getExpoGoIntermediateCertificateAsync } from '../api/getExpoGoIntermediateCertificate';\nimport { getProjectDevelopmentCertificateAsync } from '../api/getProjectDevelopmentCertificate';\nimport { AppQuery } from '../api/graphql/queries/AppQuery';\nimport { getExpoHomeDirectory } from '../api/user/UserSettings';\nimport { tryGetUserAsync } from '../api/user/actions';\nimport { Actor } from '../api/user/user';\nimport { AppByIdQuery, Permission } from '../graphql/generated';\nimport * as Log from '../log';\nimport { learnMore } from '../utils/link';\n\nconst debug = require('debug')('expo:codesigning') as typeof console.log;\n\nexport type CodeSigningInfo = {\n keyId: string;\n privateKey: string;\n certificateForPrivateKey: string;\n /**\n * Chain of certificates to serve in the manifest multipart body \"certificate_chain\" part.\n * The leaf certificate must be the 0th element of the array, followed by any intermediate certificates\n * necessary to evaluate the chain of trust ending in the implicitly trusted root certificate embedded in\n * the client.\n *\n * An empty array indicates that there is no need to serve the certificate chain in the multipart response.\n */\n certificateChainForResponse: string[];\n /**\n * Scope key cached for the project when certificate is development Expo Go code signing.\n * For project-specific code signing (keyId == the project's generated keyId) this is undefined.\n */\n scopeKey: string | null;\n};\n\ntype StoredDevelopmentExpoRootCodeSigningInfo = {\n easProjectId: string | null;\n scopeKey: string | null;\n privateKey: string | null;\n certificateChain: string[] | null;\n};\nconst DEVELOPMENT_CODE_SIGNING_SETTINGS_FILE_NAME = 'development-code-signing-settings-2.json';\n\nexport function getDevelopmentCodeSigningDirectory(): string {\n return path.join(getExpoHomeDirectory(), 'codesigning');\n}\n\nfunction assertBasenameValue(input: string): void {\n if (!input || input === '.' || input === '..' || input !== path.basename(input)) {\n throw new CommandError('Invalid EAS project ID for development code signing cache');\n }\n}\n\nfunction getProjectDevelopmentCodeSigningInfoFile<T extends JSONObject>(defaults: T) {\n function getFile(easProjectId: string): JsonFile<T> {\n assertBasenameValue(easProjectId);\n const filePath = path.join(\n getDevelopmentCodeSigningDirectory(),\n easProjectId,\n DEVELOPMENT_CODE_SIGNING_SETTINGS_FILE_NAME\n );\n return new JsonFile<T>(filePath);\n }\n\n async function readAsync(easProjectId: string): Promise<T> {\n let projectSettings;\n try {\n projectSettings = await getFile(easProjectId).readAsync();\n } catch {\n projectSettings = await getFile(easProjectId).writeAsync(defaults, { ensureDir: true });\n }\n // Set defaults for any missing fields\n return { ...defaults, ...projectSettings };\n }\n\n async function setAsync(easProjectId: string, json: Partial<T>): Promise<T> {\n try {\n return await getFile(easProjectId).mergeAsync(json, {\n cantReadFileDefault: defaults,\n });\n } catch {\n return await getFile(easProjectId).writeAsync(\n {\n ...defaults,\n ...json,\n },\n { ensureDir: true }\n );\n }\n }\n\n return {\n getFile,\n readAsync,\n setAsync,\n };\n}\n\nexport const DevelopmentCodeSigningInfoFile =\n getProjectDevelopmentCodeSigningInfoFile<StoredDevelopmentExpoRootCodeSigningInfo>({\n easProjectId: null,\n scopeKey: null,\n privateKey: null,\n certificateChain: null,\n });\n\n/**\n * Get info necessary to generate a response `expo-signature` header given a project and incoming request `expo-expect-signature` header.\n * This only knows how to serve two code signing keyids:\n * - `expo-root` indicates that it should use a development certificate in the `expo-root` chain. See {@link getExpoRootDevelopmentCodeSigningInfoAsync}\n * - <developer's expo-updates keyid> indicates that it should sign with the configured certificate. See {@link getProjectCodeSigningCertificateAsync}\n */\nexport async function getCodeSigningInfoAsync(\n exp: ExpoConfig,\n expectSignatureHeader: string | null,\n privateKeyPath: string | undefined\n): Promise<CodeSigningInfo | null> {\n if (!expectSignatureHeader) {\n return null;\n }\n\n let parsedExpectSignature: Dictionary;\n try {\n parsedExpectSignature = parseDictionary(expectSignatureHeader);\n } catch {\n throw new CommandError('Invalid value for expo-expect-signature header');\n }\n\n const expectedKeyIdOuter = parsedExpectSignature.get('keyid');\n if (!expectedKeyIdOuter) {\n throw new CommandError('keyid not present in expo-expect-signature header');\n }\n\n const expectedKeyId = expectedKeyIdOuter[0];\n if (typeof expectedKeyId !== 'string') {\n throw new CommandError(\n `Invalid value for keyid in expo-expect-signature header: ${expectedKeyId}`\n );\n }\n\n let expectedAlg: string | null = null;\n const expectedAlgOuter = parsedExpectSignature.get('alg');\n if (expectedAlgOuter) {\n const expectedAlgTemp = expectedAlgOuter[0];\n if (typeof expectedAlgTemp !== 'string') {\n throw new CommandError('Invalid value for alg in expo-expect-signature header');\n }\n expectedAlg = expectedAlgTemp;\n }\n\n if (expectedKeyId === 'expo-root') {\n return await getExpoRootDevelopmentCodeSigningInfoAsync(exp);\n } else if (expectedKeyId === 'expo-go') {\n throw new CommandError(\n 'Invalid certificate requested: cannot sign with embedded keyid=expo-go key'\n );\n } else {\n return await getProjectCodeSigningCertificateAsync(\n exp,\n privateKeyPath,\n expectedKeyId,\n expectedAlg\n );\n }\n}\n\n/**\n * Get a development code signing certificate for the expo-root -> expo-go -> (development certificate) certificate chain.\n * This requires the user be logged in and online, otherwise try to use the cached development certificate.\n */\nasync function getExpoRootDevelopmentCodeSigningInfoAsync(\n exp: ExpoConfig\n): Promise<CodeSigningInfo | null> {\n const easProjectId = exp.extra?.eas?.projectId;\n // can't check for scope key validity since scope key is derived on the server from projectId and we may be offline.\n // we rely upon the client certificate check to validate the scope key\n if (!easProjectId) {\n debug(\n `WARN: Expo Application Services (EAS) is not configured for your project. Configuring EAS enables a more secure development experience amongst many other benefits. ${learnMore(\n 'https://docs.expo.dev/eas/'\n )}`\n );\n return null;\n }\n\n const developmentCodeSigningInfoFromFile =\n await DevelopmentCodeSigningInfoFile.readAsync(easProjectId);\n const validatedCodeSigningInfo = validateStoredDevelopmentExpoRootCertificateCodeSigningInfo(\n developmentCodeSigningInfoFromFile,\n easProjectId\n );\n\n // 1. If online, ensure logged in, generate key pair and CSR, fetch and cache certificate chain for projectId\n // (overwriting existing dev cert in case projectId changed or it has expired)\n if (!env.EXPO_OFFLINE) {\n try {\n const newCodeSigningInfo =\n await fetchAndCacheNewDevelopmentCodeSigningInfoAsync(easProjectId);\n\n if (newCodeSigningInfo) {\n return newCodeSigningInfo;\n // fall back to cached certificate if we couldn't fetch a new one\n } else if (validatedCodeSigningInfo) {\n Log.warn(\n 'Could not fetch new Expo development certificate, falling back to cached certificate'\n );\n return validatedCodeSigningInfo;\n } else {\n return null;\n }\n } catch (e: any) {\n if (validatedCodeSigningInfo) {\n Log.warn(\n 'There was an error fetching the Expo development certificate, falling back to cached certificate'\n );\n return validatedCodeSigningInfo;\n } else {\n // need to return null here and say a message\n throw e;\n }\n }\n }\n\n // 2. check for cached cert/private key matching projectId and scopeKey of project, if found and valid return private key and cert chain including expo-go cert\n if (validatedCodeSigningInfo) {\n return validatedCodeSigningInfo;\n }\n\n // 3. if offline, return null\n Log.warn('Offline and no cached development certificate found, unable to sign manifest');\n return null;\n}\n\n/**\n * Get the certificate configured for expo-updates for this project.\n */\nasync function getProjectCodeSigningCertificateAsync(\n exp: ExpoConfig,\n privateKeyPath: string | undefined,\n expectedKeyId: string,\n expectedAlg: string | null\n): Promise<CodeSigningInfo | null> {\n const codeSigningCertificatePath = exp.updates?.codeSigningCertificate;\n if (!codeSigningCertificatePath) {\n return null;\n }\n\n if (!privateKeyPath) {\n throw new CommandError(\n 'Must specify --private-key-path argument to sign development manifest for requested code signing key'\n );\n }\n\n const codeSigningMetadata = exp.updates?.codeSigningMetadata;\n if (!codeSigningMetadata) {\n throw new CommandError(\n 'Must specify \"codeSigningMetadata\" under the \"updates\" field of your app config file to use EAS code signing'\n );\n }\n\n const { alg, keyid } = codeSigningMetadata;\n if (!alg || !keyid) {\n throw new CommandError(\n 'Must specify \"keyid\" and \"alg\" in the \"codeSigningMetadata\" field under the \"updates\" field of your app config file to use EAS code signing'\n );\n }\n\n if (expectedKeyId !== keyid) {\n throw new CommandError(`keyid mismatch: client=${expectedKeyId}, project=${keyid}`);\n }\n\n if (expectedAlg && expectedAlg !== alg) {\n throw new CommandError(`\"alg\" field mismatch (client=${expectedAlg}, project=${alg})`);\n }\n\n const { privateKeyPEM, certificatePEM } =\n await getProjectPrivateKeyAndCertificateFromFilePathsAsync({\n codeSigningCertificatePath,\n privateKeyPath,\n });\n\n return {\n keyId: keyid,\n privateKey: privateKeyPEM,\n certificateForPrivateKey: certificatePEM,\n certificateChainForResponse: [],\n scopeKey: null,\n };\n}\n\nasync function readFileWithErrorAsync(path: string, errorMessage: string): Promise<string> {\n try {\n return await fs.readFile(path, 'utf8');\n } catch {\n throw new CommandError(errorMessage);\n }\n}\n\nasync function getProjectPrivateKeyAndCertificateFromFilePathsAsync({\n codeSigningCertificatePath,\n privateKeyPath,\n}: {\n codeSigningCertificatePath: string;\n privateKeyPath: string;\n}): Promise<{ privateKeyPEM: string; certificatePEM: string }> {\n const [codeSigningCertificatePEM, privateKeyPEM] = await Promise.all([\n readFileWithErrorAsync(\n codeSigningCertificatePath,\n `Code signing certificate cannot be read from path: ${codeSigningCertificatePath}`\n ),\n readFileWithErrorAsync(\n privateKeyPath,\n `Code signing private key cannot be read from path: ${privateKeyPath}`\n ),\n ]);\n\n const privateKey = convertPrivateKeyPEMToPrivateKey(privateKeyPEM);\n const certificate = convertCertificatePEMToCertificate(codeSigningCertificatePEM);\n validateSelfSignedCertificate(certificate, {\n publicKey: certificate.publicKey as PKI.rsa.PublicKey,\n privateKey,\n });\n\n return { privateKeyPEM, certificatePEM: codeSigningCertificatePEM };\n}\n\n/**\n * Validate that the cached code signing info is still valid for the current project and\n * that it hasn't expired. If invalid, return null.\n */\nfunction validateStoredDevelopmentExpoRootCertificateCodeSigningInfo(\n codeSigningInfo: StoredDevelopmentExpoRootCodeSigningInfo,\n easProjectId: string\n): CodeSigningInfo | null {\n if (codeSigningInfo.easProjectId !== easProjectId) {\n return null;\n }\n\n const {\n privateKey: privateKeyPEM,\n certificateChain: certificatePEMs,\n scopeKey,\n } = codeSigningInfo;\n if (!privateKeyPEM || !certificatePEMs) {\n return null;\n }\n\n const certificateChain = certificatePEMs.map((certificatePEM) =>\n convertCertificatePEMToCertificate(certificatePEM)\n );\n\n // TODO(wschurman): maybe move to @expo/code-signing-certificates\n\n // ensure all intermediate certificates are valid\n for (const certificate of certificateChain) {\n const now = new Date();\n if (certificate.validity.notBefore > now || certificate.validity.notAfter < now) {\n return null;\n }\n }\n\n // TODO(wschurman): maybe do more validation, like validation of projectID and scopeKey within eas certificate extension\n\n return {\n keyId: 'expo-go',\n certificateChainForResponse: certificatePEMs,\n certificateForPrivateKey: certificatePEMs[0],\n privateKey: privateKeyPEM,\n scopeKey,\n };\n}\n\nfunction actorCanGetProjectDevelopmentCertificate(actor: Actor, app: AppByIdQuery['app']['byId']) {\n const owningAccountId = app.ownerAccount.id;\n\n const owningAccountIsActorPrimaryAccount =\n actor.__typename === 'User' || actor.__typename === 'SSOUser'\n ? actor.primaryAccount.id === owningAccountId\n : false;\n const userHasPublishPermissionForOwningAccount = !!actor.accounts\n .find((account) => account.id === owningAccountId)\n ?.users?.find((userPermission) => userPermission.actor.id === actor.id)\n ?.permissions?.includes(Permission.Publish);\n return owningAccountIsActorPrimaryAccount || userHasPublishPermissionForOwningAccount;\n}\n\nasync function fetchAndCacheNewDevelopmentCodeSigningInfoAsync(\n easProjectId: string\n): Promise<CodeSigningInfo | null> {\n const actor = await tryGetUserAsync();\n\n if (!actor) {\n return null;\n }\n\n let app: AppByIdQuery['app']['byId'];\n try {\n app = await AppQuery.byIdAsync(easProjectId);\n } catch (e) {\n if (e instanceof GraphQLError || e instanceof CombinedError) {\n return null;\n }\n throw e;\n }\n if (!actorCanGetProjectDevelopmentCertificate(actor, app)) {\n return null;\n }\n\n const keyPair = generateKeyPair();\n const keyPairPEM = convertKeyPairToPEM(keyPair);\n const csr = generateCSR(keyPair, `Development Certificate for ${easProjectId}`);\n const csrPEM = convertCSRToCSRPEM(csr);\n const [developmentSigningCertificate, expoGoIntermediateCertificate] = await Promise.all([\n getProjectDevelopmentCertificateAsync(easProjectId, csrPEM),\n getExpoGoIntermediateCertificateAsync(easProjectId),\n ]);\n\n await DevelopmentCodeSigningInfoFile.setAsync(easProjectId, {\n easProjectId,\n scopeKey: app.scopeKey,\n privateKey: keyPairPEM.privateKeyPEM,\n certificateChain: [developmentSigningCertificate, expoGoIntermediateCertificate],\n });\n\n return {\n keyId: 'expo-go',\n certificateChainForResponse: [developmentSigningCertificate, expoGoIntermediateCertificate],\n certificateForPrivateKey: developmentSigningCertificate,\n privateKey: keyPairPEM.privateKeyPEM,\n scopeKey: app.scopeKey,\n };\n}\n/**\n * Generate the `expo-signature` header for a manifest and code signing info.\n */\nexport function signManifestString(\n stringifiedManifest: string,\n codeSigningInfo: CodeSigningInfo\n): string {\n const privateKey = convertPrivateKeyPEMToPrivateKey(codeSigningInfo.privateKey);\n const certificate = convertCertificatePEMToCertificate(codeSigningInfo.certificateForPrivateKey);\n return signBufferRSASHA256AndVerify(\n privateKey,\n certificate,\n Buffer.from(stringifiedManifest, 'utf8')\n );\n}\n"],"names":["DevelopmentCodeSigningInfoFile","getCodeSigningInfoAsync","getDevelopmentCodeSigningDirectory","signManifestString","debug","require","DEVELOPMENT_CODE_SIGNING_SETTINGS_FILE_NAME","path","join","getExpoHomeDirectory","assertBasenameValue","input","basename","CommandError","getProjectDevelopmentCodeSigningInfoFile","defaults","getFile","easProjectId","filePath","JsonFile","readAsync","projectSettings","writeAsync","ensureDir","setAsync","json","mergeAsync","cantReadFileDefault","scopeKey","privateKey","certificateChain","exp","expectSignatureHeader","privateKeyPath","parsedExpectSignature","parseDictionary","expectedKeyIdOuter","get","expectedKeyId","expectedAlg","expectedAlgOuter","expectedAlgTemp","getExpoRootDevelopmentCodeSigningInfoAsync","getProjectCodeSigningCertificateAsync","extra","eas","projectId","learnMore","developmentCodeSigningInfoFromFile","validatedCodeSigningInfo","validateStoredDevelopmentExpoRootCertificateCodeSigningInfo","env","EXPO_OFFLINE","newCodeSigningInfo","fetchAndCacheNewDevelopmentCodeSigningInfoAsync","Log","warn","e","codeSigningCertificatePath","updates","codeSigningCertificate","codeSigningMetadata","alg","keyid","privateKeyPEM","certificatePEM","getProjectPrivateKeyAndCertificateFromFilePathsAsync","keyId","certificateForPrivateKey","certificateChainForResponse","readFileWithErrorAsync","errorMessage","fs","readFile","codeSigningCertificatePEM","Promise","all","convertPrivateKeyPEMToPrivateKey","certificate","convertCertificatePEMToCertificate","validateSelfSignedCertificate","publicKey","codeSigningInfo","certificatePEMs","map","now","Date","validity","notBefore","notAfter","actorCanGetProjectDevelopmentCertificate","actor","app","owningAccountId","ownerAccount","id","owningAccountIsActorPrimaryAccount","__typename","primaryAccount","userHasPublishPermissionForOwningAccount","accounts","find","account","users","userPermission","permissions","includes","Permission","Publish","tryGetUserAsync","AppQuery","byIdAsync","GraphQLError","CombinedError","keyPair","generateKeyPair","keyPairPEM","convertKeyPairToPEM","csr","generateCSR","csrPEM","convertCSRToCSRPEM","developmentSigningCertificate","expoGoIntermediateCertificate","getProjectDevelopmentCertificateAsync","getExpoGoIntermediateCertificateAsync","stringifiedManifest","signBufferRSASHA256AndVerify","Buffer","from"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/utils/codesigning.ts"],"sourcesContent":["import { GraphQLError } from '@0no-co/graphql.web';\nimport {\n convertCertificatePEMToCertificate,\n convertKeyPairToPEM,\n convertCSRToCSRPEM,\n generateKeyPair,\n generateCSR,\n convertPrivateKeyPEMToPrivateKey,\n validateSelfSignedCertificate,\n signBufferRSASHA256AndVerify,\n} from '@expo/code-signing-certificates';\nimport { ExpoConfig } from '@expo/config';\nimport JsonFile, { JSONObject } from '@expo/json-file';\nimport { CombinedError } from '@urql/core';\nimport { promises as fs } from 'fs';\nimport { pki as PKI } from 'node-forge';\nimport path from 'path';\nimport { Dictionary, parseDictionary } from 'structured-headers';\n\nimport { env } from './env';\nimport { CommandError } from './errors';\nimport { getExpoGoIntermediateCertificateAsync } from '../api/getExpoGoIntermediateCertificate';\nimport { getProjectDevelopmentCertificateAsync } from '../api/getProjectDevelopmentCertificate';\nimport { AppQuery } from '../api/graphql/queries/AppQuery';\nimport { getExpoHomeDirectory } from '../api/user/UserSettings';\nimport { tryGetUserAsync } from '../api/user/actions';\nimport { Actor } from '../api/user/user';\nimport { AppByIdQuery, Permission } from '../graphql/generated';\nimport * as Log from '../log';\nimport { learnMore } from '../utils/link';\n\nconst debug = require('debug')('expo:codesigning') as typeof console.log;\n\nexport type CodeSigningInfo = {\n keyId: string;\n privateKey: string;\n certificateForPrivateKey: string;\n /**\n * Chain of certificates to serve in the manifest multipart body \"certificate_chain\" part.\n * The leaf certificate must be the 0th element of the array, followed by any intermediate certificates\n * necessary to evaluate the chain of trust ending in the implicitly trusted root certificate embedded in\n * the client.\n *\n * An empty array indicates that there is no need to serve the certificate chain in the multipart response.\n */\n certificateChainForResponse: string[];\n /**\n * Scope key cached for the project when certificate is development Expo Go code signing.\n * For project-specific code signing (keyId == the project's generated keyId) this is undefined.\n */\n scopeKey: string | null;\n};\n\ntype StoredDevelopmentExpoRootCodeSigningInfo = {\n easProjectId: string | null;\n scopeKey: string | null;\n privateKey: string | null;\n certificateChain: string[] | null;\n};\nconst DEVELOPMENT_CODE_SIGNING_SETTINGS_FILE_NAME = 'development-code-signing-settings-2.json';\n\nexport function getDevelopmentCodeSigningDirectory(): string {\n return path.join(getExpoHomeDirectory(), 'codesigning');\n}\n\nfunction assertBasenameValue(input: string): void {\n if (!input || input === '.' || input === '..' || input !== path.basename(input)) {\n throw new CommandError('Invalid EAS project ID for development code signing cache');\n }\n}\n\nfunction getProjectDevelopmentCodeSigningInfoFile<T extends JSONObject>(defaults: T) {\n function getFile(easProjectId: string): JsonFile<T> {\n assertBasenameValue(easProjectId);\n const filePath = path.join(\n getDevelopmentCodeSigningDirectory(),\n easProjectId,\n DEVELOPMENT_CODE_SIGNING_SETTINGS_FILE_NAME\n );\n return new JsonFile<T>(filePath);\n }\n\n async function readAsync(easProjectId: string): Promise<T> {\n let projectSettings;\n try {\n projectSettings = await getFile(easProjectId).readAsync();\n } catch {\n projectSettings = await getFile(easProjectId).writeAsync(defaults, { ensureDir: true });\n }\n // Set defaults for any missing fields\n return { ...defaults, ...projectSettings };\n }\n\n async function setAsync(easProjectId: string, json: Partial<T>): Promise<T> {\n try {\n return await getFile(easProjectId).mergeAsync(json, {\n cantReadFileDefault: defaults,\n });\n } catch {\n return await getFile(easProjectId).writeAsync(\n {\n ...defaults,\n ...json,\n },\n { ensureDir: true }\n );\n }\n }\n\n return {\n getFile,\n readAsync,\n setAsync,\n };\n}\n\nexport const DevelopmentCodeSigningInfoFile =\n getProjectDevelopmentCodeSigningInfoFile<StoredDevelopmentExpoRootCodeSigningInfo>({\n easProjectId: null,\n scopeKey: null,\n privateKey: null,\n certificateChain: null,\n });\n\n/**\n * Get info necessary to generate a response `expo-signature` header given a project and incoming request `expo-expect-signature` header.\n * This only knows how to serve two code signing keyids:\n * - `expo-root` indicates that it should use a development certificate in the `expo-root` chain. See {@link getExpoRootDevelopmentCodeSigningInfoAsync}\n * - <developer's expo-updates keyid> indicates that it should sign with the configured certificate. See {@link getProjectCodeSigningCertificateAsync}\n */\nexport async function getCodeSigningInfoAsync(\n exp: ExpoConfig,\n expectSignatureHeader: string | null,\n privateKeyPath: string | undefined\n): Promise<CodeSigningInfo | null> {\n if (!expectSignatureHeader) {\n return null;\n }\n\n let parsedExpectSignature: Dictionary;\n try {\n parsedExpectSignature = parseDictionary(expectSignatureHeader);\n } catch {\n throw new CommandError('Invalid value for expo-expect-signature header');\n }\n\n const expectedKeyIdOuter = parsedExpectSignature.get('keyid');\n if (!expectedKeyIdOuter) {\n throw new CommandError('keyid not present in expo-expect-signature header');\n }\n\n const expectedKeyId = expectedKeyIdOuter[0];\n if (typeof expectedKeyId !== 'string') {\n throw new CommandError(\n `Invalid value for keyid in expo-expect-signature header: ${expectedKeyId}`\n );\n }\n\n let expectedAlg: string | null = null;\n const expectedAlgOuter = parsedExpectSignature.get('alg');\n if (expectedAlgOuter) {\n const expectedAlgTemp = expectedAlgOuter[0];\n if (typeof expectedAlgTemp !== 'string') {\n throw new CommandError('Invalid value for alg in expo-expect-signature header');\n }\n expectedAlg = expectedAlgTemp;\n }\n\n if (expectedKeyId === 'expo-root') {\n return await getExpoRootDevelopmentCodeSigningInfoAsync(exp);\n } else if (expectedKeyId === 'expo-go') {\n throw new CommandError(\n 'Invalid certificate requested: cannot sign with embedded keyid=expo-go key'\n );\n } else {\n return await getProjectCodeSigningCertificateAsync(\n exp,\n privateKeyPath,\n expectedKeyId,\n expectedAlg\n );\n }\n}\n\n/**\n * Get a development code signing certificate for the expo-root -> expo-go -> (development certificate) certificate chain.\n * This requires the user be logged in and online, otherwise try to use the cached development certificate.\n */\nasync function getExpoRootDevelopmentCodeSigningInfoAsync(\n exp: ExpoConfig\n): Promise<CodeSigningInfo | null> {\n const easProjectId = exp.extra?.eas?.projectId;\n // can't check for scope key validity since scope key is derived on the server from projectId and we may be offline.\n // we rely upon the client certificate check to validate the scope key\n if (!easProjectId) {\n debug(\n `WARN: Expo Application Services (EAS) is not configured for your project. Configuring EAS enables a more secure development experience amongst many other benefits. ${learnMore(\n 'https://docs.expo.dev/eas/'\n )}`\n );\n return null;\n }\n\n const developmentCodeSigningInfoFromFile =\n await DevelopmentCodeSigningInfoFile.readAsync(easProjectId);\n const validatedCodeSigningInfo = validateStoredDevelopmentExpoRootCertificateCodeSigningInfo(\n developmentCodeSigningInfoFromFile,\n easProjectId\n );\n\n // 1. If online, ensure logged in, generate key pair and CSR, fetch and cache certificate chain for projectId\n // (overwriting existing dev cert in case projectId changed or it has expired)\n if (!env.EXPO_OFFLINE) {\n try {\n const newCodeSigningInfo =\n await fetchAndCacheNewDevelopmentCodeSigningInfoAsync(easProjectId);\n\n if (newCodeSigningInfo) {\n return newCodeSigningInfo;\n // fall back to cached certificate if we couldn't fetch a new one\n } else if (validatedCodeSigningInfo) {\n Log.warn(\n 'Could not fetch new Expo development certificate, falling back to cached certificate'\n );\n return validatedCodeSigningInfo;\n } else {\n return null;\n }\n } catch (e: any) {\n if (validatedCodeSigningInfo) {\n Log.warn(\n 'There was an error fetching the Expo development certificate, falling back to cached certificate'\n );\n return validatedCodeSigningInfo;\n } else {\n // need to return null here and say a message\n throw e;\n }\n }\n }\n\n // 2. check for cached cert/private key matching projectId and scopeKey of project, if found and valid return private key and cert chain including expo-go cert\n if (validatedCodeSigningInfo) {\n return validatedCodeSigningInfo;\n }\n\n // 3. if offline, return null\n Log.warn('Offline and no cached development certificate found, unable to sign manifest');\n return null;\n}\n\n/**\n * Get the certificate configured for expo-updates for this project.\n */\nasync function getProjectCodeSigningCertificateAsync(\n exp: ExpoConfig,\n privateKeyPath: string | undefined,\n expectedKeyId: string,\n expectedAlg: string | null\n): Promise<CodeSigningInfo | null> {\n const codeSigningCertificatePath = exp.updates?.codeSigningCertificate;\n if (!codeSigningCertificatePath) {\n if (privateKeyPath) {\n throw new CommandError(\n '--private-key-path was specified, but updates.codeSigningCertificate is not set in the resolved app config. Code signing requires both the certificate in app config and the private key. Ensure codeSigningCertificate (and codeSigningMetadata) are present, or omit --private-key-path if you do not intend to sign.\\nLearn more: https://docs.expo.dev/eas-update/code-signing/'\n );\n }\n return null;\n }\n\n if (!privateKeyPath) {\n throw new CommandError(\n 'Must specify --private-key-path argument to sign development manifest for requested code signing key'\n );\n }\n\n const codeSigningMetadata = exp.updates?.codeSigningMetadata;\n if (!codeSigningMetadata) {\n throw new CommandError(\n 'Must specify \"codeSigningMetadata\" under the \"updates\" field of your app config file to use EAS code signing'\n );\n }\n\n const { alg, keyid } = codeSigningMetadata;\n if (!alg || !keyid) {\n throw new CommandError(\n 'Must specify \"keyid\" and \"alg\" in the \"codeSigningMetadata\" field under the \"updates\" field of your app config file to use EAS code signing'\n );\n }\n\n if (expectedKeyId !== keyid) {\n throw new CommandError(`keyid mismatch: client=${expectedKeyId}, project=${keyid}`);\n }\n\n if (expectedAlg && expectedAlg !== alg) {\n throw new CommandError(`\"alg\" field mismatch (client=${expectedAlg}, project=${alg})`);\n }\n\n const { privateKeyPEM, certificatePEM } =\n await getProjectPrivateKeyAndCertificateFromFilePathsAsync({\n codeSigningCertificatePath,\n privateKeyPath,\n });\n\n return {\n keyId: keyid,\n privateKey: privateKeyPEM,\n certificateForPrivateKey: certificatePEM,\n certificateChainForResponse: [],\n scopeKey: null,\n };\n}\n\nasync function readFileWithErrorAsync(path: string, errorMessage: string): Promise<string> {\n try {\n return await fs.readFile(path, 'utf8');\n } catch {\n throw new CommandError(errorMessage);\n }\n}\n\nasync function getProjectPrivateKeyAndCertificateFromFilePathsAsync({\n codeSigningCertificatePath,\n privateKeyPath,\n}: {\n codeSigningCertificatePath: string;\n privateKeyPath: string;\n}): Promise<{ privateKeyPEM: string; certificatePEM: string }> {\n const [codeSigningCertificatePEM, privateKeyPEM] = await Promise.all([\n readFileWithErrorAsync(\n codeSigningCertificatePath,\n `Code signing certificate cannot be read from path: ${codeSigningCertificatePath}`\n ),\n readFileWithErrorAsync(\n privateKeyPath,\n `Code signing private key cannot be read from path: ${privateKeyPath}`\n ),\n ]);\n\n const privateKey = convertPrivateKeyPEMToPrivateKey(privateKeyPEM);\n const certificate = convertCertificatePEMToCertificate(codeSigningCertificatePEM);\n validateSelfSignedCertificate(certificate, {\n publicKey: certificate.publicKey as PKI.rsa.PublicKey,\n privateKey,\n });\n\n return { privateKeyPEM, certificatePEM: codeSigningCertificatePEM };\n}\n\n/**\n * Validate that the cached code signing info is still valid for the current project and\n * that it hasn't expired. If invalid, return null.\n */\nfunction validateStoredDevelopmentExpoRootCertificateCodeSigningInfo(\n codeSigningInfo: StoredDevelopmentExpoRootCodeSigningInfo,\n easProjectId: string\n): CodeSigningInfo | null {\n if (codeSigningInfo.easProjectId !== easProjectId) {\n return null;\n }\n\n const {\n privateKey: privateKeyPEM,\n certificateChain: certificatePEMs,\n scopeKey,\n } = codeSigningInfo;\n if (!privateKeyPEM || !certificatePEMs) {\n return null;\n }\n\n const certificateChain = certificatePEMs.map((certificatePEM) =>\n convertCertificatePEMToCertificate(certificatePEM)\n );\n\n // TODO(wschurman): maybe move to @expo/code-signing-certificates\n\n // ensure all intermediate certificates are valid\n for (const certificate of certificateChain) {\n const now = new Date();\n if (certificate.validity.notBefore > now || certificate.validity.notAfter < now) {\n return null;\n }\n }\n\n // TODO(wschurman): maybe do more validation, like validation of projectID and scopeKey within eas certificate extension\n\n return {\n keyId: 'expo-go',\n certificateChainForResponse: certificatePEMs,\n certificateForPrivateKey: certificatePEMs[0],\n privateKey: privateKeyPEM,\n scopeKey,\n };\n}\n\nfunction actorCanGetProjectDevelopmentCertificate(actor: Actor, app: AppByIdQuery['app']['byId']) {\n const owningAccountId = app.ownerAccount.id;\n\n const owningAccountIsActorPrimaryAccount =\n actor.__typename === 'User' || actor.__typename === 'SSOUser'\n ? actor.primaryAccount.id === owningAccountId\n : false;\n const userHasPublishPermissionForOwningAccount = !!actor.accounts\n .find((account) => account.id === owningAccountId)\n ?.users?.find((userPermission) => userPermission.actor.id === actor.id)\n ?.permissions?.includes(Permission.Publish);\n return owningAccountIsActorPrimaryAccount || userHasPublishPermissionForOwningAccount;\n}\n\nasync function fetchAndCacheNewDevelopmentCodeSigningInfoAsync(\n easProjectId: string\n): Promise<CodeSigningInfo | null> {\n const actor = await tryGetUserAsync();\n\n if (!actor) {\n return null;\n }\n\n let app: AppByIdQuery['app']['byId'];\n try {\n app = await AppQuery.byIdAsync(easProjectId);\n } catch (e) {\n if (e instanceof GraphQLError || e instanceof CombinedError) {\n return null;\n }\n throw e;\n }\n if (!actorCanGetProjectDevelopmentCertificate(actor, app)) {\n return null;\n }\n\n const keyPair = generateKeyPair();\n const keyPairPEM = convertKeyPairToPEM(keyPair);\n const csr = generateCSR(keyPair, `Development Certificate for ${easProjectId}`);\n const csrPEM = convertCSRToCSRPEM(csr);\n const [developmentSigningCertificate, expoGoIntermediateCertificate] = await Promise.all([\n getProjectDevelopmentCertificateAsync(easProjectId, csrPEM),\n getExpoGoIntermediateCertificateAsync(easProjectId),\n ]);\n\n await DevelopmentCodeSigningInfoFile.setAsync(easProjectId, {\n easProjectId,\n scopeKey: app.scopeKey,\n privateKey: keyPairPEM.privateKeyPEM,\n certificateChain: [developmentSigningCertificate, expoGoIntermediateCertificate],\n });\n\n return {\n keyId: 'expo-go',\n certificateChainForResponse: [developmentSigningCertificate, expoGoIntermediateCertificate],\n certificateForPrivateKey: developmentSigningCertificate,\n privateKey: keyPairPEM.privateKeyPEM,\n scopeKey: app.scopeKey,\n };\n}\n/**\n * Generate the `expo-signature` header for a manifest and code signing info.\n */\nexport function signManifestString(\n stringifiedManifest: string,\n codeSigningInfo: CodeSigningInfo\n): string {\n const privateKey = convertPrivateKeyPEMToPrivateKey(codeSigningInfo.privateKey);\n const certificate = convertCertificatePEMToCertificate(codeSigningInfo.certificateForPrivateKey);\n return signBufferRSASHA256AndVerify(\n privateKey,\n certificate,\n Buffer.from(stringifiedManifest, 'utf8')\n );\n}\n"],"names":["DevelopmentCodeSigningInfoFile","getCodeSigningInfoAsync","getDevelopmentCodeSigningDirectory","signManifestString","debug","require","DEVELOPMENT_CODE_SIGNING_SETTINGS_FILE_NAME","path","join","getExpoHomeDirectory","assertBasenameValue","input","basename","CommandError","getProjectDevelopmentCodeSigningInfoFile","defaults","getFile","easProjectId","filePath","JsonFile","readAsync","projectSettings","writeAsync","ensureDir","setAsync","json","mergeAsync","cantReadFileDefault","scopeKey","privateKey","certificateChain","exp","expectSignatureHeader","privateKeyPath","parsedExpectSignature","parseDictionary","expectedKeyIdOuter","get","expectedKeyId","expectedAlg","expectedAlgOuter","expectedAlgTemp","getExpoRootDevelopmentCodeSigningInfoAsync","getProjectCodeSigningCertificateAsync","extra","eas","projectId","learnMore","developmentCodeSigningInfoFromFile","validatedCodeSigningInfo","validateStoredDevelopmentExpoRootCertificateCodeSigningInfo","env","EXPO_OFFLINE","newCodeSigningInfo","fetchAndCacheNewDevelopmentCodeSigningInfoAsync","Log","warn","e","codeSigningCertificatePath","updates","codeSigningCertificate","codeSigningMetadata","alg","keyid","privateKeyPEM","certificatePEM","getProjectPrivateKeyAndCertificateFromFilePathsAsync","keyId","certificateForPrivateKey","certificateChainForResponse","readFileWithErrorAsync","errorMessage","fs","readFile","codeSigningCertificatePEM","Promise","all","convertPrivateKeyPEMToPrivateKey","certificate","convertCertificatePEMToCertificate","validateSelfSignedCertificate","publicKey","codeSigningInfo","certificatePEMs","map","now","Date","validity","notBefore","notAfter","actorCanGetProjectDevelopmentCertificate","actor","app","owningAccountId","ownerAccount","id","owningAccountIsActorPrimaryAccount","__typename","primaryAccount","userHasPublishPermissionForOwningAccount","accounts","find","account","users","userPermission","permissions","includes","Permission","Publish","tryGetUserAsync","AppQuery","byIdAsync","GraphQLError","CombinedError","keyPair","generateKeyPair","keyPairPEM","convertKeyPairToPEM","csr","generateCSR","csrPEM","convertCSRToCSRPEM","developmentSigningCertificate","expoGoIntermediateCertificate","getProjectDevelopmentCertificateAsync","getExpoGoIntermediateCertificateAsync","stringifiedManifest","signBufferRSASHA256AndVerify","Buffer","from"],"mappings":"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"}
|
|
@@ -16,16 +16,16 @@ _export(exports, {
|
|
|
16
16
|
return findUpProjectRootOrAssert;
|
|
17
17
|
}
|
|
18
18
|
});
|
|
19
|
-
function
|
|
20
|
-
const data = /*#__PURE__*/ _interop_require_default(require("
|
|
21
|
-
|
|
19
|
+
function _fs() {
|
|
20
|
+
const data = /*#__PURE__*/ _interop_require_default(require("fs"));
|
|
21
|
+
_fs = function() {
|
|
22
22
|
return data;
|
|
23
23
|
};
|
|
24
24
|
return data;
|
|
25
25
|
}
|
|
26
|
-
function
|
|
27
|
-
const data = /*#__PURE__*/ _interop_require_default(require("
|
|
28
|
-
|
|
26
|
+
function _path() {
|
|
27
|
+
const data = /*#__PURE__*/ _interop_require_default(require("path"));
|
|
28
|
+
_path = function() {
|
|
29
29
|
return data;
|
|
30
30
|
};
|
|
31
31
|
return data;
|
|
@@ -41,21 +41,19 @@ function findUpProjectRootOrAssert(cwd) {
|
|
|
41
41
|
if (!projectRoot) {
|
|
42
42
|
throw new _errors.CommandError(`Project root directory not found (working directory: ${cwd})`);
|
|
43
43
|
}
|
|
44
|
-
return projectRoot;
|
|
44
|
+
return _path().default.dirname(projectRoot);
|
|
45
45
|
}
|
|
46
|
-
function findUpProjectRoot(
|
|
47
|
-
|
|
48
|
-
if (found) return _path().default.dirname(found);
|
|
49
|
-
const parent = _path().default.dirname(cwd);
|
|
50
|
-
if (parent === cwd) return null;
|
|
51
|
-
return findUpProjectRoot(parent);
|
|
46
|
+
function findUpProjectRoot(root) {
|
|
47
|
+
return findFileInParents(root, 'package.json');
|
|
52
48
|
}
|
|
53
|
-
function findFileInParents(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
49
|
+
function findFileInParents(root, fileName) {
|
|
50
|
+
for(let dir = root; _path().default.dirname(dir) !== dir; dir = _path().default.dirname(dir)){
|
|
51
|
+
const file = _path().default.resolve(dir, fileName);
|
|
52
|
+
if (_fs().default.existsSync(file)) {
|
|
53
|
+
return file;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
59
57
|
}
|
|
60
58
|
|
|
61
59
|
//# sourceMappingURL=findUp.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/utils/findUp.ts"],"sourcesContent":["import
|
|
1
|
+
{"version":3,"sources":["../../../src/utils/findUp.ts"],"sourcesContent":["import fs from 'fs';\nimport path from 'path';\n\nimport { CommandError } from '../utils/errors';\n\n/** Look up directories until one with a `package.json` can be found, assert if none can be found. */\nexport function findUpProjectRootOrAssert(cwd: string): string {\n const projectRoot = findUpProjectRoot(cwd);\n if (!projectRoot) {\n throw new CommandError(`Project root directory not found (working directory: ${cwd})`);\n }\n return path.dirname(projectRoot);\n}\n\nfunction findUpProjectRoot(root: string): string | null {\n return findFileInParents(root, 'package.json');\n}\n\n/**\n * Find a file in the (closest) parent directories.\n * This will recursively look for the file, until the root directory is reached.\n */\nexport function findFileInParents(root: string, fileName: string): string | null {\n for (let dir = root; path.dirname(dir) !== dir; dir = path.dirname(dir)) {\n const file = path.resolve(dir, fileName);\n if (fs.existsSync(file)) {\n return file;\n }\n }\n return null;\n}\n"],"names":["findFileInParents","findUpProjectRootOrAssert","cwd","projectRoot","findUpProjectRoot","CommandError","path","dirname","root","fileName","dir","file","resolve","fs","existsSync"],"mappings":";;;;;;;;;;;IAsBgBA,iBAAiB;eAAjBA;;IAhBAC,yBAAyB;eAAzBA;;;;gEAND;;;;;;;gEACE;;;;;;wBAEY;;;;;;AAGtB,SAASA,0BAA0BC,GAAW;IACnD,MAAMC,cAAcC,kBAAkBF;IACtC,IAAI,CAACC,aAAa;QAChB,MAAM,IAAIE,oBAAY,CAAC,CAAC,qDAAqD,EAAEH,IAAI,CAAC,CAAC;IACvF;IACA,OAAOI,eAAI,CAACC,OAAO,CAACJ;AACtB;AAEA,SAASC,kBAAkBI,IAAY;IACrC,OAAOR,kBAAkBQ,MAAM;AACjC;AAMO,SAASR,kBAAkBQ,IAAY,EAAEC,QAAgB;IAC9D,IAAK,IAAIC,MAAMF,MAAMF,eAAI,CAACC,OAAO,CAACG,SAASA,KAAKA,MAAMJ,eAAI,CAACC,OAAO,CAACG,KAAM;QACvE,MAAMC,OAAOL,eAAI,CAACM,OAAO,CAACF,KAAKD;QAC/B,IAAII,aAAE,CAACC,UAAU,CAACH,OAAO;YACvB,OAAOA;QACT;IACF;IACA,OAAO;AACT"}
|
|
@@ -17,6 +17,7 @@ function _nodecrypto() {
|
|
|
17
17
|
}
|
|
18
18
|
const _FetchClient = require("./clients/FetchClient");
|
|
19
19
|
const _FetchDetachedClient = require("./clients/FetchDetachedClient");
|
|
20
|
+
const _agent = require("./utils/agent");
|
|
20
21
|
const _context = require("./utils/context");
|
|
21
22
|
const _UserSettings = require("../../api/user/UserSettings");
|
|
22
23
|
const _env = require("../env");
|
|
@@ -74,6 +75,7 @@ class Telemetry {
|
|
|
74
75
|
}
|
|
75
76
|
}
|
|
76
77
|
recordInternal(records) {
|
|
78
|
+
const agent = (0, _agent.getAgentTelemetryContext)();
|
|
77
79
|
return this.client.record(records.map((record)=>({
|
|
78
80
|
...record,
|
|
79
81
|
type: 'track',
|
|
@@ -84,6 +86,9 @@ class Telemetry {
|
|
|
84
86
|
context: {
|
|
85
87
|
...this.context,
|
|
86
88
|
sessionId: this.actor.sessionId,
|
|
89
|
+
...agent ? {
|
|
90
|
+
agent
|
|
91
|
+
} : {},
|
|
87
92
|
client: {
|
|
88
93
|
mode: this.client.strategy
|
|
89
94
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/utils/telemetry/Telemetry.ts"],"sourcesContent":["import crypto from 'node:crypto';\n\nimport { FetchClient } from './clients/FetchClient';\nimport { FetchDetachedClient } from './clients/FetchDetachedClient';\nimport { TelemetryClient, TelemetryClientStrategy, TelemetryRecord } from './types';\nimport { createContext } from './utils/context';\nimport { getAnonymousId } from '../../api/user/UserSettings';\nimport { env } from '../env';\n\nconst debug = require('debug')('expo:telemetry') as typeof console.log;\n\ntype TelemetryOptions = {\n /** A locally generated ID, untracable to an actual user */\n anonymousId?: string;\n /** A locally generated ID, per CLI invocation */\n sessionId?: string;\n /** The authenticated user ID, this is used to generate an untracable hash */\n userId?: string;\n /** The underlying telemetry strategy to use */\n strategy?: TelemetryClientStrategy;\n};\n\ntype TelemetryActor = Required<Pick<TelemetryOptions, 'anonymousId' | 'sessionId'>> & {\n /**\n * Hashed version of the user ID, untracable to an actual user.\n * If this value is set to `undefined`, telemetry is considered uninitialized and will wait until its set.\n * If this value is set to `null`, telemetry is considered initialized without an authenticated user.\n * If this value is set to a string, telemetry is considered initialized with an authenticated user.\n */\n userHash?: string | null;\n};\n\nexport class Telemetry {\n private context = createContext();\n private client: TelemetryClient = new FetchDetachedClient();\n private actor: TelemetryActor;\n\n /** A list of all events, recorded before the telemetry was fully initialized */\n private earlyRecords: TelemetryRecord[] = [];\n\n constructor({\n anonymousId = getAnonymousId(),\n sessionId = crypto.randomUUID(),\n userId,\n strategy = 'detached',\n }: TelemetryOptions = {}) {\n this.actor = { anonymousId, sessionId };\n this.setStrategy(env.EXPO_NO_TELEMETRY_DETACH ? 'debug' : strategy);\n\n if (userId) {\n this.initialize({ userId });\n }\n }\n\n get strategy() {\n return this.client.strategy;\n }\n\n setStrategy(strategy: TelemetryOptions['strategy']) {\n // Abort when client is already using the correct strategy\n if (this.client.strategy === strategy) return;\n // Abort when debugging the telemetry\n if (env.EXPO_NO_TELEMETRY_DETACH && strategy !== 'debug') return;\n\n debug('Switching strategy from %s to %s', this.client.strategy, strategy);\n\n // Load and instantiate the correct client, based on strategy\n const client = createClientFromStrategy(strategy);\n // Replace the client, and re-record any pending records\n this.client.abort().forEach((record) => client.record([record]));\n this.client = client;\n\n return this;\n }\n\n get isInitialized() {\n return this.actor.userHash !== undefined;\n }\n\n initialize({ userId }: { userId: string | null }) {\n this.actor.userHash = userId ? hashUserId(userId) : null;\n this.flushEarlyRecords();\n }\n\n private flushEarlyRecords() {\n if (this.earlyRecords.length) {\n this.recordInternal(this.earlyRecords);\n this.earlyRecords = [];\n }\n }\n\n private recordInternal(records: TelemetryRecord[]) {\n return this.client.record(\n records.map((record) => ({\n ...record,\n type: 'track' as const,\n sentAt: new Date(),\n messageId: createMessageId(record),\n anonymousId: this.actor.anonymousId,\n userHash: this.actor.userHash,\n context: {\n ...this.context,\n sessionId: this.actor.sessionId,\n client: { mode: this.client.strategy },\n },\n }))\n );\n }\n\n record(record: TelemetryRecord | TelemetryRecord[]) {\n const records = Array.isArray(record) ? record : [record];\n\n debug('Recording %d event(s)', records.length);\n\n if (!this.isInitialized) {\n this.earlyRecords.push(...records);\n return;\n }\n\n return this.recordInternal(records);\n }\n\n flush() {\n debug('Flushing events...');\n this.flushEarlyRecords();\n return this.client.flush();\n }\n\n flushOnExit() {\n this.setStrategy('detached');\n this.flushEarlyRecords();\n return this.client.flush();\n }\n}\n\nfunction createClientFromStrategy(strategy: TelemetryOptions['strategy']) {\n // When debugging, use the actual Rudderstack client, but lazy load it\n if (env.EXPO_NO_TELEMETRY_DETACH || strategy === 'debug' || strategy === 'instant') {\n return new FetchClient();\n }\n\n return new FetchDetachedClient();\n}\n\n/** Generate a unique message ID using a random hash and UUID */\nfunction createMessageId(record: TelemetryRecord) {\n const uuid = crypto.randomUUID();\n const md5 = crypto.createHash('md5').update(JSON.stringify(record)).digest('hex');\n\n return `node-${md5}-${uuid}`;\n}\n\n/** Hash the user identifier to make it untracable */\nfunction hashUserId(userId: string) {\n return crypto.createHash('sha256').update(userId).digest('hex');\n}\n"],"names":["Telemetry","debug","require","constructor","anonymousId","getAnonymousId","sessionId","crypto","randomUUID","userId","strategy","context","createContext","client","FetchDetachedClient","earlyRecords","actor","setStrategy","env","EXPO_NO_TELEMETRY_DETACH","initialize","createClientFromStrategy","abort","forEach","record","isInitialized","userHash","undefined","hashUserId","flushEarlyRecords","length","recordInternal","records","map","type","sentAt","Date","messageId","createMessageId","mode","Array","isArray","push","flush","flushOnExit","FetchClient","uuid","md5","createHash","update","JSON","stringify","digest"],"mappings":";;;;+
|
|
1
|
+
{"version":3,"sources":["../../../../src/utils/telemetry/Telemetry.ts"],"sourcesContent":["import crypto from 'node:crypto';\n\nimport { FetchClient } from './clients/FetchClient';\nimport { FetchDetachedClient } from './clients/FetchDetachedClient';\nimport { TelemetryClient, TelemetryClientStrategy, TelemetryRecord } from './types';\nimport { getAgentTelemetryContext } from './utils/agent';\nimport { createContext } from './utils/context';\nimport { getAnonymousId } from '../../api/user/UserSettings';\nimport { env } from '../env';\n\nconst debug = require('debug')('expo:telemetry') as typeof console.log;\n\ntype TelemetryOptions = {\n /** A locally generated ID, untracable to an actual user */\n anonymousId?: string;\n /** A locally generated ID, per CLI invocation */\n sessionId?: string;\n /** The authenticated user ID, this is used to generate an untracable hash */\n userId?: string;\n /** The underlying telemetry strategy to use */\n strategy?: TelemetryClientStrategy;\n};\n\ntype TelemetryActor = Required<Pick<TelemetryOptions, 'anonymousId' | 'sessionId'>> & {\n /**\n * Hashed version of the user ID, untracable to an actual user.\n * If this value is set to `undefined`, telemetry is considered uninitialized and will wait until its set.\n * If this value is set to `null`, telemetry is considered initialized without an authenticated user.\n * If this value is set to a string, telemetry is considered initialized with an authenticated user.\n */\n userHash?: string | null;\n};\n\nexport class Telemetry {\n private context = createContext();\n private client: TelemetryClient = new FetchDetachedClient();\n private actor: TelemetryActor;\n\n /** A list of all events, recorded before the telemetry was fully initialized */\n private earlyRecords: TelemetryRecord[] = [];\n\n constructor({\n anonymousId = getAnonymousId(),\n sessionId = crypto.randomUUID(),\n userId,\n strategy = 'detached',\n }: TelemetryOptions = {}) {\n this.actor = { anonymousId, sessionId };\n this.setStrategy(env.EXPO_NO_TELEMETRY_DETACH ? 'debug' : strategy);\n\n if (userId) {\n this.initialize({ userId });\n }\n }\n\n get strategy() {\n return this.client.strategy;\n }\n\n setStrategy(strategy: TelemetryOptions['strategy']) {\n // Abort when client is already using the correct strategy\n if (this.client.strategy === strategy) return;\n // Abort when debugging the telemetry\n if (env.EXPO_NO_TELEMETRY_DETACH && strategy !== 'debug') return;\n\n debug('Switching strategy from %s to %s', this.client.strategy, strategy);\n\n // Load and instantiate the correct client, based on strategy\n const client = createClientFromStrategy(strategy);\n // Replace the client, and re-record any pending records\n this.client.abort().forEach((record) => client.record([record]));\n this.client = client;\n\n return this;\n }\n\n get isInitialized() {\n return this.actor.userHash !== undefined;\n }\n\n initialize({ userId }: { userId: string | null }) {\n this.actor.userHash = userId ? hashUserId(userId) : null;\n this.flushEarlyRecords();\n }\n\n private flushEarlyRecords() {\n if (this.earlyRecords.length) {\n this.recordInternal(this.earlyRecords);\n this.earlyRecords = [];\n }\n }\n\n private recordInternal(records: TelemetryRecord[]) {\n const agent = getAgentTelemetryContext();\n\n return this.client.record(\n records.map((record) => ({\n ...record,\n type: 'track' as const,\n sentAt: new Date(),\n messageId: createMessageId(record),\n anonymousId: this.actor.anonymousId,\n userHash: this.actor.userHash,\n context: {\n ...this.context,\n sessionId: this.actor.sessionId,\n ...(agent ? { agent } : {}),\n client: { mode: this.client.strategy },\n },\n }))\n );\n }\n\n record(record: TelemetryRecord | TelemetryRecord[]) {\n const records = Array.isArray(record) ? record : [record];\n\n debug('Recording %d event(s)', records.length);\n\n if (!this.isInitialized) {\n this.earlyRecords.push(...records);\n return;\n }\n\n return this.recordInternal(records);\n }\n\n flush() {\n debug('Flushing events...');\n this.flushEarlyRecords();\n return this.client.flush();\n }\n\n flushOnExit() {\n this.setStrategy('detached');\n this.flushEarlyRecords();\n return this.client.flush();\n }\n}\n\nfunction createClientFromStrategy(strategy: TelemetryOptions['strategy']) {\n // When debugging, use the actual Rudderstack client, but lazy load it\n if (env.EXPO_NO_TELEMETRY_DETACH || strategy === 'debug' || strategy === 'instant') {\n return new FetchClient();\n }\n\n return new FetchDetachedClient();\n}\n\n/** Generate a unique message ID using a random hash and UUID */\nfunction createMessageId(record: TelemetryRecord) {\n const uuid = crypto.randomUUID();\n const md5 = crypto.createHash('md5').update(JSON.stringify(record)).digest('hex');\n\n return `node-${md5}-${uuid}`;\n}\n\n/** Hash the user identifier to make it untracable */\nfunction hashUserId(userId: string) {\n return crypto.createHash('sha256').update(userId).digest('hex');\n}\n"],"names":["Telemetry","debug","require","constructor","anonymousId","getAnonymousId","sessionId","crypto","randomUUID","userId","strategy","context","createContext","client","FetchDetachedClient","earlyRecords","actor","setStrategy","env","EXPO_NO_TELEMETRY_DETACH","initialize","createClientFromStrategy","abort","forEach","record","isInitialized","userHash","undefined","hashUserId","flushEarlyRecords","length","recordInternal","records","agent","getAgentTelemetryContext","map","type","sentAt","Date","messageId","createMessageId","mode","Array","isArray","push","flush","flushOnExit","FetchClient","uuid","md5","createHash","update","JSON","stringify","digest"],"mappings":"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"}
|
|
@@ -33,7 +33,7 @@ class FetchClient {
|
|
|
33
33
|
this.headers = {
|
|
34
34
|
accept: 'application/json',
|
|
35
35
|
'content-type': 'application/json',
|
|
36
|
-
'user-agent': `expo-cli/${"54.0.
|
|
36
|
+
'user-agent': `expo-cli/${"54.0.27"}`,
|
|
37
37
|
authorization: 'Basic ' + _nodebuffer().Buffer.from(`${target}:`).toString('base64')
|
|
38
38
|
};
|
|
39
39
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "getAgentTelemetryContext", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return getAgentTelemetryContext;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
function _agentclidetector() {
|
|
12
|
+
const data = require("agent-cli-detector");
|
|
13
|
+
_agentclidetector = function() {
|
|
14
|
+
return data;
|
|
15
|
+
};
|
|
16
|
+
return data;
|
|
17
|
+
}
|
|
18
|
+
const debug = require('debug')('expo:telemetry:agent');
|
|
19
|
+
let agentTelemetryContext;
|
|
20
|
+
function getAgentTelemetryContext() {
|
|
21
|
+
if (agentTelemetryContext === undefined) {
|
|
22
|
+
agentTelemetryContext = resolveAgentTelemetryContext();
|
|
23
|
+
}
|
|
24
|
+
return agentTelemetryContext;
|
|
25
|
+
}
|
|
26
|
+
function resolveAgentTelemetryContext() {
|
|
27
|
+
try {
|
|
28
|
+
const { agent, detected } = (0, _agentclidetector().detectAgent)();
|
|
29
|
+
if (!detected || agent == null) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
id: agent.id,
|
|
34
|
+
sessionId: agent.sessionId
|
|
35
|
+
};
|
|
36
|
+
} catch (error) {
|
|
37
|
+
debug('Failed to detect coding agent: %s', (error == null ? void 0 : error.message) ?? error);
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
//# sourceMappingURL=agent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../src/utils/telemetry/utils/agent.ts"],"sourcesContent":["import { detectAgent } from 'agent-cli-detector';\n\nconst debug = require('debug')('expo:telemetry:agent') as typeof console.log;\n\nexport type AgentTelemetryContext = {\n id: string;\n sessionId: string | undefined;\n};\n\nlet agentTelemetryContext: AgentTelemetryContext | null | undefined;\n\nexport function getAgentTelemetryContext(): AgentTelemetryContext | null {\n if (agentTelemetryContext === undefined) {\n agentTelemetryContext = resolveAgentTelemetryContext();\n }\n\n return agentTelemetryContext;\n}\n\nfunction resolveAgentTelemetryContext(): AgentTelemetryContext | null {\n try {\n const { agent, detected } = detectAgent();\n if (!detected || agent == null) {\n return null;\n }\n return { id: agent.id, sessionId: agent.sessionId };\n } catch (error: any) {\n debug('Failed to detect coding agent: %s', error?.message ?? error);\n return null;\n }\n}\n"],"names":["getAgentTelemetryContext","debug","require","agentTelemetryContext","undefined","resolveAgentTelemetryContext","agent","detected","detectAgent","id","sessionId","error","message"],"mappings":";;;;+BAWgBA;;;eAAAA;;;;yBAXY;;;;;;AAE5B,MAAMC,QAAQC,QAAQ,SAAS;AAO/B,IAAIC;AAEG,SAASH;IACd,IAAIG,0BAA0BC,WAAW;QACvCD,wBAAwBE;IAC1B;IAEA,OAAOF;AACT;AAEA,SAASE;IACP,IAAI;QACF,MAAM,EAAEC,KAAK,EAAEC,QAAQ,EAAE,GAAGC,IAAAA,+BAAW;QACvC,IAAI,CAACD,YAAYD,SAAS,MAAM;YAC9B,OAAO;QACT;QACA,OAAO;YAAEG,IAAIH,MAAMG,EAAE;YAAEC,WAAWJ,MAAMI,SAAS;QAAC;IACpD,EAAE,OAAOC,OAAY;QACnBV,MAAM,qCAAqCU,CAAAA,yBAAAA,MAAOC,OAAO,KAAID;QAC7D,OAAO;IACT;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@expo/cli",
|
|
3
|
-
"version": "54.0.
|
|
3
|
+
"version": "54.0.27",
|
|
4
4
|
"description": "The Expo CLI",
|
|
5
5
|
"main": "build/bin/cli",
|
|
6
6
|
"bin": {
|
|
@@ -43,19 +43,19 @@
|
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@0no-co/graphql.web": "^1.0.8",
|
|
45
45
|
"@expo/code-signing-certificates": "^0.0.6",
|
|
46
|
-
"@expo/config": "~12.0.
|
|
47
|
-
"@expo/config-plugins": "~54.0.
|
|
46
|
+
"@expo/config": "~12.0.14",
|
|
47
|
+
"@expo/config-plugins": "~54.0.5",
|
|
48
48
|
"@expo/devcert": "^1.2.1",
|
|
49
|
-
"@expo/env": "~2.0.
|
|
49
|
+
"@expo/env": "~2.0.12",
|
|
50
50
|
"@expo/image-utils": "^0.8.8",
|
|
51
51
|
"@expo/json-file": "^10.0.16",
|
|
52
52
|
"@expo/metro": "~54.2.0",
|
|
53
|
-
"@expo/metro-config": "~54.0.
|
|
53
|
+
"@expo/metro-config": "~54.0.17",
|
|
54
54
|
"@expo/osascript": "^2.3.8",
|
|
55
|
-
"@expo/package-manager": "^1.9.
|
|
55
|
+
"@expo/package-manager": "^1.9.11",
|
|
56
56
|
"@expo/plist": "^0.4.9",
|
|
57
|
-
"@expo/prebuild-config": "^54.0.
|
|
58
|
-
"@expo/schema-utils": "^0.1.
|
|
57
|
+
"@expo/prebuild-config": "^54.0.9",
|
|
58
|
+
"@expo/schema-utils": "^0.1.9",
|
|
59
59
|
"@expo/spawn-async": "^1.7.2",
|
|
60
60
|
"@expo/ws-tunnel": "^1.0.1",
|
|
61
61
|
"@expo/xcpretty": "^4.3.0",
|
|
@@ -63,6 +63,7 @@
|
|
|
63
63
|
"@urql/core": "^5.0.6",
|
|
64
64
|
"@urql/exchange-retry": "^1.3.0",
|
|
65
65
|
"accepts": "^1.3.8",
|
|
66
|
+
"agent-cli-detector": "^0.1.2",
|
|
66
67
|
"arg": "^5.0.2",
|
|
67
68
|
"better-opn": "~3.0.2",
|
|
68
69
|
"bplist-creator": "0.1.0",
|
|
@@ -169,5 +170,5 @@
|
|
|
169
170
|
"tree-kill": "^1.2.2",
|
|
170
171
|
"tsd": "^0.28.1"
|
|
171
172
|
},
|
|
172
|
-
"gitHead": "
|
|
173
|
+
"gitHead": "5b42e3d21e0ac5e086752361ca8a5cb4de53bec1"
|
|
173
174
|
}
|