@expo/cli 56.1.4 → 56.1.6
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/api/user/actions.js +7 -0
- package/build/src/api/user/actions.js.map +1 -1
- package/build/src/api/user/expoSsoLauncher.js +2 -8
- package/build/src/api/user/expoSsoLauncher.js.map +1 -1
- package/build/src/api/user/user.js +12 -0
- package/build/src/api/user/user.js.map +1 -1
- package/build/src/events/index.js +1 -1
- package/build/src/export/embed/exportEmbedAsync.js +1 -1
- package/build/src/export/embed/exportEmbedAsync.js.map +1 -1
- package/build/src/export/embed/exportServer.js +1 -1
- package/build/src/export/embed/exportServer.js.map +1 -1
- package/build/src/export/exportApp.js +1 -1
- package/build/src/export/exportApp.js.map +1 -1
- package/build/src/export/publicFolder.js +19 -1
- package/build/src/export/publicFolder.js.map +1 -1
- package/build/src/login/index.js +25 -5
- package/build/src/login/index.js.map +1 -1
- package/build/src/run/android/resolveLaunchProps.js +4 -1
- package/build/src/run/android/resolveLaunchProps.js.map +1 -1
- package/build/src/start/doctor/dependencies/reactNativeTv.js +149 -0
- package/build/src/start/doctor/dependencies/reactNativeTv.js.map +1 -0
- package/build/src/start/doctor/dependencies/validateDependenciesVersions.js +28 -3
- package/build/src/start/doctor/dependencies/validateDependenciesVersions.js.map +1 -1
- package/build/src/start/platforms/ios/simctl.js +4 -0
- package/build/src/start/platforms/ios/simctl.js.map +1 -1
- package/build/src/start/server/DevToolsPlugin.js +26 -1
- package/build/src/start/server/DevToolsPlugin.js.map +1 -1
- package/build/src/start/server/DevToolsPluginCliExtensionExecutor.js +57 -22
- package/build/src/start/server/DevToolsPluginCliExtensionExecutor.js.map +1 -1
- package/build/src/start/server/DevToolsPluginCliExtensionResults.js +29 -0
- package/build/src/start/server/DevToolsPluginCliExtensionResults.js.map +1 -1
- package/build/src/start/server/MCPDevToolsPluginCLIExtensions.js +15 -5
- package/build/src/start/server/MCPDevToolsPluginCLIExtensions.js.map +1 -1
- package/build/src/start/server/UrlCreator.js +4 -0
- package/build/src/start/server/UrlCreator.js.map +1 -1
- package/build/src/start/server/createMCPDevToolsExtensionSchema.js +13 -1
- package/build/src/start/server/createMCPDevToolsExtensionSchema.js.map +1 -1
- package/build/src/start/server/metro/MetroBundlerDevServer.js +95 -32
- package/build/src/start/server/metro/MetroBundlerDevServer.js.map +1 -1
- package/build/src/start/server/metro/createServerComponentsMiddleware.js +13 -13
- package/build/src/start/server/metro/createServerComponentsMiddleware.js.map +1 -1
- package/build/src/start/server/metro/dev-server/createMessageSocket.js +13 -2
- package/build/src/start/server/metro/dev-server/createMessageSocket.js.map +1 -1
- package/build/src/start/server/metro/dev-server/createMetroMiddleware.js +2 -1
- package/build/src/start/server/metro/dev-server/createMetroMiddleware.js.map +1 -1
- package/build/src/start/server/metro/instantiateMetro.js +5 -4
- package/build/src/start/server/metro/instantiateMetro.js.map +1 -1
- package/build/src/start/server/metro/router.js +10 -1
- package/build/src/start/server/metro/router.js.map +1 -1
- package/build/src/start/server/metro/withMetroMultiPlatform.js +8 -4
- package/build/src/start/server/metro/withMetroMultiPlatform.js.map +1 -1
- package/build/src/start/server/middleware/OpenMiddleware.js +150 -0
- package/build/src/start/server/middleware/OpenMiddleware.js.map +1 -0
- package/build/src/start/server/middleware/RuntimeRedirectMiddleware.js +13 -4
- package/build/src/start/server/middleware/RuntimeRedirectMiddleware.js.map +1 -1
- package/build/src/start/server/middleware/ServeStaticMiddleware.js +2 -9
- package/build/src/start/server/middleware/ServeStaticMiddleware.js.map +1 -1
- package/build/src/start/server/middleware/openHandlers.js +157 -0
- package/build/src/start/server/middleware/openHandlers.js.map +1 -0
- package/build/src/start/server/type-generation/routes.js +1 -3
- package/build/src/start/server/type-generation/routes.js.map +1 -1
- package/build/src/start/server/webTemplate.js +3 -5
- package/build/src/start/server/webTemplate.js.map +1 -1
- package/build/src/utils/net.js +7 -1
- package/build/src/utils/net.js.map +1 -1
- package/build/src/utils/open.js +243 -11
- package/build/src/utils/open.js.map +1 -1
- package/build/src/utils/tar.js +2 -2
- package/build/src/utils/tar.js.map +1 -1
- package/build/src/utils/telemetry/clients/FetchClient.js +1 -1
- package/build/src/utils/telemetry/utils/context.js +1 -1
- package/package.json +17 -18
- package/static/loading-page/index.html +35 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../src/start/doctor/dependencies/validateDependenciesVersions.ts"],"sourcesContent":["import type { ExpoConfig, PackageJSONConfig } from '@expo/config';\nimport assert from 'assert';\nimport chalk from 'chalk';\nimport npmPackageArg from 'npm-package-arg';\nimport semver from 'semver';\nimport semverRangeSubset from 'semver/ranges/subset';\n\nimport type { BundledNativeModules } from './bundledNativeModules';\nimport { getCombinedKnownVersionsAsync } from './getVersionedPackages';\nimport { resolveAllPackageVersionsAsync } from './resolvePackages';\nimport * as Log from '../../../log';\nimport { env } from '../../../utils/env';\n\nconst debug = require('debug')('expo:doctor:dependencies:validate') as typeof console.log;\n\ntype IncorrectDependency = {\n packageName: string;\n packageType: 'dependencies' | 'devDependencies';\n expectedVersionOrRange: string;\n actualVersion: string;\n};\n\ntype DependenciesToCheck = { known: string[]; unknown: string[] };\n\n/**\n * Print a list of incorrect dependency versions.\n * This only checks dependencies when not running in offline mode.\n *\n * @param projectRoot Expo project root.\n * @param exp Expo project config.\n * @param pkg Project's `package.json`.\n * @param packagesToCheck A list of packages to check, if undefined or empty, all will be checked.\n * @returns `true` if there are no incorrect dependencies.\n */\nexport async function validateDependenciesVersionsAsync(\n projectRoot: string,\n exp: Pick<ExpoConfig, 'sdkVersion'>,\n pkg: PackageJSONConfig,\n packagesToCheck?: string[]\n): Promise<boolean | null> {\n if (env.EXPO_OFFLINE) {\n Log.warn('Skipping dependency validation in offline mode');\n return null;\n } else if (env.EXPO_NO_DEPENDENCY_VALIDATION) {\n debug('Dependency validation is disabled through EXPO_NO_DEPENDENCY_VALIDATION=1');\n return null;\n }\n\n const incorrectDeps = await getVersionedDependenciesAsync(projectRoot, exp, pkg, packagesToCheck);\n return logIncorrectDependencies(incorrectDeps);\n}\n\nfunction logInvalidDependency({\n packageName,\n expectedVersionOrRange,\n actualVersion,\n}: IncorrectDependency) {\n Log.warn(\n chalk` {bold ${packageName}}{cyan @}{red ${actualVersion}} - expected version: {green ${expectedVersionOrRange}}`\n );\n}\n\nexport function logIncorrectDependencies(incorrectDeps: IncorrectDependency[]) {\n if (!incorrectDeps.length) {\n return true;\n }\n\n Log.warn(\n chalk`The following packages should be updated for best compatibility with the installed {bold expo} version:`\n );\n incorrectDeps.forEach((dep) => logInvalidDependency(dep));\n\n Log.warn(\n 'Your project may not work correctly until you install the expected versions of the packages.'\n );\n\n return false;\n}\n\n/**\n * Return a list of versioned dependencies for the project SDK version.\n *\n * @param projectRoot Expo project root.\n * @param exp Expo project config.\n * @param pkg Project's `package.json`.\n * @param packagesToCheck A list of packages to check, if undefined or empty, all will be checked.\n * @returns A list of incorrect dependencies.\n */\nexport async function getVersionedDependenciesAsync(\n projectRoot: string,\n exp: Pick<ExpoConfig, 'sdkVersion'>,\n pkg: PackageJSONConfig,\n packagesToCheck?: string[]\n): Promise<IncorrectDependency[]> {\n // This should never happen under normal circumstances since\n // the CLI is versioned in the `expo` package.\n assert(exp.sdkVersion, 'SDK Version is missing');\n\n // Get from both endpoints and combine the known package versions.\n const combinedKnownPackages = await getCombinedKnownVersionsAsync({\n projectRoot,\n sdkVersion: exp.sdkVersion,\n });\n // debug(`Known dependencies: %O`, combinedKnownPackages);\n\n const resolvedDependencies = packagesToCheck?.length\n ? // Diff the provided packages to ensure we only check against installed packages.\n getFilteredObject(packagesToCheck, { ...pkg.dependencies, ...pkg.devDependencies })\n : // If no packages are provided, check against the `package.json` `dependencies` + `devDependencies` object.\n { ...pkg.dependencies, ...pkg.devDependencies };\n debug(`Checking dependencies for ${exp.sdkVersion}: %O`, resolvedDependencies);\n\n // intersection of packages from package.json and bundled native modules\n const { known: resolvedPackagesToCheck, unknown } = getPackagesToCheck(\n combinedKnownPackages,\n resolvedDependencies\n );\n debug(`Comparing known versions: %O`, resolvedPackagesToCheck);\n debug(`Skipping packages that cannot be versioned automatically: %O`, unknown);\n // read package versions from the file system (node_modules)\n const packageVersions = await resolveAllPackageVersionsAsync(\n projectRoot,\n resolvedPackagesToCheck\n );\n debug(`Package versions: %O`, packageVersions);\n // find incorrect dependencies by comparing the actual package versions with the bundled native module version ranges\n let incorrectDeps = findIncorrectDependencies(pkg, packageVersions, combinedKnownPackages);\n debug(`Incorrect dependencies: %O`, incorrectDeps);\n\n if (pkg?.expo?.install?.exclude) {\n const packagesToExclude = pkg.expo.install.exclude;\n\n // Parse the exclude list to ensure we can factor in any specified version ranges\n const parsedPackagesToExclude = packagesToExclude.reduce(\n (acc: Record<string, npmPackageArg.Result>, packageName: string) => {\n const npaResult = npmPackageArg(packageName);\n if (typeof npaResult.name === 'string') {\n acc[npaResult.name] = npaResult;\n } else {\n acc[packageName] = npaResult;\n }\n return acc;\n },\n {}\n );\n\n const incorrectAndExcludedDeps = incorrectDeps\n .filter((dep) => {\n if (parsedPackagesToExclude[dep.packageName]) {\n const { name, raw, rawSpec, type } = parsedPackagesToExclude[dep.packageName];\n const suggestedRange = combinedKnownPackages[name]!;\n\n // If only the package name itself is specified, then we keep it in the exclude list\n if (name === raw) {\n return true;\n } else if (type === 'version') {\n return suggestedRange === rawSpec;\n } else if (type === 'range') {\n // Fall through exclusions if the suggested range is invalid\n if (!semver.validRange(suggestedRange)) {\n debug(\n `Invalid semver range in combined known packages for package ${name} in expo.install.exclude: %O`,\n suggestedRange\n );\n return false;\n }\n\n return semverRangeSubset(suggestedRange, rawSpec);\n } else {\n debug(\n `Unsupported npm package argument type for package ${name} in expo.install.exclude: %O`,\n type\n );\n }\n }\n\n return false;\n })\n .map((dep) => dep.packageName);\n\n debug(\n `Incorrect dependency warnings filtered out by expo.install.exclude: %O`,\n incorrectAndExcludedDeps\n );\n incorrectDeps = incorrectDeps.filter(\n (dep) => !incorrectAndExcludedDeps.includes(dep.packageName)\n );\n }\n\n return incorrectDeps;\n}\n\nfunction getFilteredObject(keys: string[], object: Record<string, string>) {\n return keys.reduce<Record<string, string>>((acc, key) => {\n acc[key] = object[key]!;\n return acc;\n }, {});\n}\n\nfunction getPackagesToCheck(\n bundledNativeModules: BundledNativeModules,\n dependencies?: Record<string, string> | null\n): DependenciesToCheck {\n const dependencyNames = Object.keys(dependencies ?? {});\n const known: string[] = [];\n const unknown: string[] = [];\n for (const dependencyName of dependencyNames) {\n if (dependencyName in bundledNativeModules) {\n known.push(dependencyName);\n } else {\n unknown.push(dependencyName);\n }\n }\n return { known, unknown };\n}\n\nfunction findIncorrectDependencies(\n pkg: PackageJSONConfig,\n packageVersions: Record<string, string>,\n bundledNativeModules: BundledNativeModules\n): IncorrectDependency[] {\n const packages = Object.keys(packageVersions);\n const incorrectDeps: IncorrectDependency[] = [];\n for (const packageName of packages) {\n const expectedVersionOrRange = bundledNativeModules[packageName]!;\n const actualVersion = packageVersions[packageName]!;\n if (isDependencyVersionIncorrect(packageName, actualVersion, expectedVersionOrRange)) {\n incorrectDeps.push({\n packageName,\n packageType: findDependencyType(pkg, packageName),\n expectedVersionOrRange,\n actualVersion,\n });\n }\n }\n return incorrectDeps;\n}\n\nexport function isDependencyVersionIncorrect(\n packageName: string,\n actualVersion: string,\n expectedVersionOrRange?: string\n) {\n if (!expectedVersionOrRange) {\n return false;\n }\n\n // we never want to go backwards with the expo patch version\n if (packageName === 'expo') {\n return semver.ltr(actualVersion, expectedVersionOrRange);\n }\n\n // For all other packages, check if the actual version satisfies the expected range\n const satisfies = semver.satisfies(actualVersion, expectedVersionOrRange, {\n includePrerelease: true,\n });\n\n return !satisfies;\n}\n\nfunction findDependencyType(\n pkg: PackageJSONConfig,\n packageName: string\n): IncorrectDependency['packageType'] {\n if (pkg.devDependencies && packageName in pkg.devDependencies) {\n return 'devDependencies';\n }\n\n return 'dependencies';\n}\n"],"names":["getVersionedDependenciesAsync","isDependencyVersionIncorrect","logIncorrectDependencies","validateDependenciesVersionsAsync","debug","require","projectRoot","exp","pkg","packagesToCheck","env","EXPO_OFFLINE","Log","warn","EXPO_NO_DEPENDENCY_VALIDATION","incorrectDeps","logInvalidDependency","packageName","expectedVersionOrRange","actualVersion","chalk","length","forEach","dep","assert","sdkVersion","combinedKnownPackages","getCombinedKnownVersionsAsync","resolvedDependencies","getFilteredObject","dependencies","devDependencies","known","resolvedPackagesToCheck","unknown","getPackagesToCheck","packageVersions","resolveAllPackageVersionsAsync","findIncorrectDependencies","expo","install","exclude","packagesToExclude","parsedPackagesToExclude","reduce","acc","npaResult","npmPackageArg","name","incorrectAndExcludedDeps","filter","raw","rawSpec","type","suggestedRange","semver","validRange","semverRangeSubset","map","includes","keys","object","key","bundledNativeModules","dependencyNames","Object","dependencyName","push","packages","packageType","findDependencyType","ltr","satisfies","includePrerelease"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../src/start/doctor/dependencies/validateDependenciesVersions.ts"],"sourcesContent":["import type { ExpoConfig, PackageJSONConfig } from '@expo/config';\nimport assert from 'assert';\nimport chalk from 'chalk';\nimport npmPackageArg from 'npm-package-arg';\nimport semver from 'semver';\nimport semverRangeSubset from 'semver/ranges/subset';\n\nimport type { BundledNativeModules } from './bundledNativeModules';\nimport { getCombinedKnownVersionsAsync } from './getVersionedPackages';\nimport {\n correctReactNativeTvVersion,\n isReactNativeTvProjectAsync,\n reactNativeTvVersionMatchesBundled,\n} from './reactNativeTv';\nimport { resolveAllPackageVersionsAsync } from './resolvePackages';\nimport * as Log from '../../../log';\nimport { env } from '../../../utils/env';\n\nconst debug = require('debug')('expo:doctor:dependencies:validate') as typeof console.log;\n\nexport type IncorrectDependency = {\n packageName: string;\n packageType: 'dependencies' | 'devDependencies';\n expectedVersionOrRange: string;\n actualVersion: string;\n};\n\ntype DependenciesToCheck = { known: string[]; unknown: string[] };\n\n/**\n * Print a list of incorrect dependency versions.\n * This only checks dependencies when not running in offline mode.\n *\n * @param projectRoot Expo project root.\n * @param exp Expo project config.\n * @param pkg Project's `package.json`.\n * @param packagesToCheck A list of packages to check, if undefined or empty, all will be checked.\n * @returns `true` if there are no incorrect dependencies.\n */\nexport async function validateDependenciesVersionsAsync(\n projectRoot: string,\n exp: Pick<ExpoConfig, 'sdkVersion'>,\n pkg: PackageJSONConfig,\n packagesToCheck?: string[]\n): Promise<boolean | null> {\n if (env.EXPO_OFFLINE) {\n Log.warn('Skipping dependency validation in offline mode');\n return null;\n } else if (env.EXPO_NO_DEPENDENCY_VALIDATION) {\n debug('Dependency validation is disabled through EXPO_NO_DEPENDENCY_VALIDATION=1');\n return null;\n }\n\n const incorrectDeps = await getVersionedDependenciesAsync(projectRoot, exp, pkg, packagesToCheck);\n return logIncorrectDependencies(incorrectDeps);\n}\n\nfunction logInvalidDependency({\n packageName,\n expectedVersionOrRange,\n actualVersion,\n}: IncorrectDependency) {\n Log.warn(\n chalk` {bold ${packageName}}{cyan @}{red ${actualVersion}} - expected version: {green ${expectedVersionOrRange}}`\n );\n}\n\nexport function logIncorrectDependencies(incorrectDeps: IncorrectDependency[]) {\n if (!incorrectDeps.length) {\n return true;\n }\n\n Log.warn(\n chalk`The following packages should be updated for best compatibility with the installed {bold expo} version:`\n );\n incorrectDeps.forEach((dep) => logInvalidDependency(dep));\n\n Log.warn(\n 'Your project may not work correctly until you install the expected versions of the packages.'\n );\n\n return false;\n}\n\n/**\n * Return a list of versioned dependencies for the project SDK version.\n *\n * @param projectRoot Expo project root.\n * @param exp Expo project config.\n * @param pkg Project's `package.json`.\n * @param packagesToCheck A list of packages to check, if undefined or empty, all will be checked.\n * @returns A list of incorrect dependencies.\n */\nexport async function getVersionedDependenciesAsync(\n projectRoot: string,\n exp: Pick<ExpoConfig, 'sdkVersion'>,\n pkg: PackageJSONConfig,\n packagesToCheck?: string[]\n): Promise<IncorrectDependency[]> {\n // This should never happen under normal circumstances since\n // the CLI is versioned in the `expo` package.\n assert(exp.sdkVersion, 'SDK Version is missing');\n\n // Get from both endpoints and combine the known package versions.\n const combinedKnownPackages = await getCombinedKnownVersionsAsync({\n projectRoot,\n sdkVersion: exp.sdkVersion,\n });\n // debug(`Known dependencies: %O`, combinedKnownPackages);\n\n const resolvedDependencies = packagesToCheck?.length\n ? // Diff the provided packages to ensure we only check against installed packages.\n getFilteredObject(packagesToCheck, { ...pkg.dependencies, ...pkg.devDependencies })\n : // If no packages are provided, check against the `package.json` `dependencies` + `devDependencies` object.\n { ...pkg.dependencies, ...pkg.devDependencies };\n debug(`Checking dependencies for ${exp.sdkVersion}: %O`, resolvedDependencies);\n\n // intersection of packages from package.json and bundled native modules\n const { known: resolvedPackagesToCheck, unknown } = getPackagesToCheck(\n combinedKnownPackages,\n resolvedDependencies\n );\n debug(`Comparing known versions: %O`, resolvedPackagesToCheck);\n debug(`Skipping packages that cannot be versioned automatically: %O`, unknown);\n // read package versions from the file system (node_modules)\n const packageVersions = await resolveAllPackageVersionsAsync(\n projectRoot,\n resolvedPackagesToCheck\n );\n debug(`Package versions: %O`, packageVersions);\n // Detect TV projects via the installed `react-native` package's `name`, since\n // `pkg.dependencies['react-native']` can vary across package managers.\n const isReactNativeTvProject = await isReactNativeTvProjectAsync(projectRoot);\n debug(`react-native-tvos project: %O`, isReactNativeTvProject);\n // find incorrect dependencies by comparing the actual package versions with the bundled native module version ranges\n let incorrectDeps = await findIncorrectDependencies(\n pkg,\n packageVersions,\n combinedKnownPackages,\n isReactNativeTvProject\n );\n debug(`Incorrect dependencies: %O`, incorrectDeps);\n\n if (pkg?.expo?.install?.exclude) {\n const packagesToExclude = pkg.expo.install.exclude;\n\n // Parse the exclude list to ensure we can factor in any specified version ranges\n const parsedPackagesToExclude = packagesToExclude.reduce(\n (acc: Record<string, npmPackageArg.Result>, packageName: string) => {\n const npaResult = npmPackageArg(packageName);\n if (typeof npaResult.name === 'string') {\n acc[npaResult.name] = npaResult;\n } else {\n acc[packageName] = npaResult;\n }\n return acc;\n },\n {}\n );\n\n const incorrectAndExcludedDeps = incorrectDeps\n .filter((dep) => {\n if (parsedPackagesToExclude[dep.packageName]) {\n const { name, raw, rawSpec, type } = parsedPackagesToExclude[dep.packageName];\n const suggestedRange = combinedKnownPackages[name]!;\n\n // If only the package name itself is specified, then we keep it in the exclude list\n if (name === raw) {\n return true;\n } else if (type === 'version') {\n return suggestedRange === rawSpec;\n } else if (type === 'range') {\n // Fall through exclusions if the suggested range is invalid\n if (!semver.validRange(suggestedRange)) {\n debug(\n `Invalid semver range in combined known packages for package ${name} in expo.install.exclude: %O`,\n suggestedRange\n );\n return false;\n }\n\n return semverRangeSubset(suggestedRange, rawSpec);\n } else {\n debug(\n `Unsupported npm package argument type for package ${name} in expo.install.exclude: %O`,\n type\n );\n }\n }\n\n return false;\n })\n .map((dep) => dep.packageName);\n\n debug(\n `Incorrect dependency warnings filtered out by expo.install.exclude: %O`,\n incorrectAndExcludedDeps\n );\n incorrectDeps = incorrectDeps.filter(\n (dep) => !incorrectAndExcludedDeps.includes(dep.packageName)\n );\n }\n\n return incorrectDeps;\n}\n\nfunction getFilteredObject(keys: string[], object: Record<string, string>) {\n return keys.reduce<Record<string, string>>((acc, key) => {\n acc[key] = object[key]!;\n return acc;\n }, {});\n}\n\nfunction getPackagesToCheck(\n bundledNativeModules: BundledNativeModules,\n dependencies?: Record<string, string> | null\n): DependenciesToCheck {\n const dependencyNames = Object.keys(dependencies ?? {});\n const known: string[] = [];\n const unknown: string[] = [];\n for (const dependencyName of dependencyNames) {\n if (dependencyName in bundledNativeModules) {\n known.push(dependencyName);\n } else {\n unknown.push(dependencyName);\n }\n }\n return { known, unknown };\n}\n\nasync function findIncorrectDependencies(\n pkg: PackageJSONConfig,\n packageVersions: Record<string, string>,\n bundledNativeModules: BundledNativeModules,\n isReactNativeTvProject: boolean\n): Promise<IncorrectDependency[]> {\n // For TV projects, compare the installed `major.minor` against the bundled\n // `react-native` `major.minor` — `react-native-tvos` follows the upstream\n // minor lines via a `<major>.<minor>-stable` dist-tag, so a matching minor\n // means the TV variant is up to date.\n //\n // Resolve the install spec for the TV variant once up front so the inner\n // loop stays synchronous and we don't hit the npm registry per dependency.\n const bundledReactNativeVersion = bundledNativeModules['react-native'];\n const reactNativeTvExpectedVersionOrRange =\n isReactNativeTvProject && bundledReactNativeVersion\n ? await correctReactNativeTvVersion(bundledReactNativeVersion)\n : undefined;\n\n const packages = Object.keys(packageVersions);\n const incorrectDeps: IncorrectDependency[] = [];\n for (const packageName of packages) {\n const actualVersion = packageVersions[packageName]!;\n\n if (isReactNativeTvProject && packageName === 'react-native') {\n if (\n bundledReactNativeVersion &&\n reactNativeTvExpectedVersionOrRange &&\n !reactNativeTvVersionMatchesBundled(actualVersion, bundledReactNativeVersion)\n ) {\n incorrectDeps.push({\n packageName,\n packageType: findDependencyType(pkg, packageName),\n expectedVersionOrRange: reactNativeTvExpectedVersionOrRange,\n actualVersion,\n });\n }\n continue;\n }\n\n const expectedVersionOrRange = bundledNativeModules[packageName]!;\n if (isDependencyVersionIncorrect(packageName, actualVersion, expectedVersionOrRange)) {\n incorrectDeps.push({\n packageName,\n packageType: findDependencyType(pkg, packageName),\n expectedVersionOrRange,\n actualVersion,\n });\n }\n }\n return incorrectDeps;\n}\n\nexport function isDependencyVersionIncorrect(\n packageName: string,\n actualVersion: string,\n expectedVersionOrRange?: string\n) {\n if (!expectedVersionOrRange) {\n return false;\n }\n\n // we never want to go backwards with the expo patch version\n if (packageName === 'expo') {\n return semver.ltr(actualVersion, expectedVersionOrRange);\n }\n\n // For all other packages, check if the actual version satisfies the expected range\n const satisfies = semver.satisfies(actualVersion, expectedVersionOrRange, {\n includePrerelease: true,\n });\n\n return !satisfies;\n}\n\nfunction findDependencyType(\n pkg: PackageJSONConfig,\n packageName: string\n): IncorrectDependency['packageType'] {\n if (pkg.devDependencies && packageName in pkg.devDependencies) {\n return 'devDependencies';\n }\n\n return 'dependencies';\n}\n"],"names":["getVersionedDependenciesAsync","isDependencyVersionIncorrect","logIncorrectDependencies","validateDependenciesVersionsAsync","debug","require","projectRoot","exp","pkg","packagesToCheck","env","EXPO_OFFLINE","Log","warn","EXPO_NO_DEPENDENCY_VALIDATION","incorrectDeps","logInvalidDependency","packageName","expectedVersionOrRange","actualVersion","chalk","length","forEach","dep","assert","sdkVersion","combinedKnownPackages","getCombinedKnownVersionsAsync","resolvedDependencies","getFilteredObject","dependencies","devDependencies","known","resolvedPackagesToCheck","unknown","getPackagesToCheck","packageVersions","resolveAllPackageVersionsAsync","isReactNativeTvProject","isReactNativeTvProjectAsync","findIncorrectDependencies","expo","install","exclude","packagesToExclude","parsedPackagesToExclude","reduce","acc","npaResult","npmPackageArg","name","incorrectAndExcludedDeps","filter","raw","rawSpec","type","suggestedRange","semver","validRange","semverRangeSubset","map","includes","keys","object","key","bundledNativeModules","dependencyNames","Object","dependencyName","push","bundledReactNativeVersion","reactNativeTvExpectedVersionOrRange","correctReactNativeTvVersion","undefined","packages","reactNativeTvVersionMatchesBundled","packageType","findDependencyType","ltr","satisfies","includePrerelease"],"mappings":";;;;;;;;;;;QA6FsBA;eAAAA;;QA8LNC;eAAAA;;QAxNAC;eAAAA;;QA5BMC;eAAAA;;;;gEAtCH;;;;;;;gEACD;;;;;;;gEACQ;;;;;;;gEACP;;;;;;;gEACW;;;;;;sCAGgB;+BAKvC;iCACwC;6DAC1B;qBACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEpB,MAAMC,QAAQC,QAAQ,SAAS;AAqBxB,eAAeF,kCACpBG,WAAmB,EACnBC,GAAmC,EACnCC,GAAsB,EACtBC,eAA0B;IAE1B,IAAIC,QAAG,CAACC,YAAY,EAAE;QACpBC,KAAIC,IAAI,CAAC;QACT,OAAO;IACT,OAAO,IAAIH,QAAG,CAACI,6BAA6B,EAAE;QAC5CV,MAAM;QACN,OAAO;IACT;IAEA,MAAMW,gBAAgB,MAAMf,8BAA8BM,aAAaC,KAAKC,KAAKC;IACjF,OAAOP,yBAAyBa;AAClC;AAEA,SAASC,qBAAqB,EAC5BC,WAAW,EACXC,sBAAsB,EACtBC,aAAa,EACO;IACpBP,KAAIC,IAAI,CACNO,IAAAA,gBAAK,CAAA,CAAC,QAAQ,EAAEH,YAAY,cAAc,EAAEE,cAAc,6BAA6B,EAAED,uBAAuB,CAAC,CAAC;AAEtH;AAEO,SAAShB,yBAAyBa,aAAoC;IAC3E,IAAI,CAACA,cAAcM,MAAM,EAAE;QACzB,OAAO;IACT;IAEAT,KAAIC,IAAI,CACNO,IAAAA,gBAAK,CAAA,CAAC,uGAAuG,CAAC;IAEhHL,cAAcO,OAAO,CAAC,CAACC,MAAQP,qBAAqBO;IAEpDX,KAAIC,IAAI,CACN;IAGF,OAAO;AACT;AAWO,eAAeb,8BACpBM,WAAmB,EACnBC,GAAmC,EACnCC,GAAsB,EACtBC,eAA0B;QA8CtBD,mBAAAA;IA5CJ,4DAA4D;IAC5D,8CAA8C;IAC9CgB,IAAAA,iBAAM,EAACjB,IAAIkB,UAAU,EAAE;IAEvB,kEAAkE;IAClE,MAAMC,wBAAwB,MAAMC,IAAAA,mDAA6B,EAAC;QAChErB;QACAmB,YAAYlB,IAAIkB,UAAU;IAC5B;IACA,0DAA0D;IAE1D,MAAMG,uBAAuBnB,CAAAA,mCAAAA,gBAAiBY,MAAM,IAEhDQ,kBAAkBpB,iBAAiB;QAAE,GAAGD,IAAIsB,YAAY;QAAE,GAAGtB,IAAIuB,eAAe;IAAC,KAEjF;QAAE,GAAGvB,IAAIsB,YAAY;QAAE,GAAGtB,IAAIuB,eAAe;IAAC;IAClD3B,MAAM,CAAC,0BAA0B,EAAEG,IAAIkB,UAAU,CAAC,IAAI,CAAC,EAAEG;IAEzD,wEAAwE;IACxE,MAAM,EAAEI,OAAOC,uBAAuB,EAAEC,OAAO,EAAE,GAAGC,mBAClDT,uBACAE;IAEFxB,MAAM,CAAC,4BAA4B,CAAC,EAAE6B;IACtC7B,MAAM,CAAC,4DAA4D,CAAC,EAAE8B;IACtE,4DAA4D;IAC5D,MAAME,kBAAkB,MAAMC,IAAAA,+CAA8B,EAC1D/B,aACA2B;IAEF7B,MAAM,CAAC,oBAAoB,CAAC,EAAEgC;IAC9B,8EAA8E;IAC9E,uEAAuE;IACvE,MAAME,yBAAyB,MAAMC,IAAAA,0CAA2B,EAACjC;IACjEF,MAAM,CAAC,6BAA6B,CAAC,EAAEkC;IACvC,qHAAqH;IACrH,IAAIvB,gBAAgB,MAAMyB,0BACxBhC,KACA4B,iBACAV,uBACAY;IAEFlC,MAAM,CAAC,0BAA0B,CAAC,EAAEW;IAEpC,IAAIP,wBAAAA,YAAAA,IAAKiC,IAAI,sBAATjC,oBAAAA,UAAWkC,OAAO,qBAAlBlC,kBAAoBmC,OAAO,EAAE;QAC/B,MAAMC,oBAAoBpC,IAAIiC,IAAI,CAACC,OAAO,CAACC,OAAO;QAElD,iFAAiF;QACjF,MAAME,0BAA0BD,kBAAkBE,MAAM,CACtD,CAACC,KAA2C9B;YAC1C,MAAM+B,YAAYC,IAAAA,wBAAa,EAAChC;YAChC,IAAI,OAAO+B,UAAUE,IAAI,KAAK,UAAU;gBACtCH,GAAG,CAACC,UAAUE,IAAI,CAAC,GAAGF;YACxB,OAAO;gBACLD,GAAG,CAAC9B,YAAY,GAAG+B;YACrB;YACA,OAAOD;QACT,GACA,CAAC;QAGH,MAAMI,2BAA2BpC,cAC9BqC,MAAM,CAAC,CAAC7B;YACP,IAAIsB,uBAAuB,CAACtB,IAAIN,WAAW,CAAC,EAAE;gBAC5C,MAAM,EAAEiC,IAAI,EAAEG,GAAG,EAAEC,OAAO,EAAEC,IAAI,EAAE,GAAGV,uBAAuB,CAACtB,IAAIN,WAAW,CAAC;gBAC7E,MAAMuC,iBAAiB9B,qBAAqB,CAACwB,KAAK;gBAElD,oFAAoF;gBACpF,IAAIA,SAASG,KAAK;oBAChB,OAAO;gBACT,OAAO,IAAIE,SAAS,WAAW;oBAC7B,OAAOC,mBAAmBF;gBAC5B,OAAO,IAAIC,SAAS,SAAS;oBAC3B,4DAA4D;oBAC5D,IAAI,CAACE,iBAAM,CAACC,UAAU,CAACF,iBAAiB;wBACtCpD,MACE,CAAC,4DAA4D,EAAE8C,KAAK,4BAA4B,CAAC,EACjGM;wBAEF,OAAO;oBACT;oBAEA,OAAOG,IAAAA,iBAAiB,EAACH,gBAAgBF;gBAC3C,OAAO;oBACLlD,MACE,CAAC,kDAAkD,EAAE8C,KAAK,4BAA4B,CAAC,EACvFK;gBAEJ;YACF;YAEA,OAAO;QACT,GACCK,GAAG,CAAC,CAACrC,MAAQA,IAAIN,WAAW;QAE/Bb,MACE,CAAC,sEAAsE,CAAC,EACxE+C;QAEFpC,gBAAgBA,cAAcqC,MAAM,CAClC,CAAC7B,MAAQ,CAAC4B,yBAAyBU,QAAQ,CAACtC,IAAIN,WAAW;IAE/D;IAEA,OAAOF;AACT;AAEA,SAASc,kBAAkBiC,IAAc,EAAEC,MAA8B;IACvE,OAAOD,KAAKhB,MAAM,CAAyB,CAACC,KAAKiB;QAC/CjB,GAAG,CAACiB,IAAI,GAAGD,MAAM,CAACC,IAAI;QACtB,OAAOjB;IACT,GAAG,CAAC;AACN;AAEA,SAASZ,mBACP8B,oBAA0C,EAC1CnC,YAA4C;IAE5C,MAAMoC,kBAAkBC,OAAOL,IAAI,CAAChC,gBAAgB,CAAC;IACrD,MAAME,QAAkB,EAAE;IAC1B,MAAME,UAAoB,EAAE;IAC5B,KAAK,MAAMkC,kBAAkBF,gBAAiB;QAC5C,IAAIE,kBAAkBH,sBAAsB;YAC1CjC,MAAMqC,IAAI,CAACD;QACb,OAAO;YACLlC,QAAQmC,IAAI,CAACD;QACf;IACF;IACA,OAAO;QAAEpC;QAAOE;IAAQ;AAC1B;AAEA,eAAeM,0BACbhC,GAAsB,EACtB4B,eAAuC,EACvC6B,oBAA0C,EAC1C3B,sBAA+B;IAE/B,2EAA2E;IAC3E,0EAA0E;IAC1E,2EAA2E;IAC3E,sCAAsC;IACtC,EAAE;IACF,yEAAyE;IACzE,2EAA2E;IAC3E,MAAMgC,4BAA4BL,oBAAoB,CAAC,eAAe;IACtE,MAAMM,sCACJjC,0BAA0BgC,4BACtB,MAAME,IAAAA,0CAA2B,EAACF,6BAClCG;IAEN,MAAMC,WAAWP,OAAOL,IAAI,CAAC1B;IAC7B,MAAMrB,gBAAuC,EAAE;IAC/C,KAAK,MAAME,eAAeyD,SAAU;QAClC,MAAMvD,gBAAgBiB,eAAe,CAACnB,YAAY;QAElD,IAAIqB,0BAA0BrB,gBAAgB,gBAAgB;YAC5D,IACEqD,6BACAC,uCACA,CAACI,IAAAA,iDAAkC,EAACxD,eAAemD,4BACnD;gBACAvD,cAAcsD,IAAI,CAAC;oBACjBpD;oBACA2D,aAAaC,mBAAmBrE,KAAKS;oBACrCC,wBAAwBqD;oBACxBpD;gBACF;YACF;YACA;QACF;QAEA,MAAMD,yBAAyB+C,oBAAoB,CAAChD,YAAY;QAChE,IAAIhB,6BAA6BgB,aAAaE,eAAeD,yBAAyB;YACpFH,cAAcsD,IAAI,CAAC;gBACjBpD;gBACA2D,aAAaC,mBAAmBrE,KAAKS;gBACrCC;gBACAC;YACF;QACF;IACF;IACA,OAAOJ;AACT;AAEO,SAASd,6BACdgB,WAAmB,EACnBE,aAAqB,EACrBD,sBAA+B;IAE/B,IAAI,CAACA,wBAAwB;QAC3B,OAAO;IACT;IAEA,4DAA4D;IAC5D,IAAID,gBAAgB,QAAQ;QAC1B,OAAOwC,iBAAM,CAACqB,GAAG,CAAC3D,eAAeD;IACnC;IAEA,mFAAmF;IACnF,MAAM6D,YAAYtB,iBAAM,CAACsB,SAAS,CAAC5D,eAAeD,wBAAwB;QACxE8D,mBAAmB;IACrB;IAEA,OAAO,CAACD;AACV;AAEA,SAASF,mBACPrE,GAAsB,EACtBS,WAAmB;IAEnB,IAAIT,IAAIuB,eAAe,IAAId,eAAeT,IAAIuB,eAAe,EAAE;QAC7D,OAAO;IACT;IAEA,OAAO;AACT"}
|
|
@@ -88,6 +88,7 @@ const _xcrun = require("./xcrun");
|
|
|
88
88
|
const _log = /*#__PURE__*/ _interop_require_wildcard(require("../../../log"));
|
|
89
89
|
const _errors = require("../../../utils/errors");
|
|
90
90
|
const _fn = require("../../../utils/fn");
|
|
91
|
+
const _link = require("../../../utils/link");
|
|
91
92
|
const _plist = require("../../../utils/plist");
|
|
92
93
|
const _profile = require("../../../utils/profile");
|
|
93
94
|
function _interop_require_default(obj) {
|
|
@@ -305,6 +306,9 @@ async function bootDeviceAsync(device) {
|
|
|
305
306
|
} catch (error) {
|
|
306
307
|
var _error_stderr;
|
|
307
308
|
if (!((_error_stderr = error.stderr) == null ? void 0 : _error_stderr.match(/Unable to boot device in current state: Booted/))) {
|
|
309
|
+
error.message += `\n${(0, _link.learnMore)('https://docs.expo.dev/workflow/ios-simulator/#troubleshooting', {
|
|
310
|
+
learnMoreMessage: 'Troubleshooting guide'
|
|
311
|
+
})}`;
|
|
308
312
|
throw error;
|
|
309
313
|
}
|
|
310
314
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../src/start/platforms/ios/simctl.ts"],"sourcesContent":["import type { SpawnOptions, SpawnResult } from '@expo/spawn-async';\nimport spawnAsync from '@expo/spawn-async';\nimport bplistCreator from 'bplist-creator';\nimport fs from 'fs';\nimport os from 'os';\nimport path from 'path';\n\nimport { isSpawnResultError, xcrunAsync } from './xcrun';\nimport * as Log from '../../../log';\nimport { CommandError } from '../../../utils/errors';\nimport { memoize } from '../../../utils/fn';\nimport { parsePlistAsync } from '../../../utils/plist';\nimport { profile } from '../../../utils/profile';\n\nconst debug = require('debug')('expo:simctl') as typeof console.log;\n\ntype DeviceState = 'Shutdown' | 'Booted';\n\nexport type OSType = 'iOS' | 'tvOS' | 'watchOS' | 'macOS' | 'xrOS';\n\nexport type Device = {\n availabilityError?: 'runtime profile not found';\n /** '/Users/name/Library/Developer/CoreSimulator/Devices/00E55DC0-0364-49DF-9EC6-77BE587137D4/data' */\n dataPath: string;\n /** @example `2811236352` */\n dataPathSize?: number;\n /** '/Users/name/Library/Logs/CoreSimulator/00E55DC0-0364-49DF-9EC6-77BE587137D4' */\n logPath: string;\n /** @example `479232` */\n logPathSize?: number;\n /** '00E55DC0-0364-49DF-9EC6-77BE587137D4' */\n udid: string;\n /** 'com.apple.CoreSimulator.SimRuntime.iOS-15-1' */\n runtime: string;\n /** If the device is \"available\" which generally means that the OS files haven't been deleted (this can happen when Xcode updates). */\n isAvailable: boolean;\n /** 'com.apple.CoreSimulator.SimDeviceType.iPhone-13-Pro' */\n deviceTypeIdentifier: string;\n state: DeviceState;\n /** 'iPhone 13 Pro' */\n name: string;\n /** Type of OS the device uses. */\n osType: OSType;\n /** '15.1' */\n osVersion: string;\n /** 'iPhone 13 Pro (15.1)' */\n windowName: string;\n};\n\ntype SimulatorDeviceList = {\n devices: {\n [runtime: string]: Device[];\n };\n};\n\ntype DeviceContext = Pick<Device, 'udid'>;\n\n/** Returns true if the given value is an `OSType`, if we don't recognize the value we continue anyways but warn. */\nexport function isOSType(value: any): value is OSType {\n if (!value || typeof value !== 'string') return false;\n\n const knownTypes = ['iOS', 'tvOS', 'watchOS', 'macOS'];\n if (!knownTypes.includes(value)) {\n Log.warn(`Unknown OS type: ${value}. Expected one of: ${knownTypes.join(', ')}`);\n }\n return true;\n}\n\n/**\n * Returns the local path for the installed tar.app. Returns null when the app isn't installed.\n *\n * @param device context for selecting a device.\n * @param props.appId bundle identifier for app.\n * @returns local file path to installed app binary, e.g. '/Users/evanbacon/Library/Developer/CoreSimulator/Devices/EFEEA6EF-E3F5-4EDE-9B72-29EAFA7514AE/data/Containers/Bundle/Application/FA43A0C6-C2AD-442D-B8B1-EAF3E88CF3BF/Exponent-2.21.3.tar.app'\n */\nexport async function getContainerPathAsync(\n device: Partial<DeviceContext>,\n {\n appId,\n }: {\n appId: string;\n }\n): Promise<string | null> {\n try {\n const { stdout } = await simctlAsync(['get_app_container', resolveId(device), appId]);\n return stdout.trim();\n } catch (error: any) {\n if (error.stderr?.match(/No such file or directory/)) {\n return null;\n }\n throw error;\n }\n}\n\n/** Return a value from an installed app's Info.plist. */\nexport async function getInfoPlistValueAsync(\n device: Partial<DeviceContext>,\n {\n appId,\n key,\n containerPath,\n }: {\n appId: string;\n key: string;\n containerPath?: string;\n }\n): Promise<string | null> {\n const ensuredContainerPath = containerPath ?? (await getContainerPathAsync(device, { appId }));\n if (ensuredContainerPath) {\n try {\n const { output } = await spawnAsync(\n 'defaults',\n ['read', `${ensuredContainerPath}/Info`, key],\n {\n stdio: 'pipe',\n }\n );\n return output.join('\\n').trim();\n } catch {\n return null;\n }\n }\n return null;\n}\n\n/** Rewrite the simulator permissions to allow opening deep links without needing to prompt the user first. */\nasync function updateSimulatorLinkingPermissionsAsync(\n device: Partial<DeviceContext>,\n { url, appId }: { url: string; appId?: string }\n) {\n if (!device.udid || !appId) {\n debug('Skipping deep link permissions as missing properties could not be found:', {\n url,\n appId,\n udid: device.udid,\n });\n return;\n }\n debug('Rewriting simulator permissions to support deep linking:', {\n url,\n appId,\n udid: device.udid,\n });\n let scheme: string;\n try {\n // Attempt to extract the scheme from the URL.\n scheme = new URL(url).protocol.slice(0, -1);\n } catch (error: any) {\n debug(`Could not parse the URL scheme: ${error.message}`);\n return;\n }\n\n // Get the hard-coded path to the simulator's scheme approval plist file.\n const plistPath = path.join(\n os.homedir(),\n `Library/Developer/CoreSimulator/Devices`,\n device.udid,\n `data/Library/Preferences/com.apple.launchservices.schemeapproval.plist`\n );\n\n const plistData = fs.existsSync(plistPath)\n ? // If the file exists, then read it in the bplist format.\n await parsePlistAsync(plistPath)\n : // The file doesn't exist when we first launch the simulator, but an empty object can be used to create it (June 2024 x Xcode 15.3).\n // Can be tested by launching a new simulator or by deleting the file and relaunching the simulator.\n {};\n\n debug('Allowed links:', plistData);\n const key = `com.apple.CoreSimulator.CoreSimulatorBridge-->${scheme}`;\n // Replace any existing value for the scheme with the new appId.\n plistData[key] = appId;\n debug('Allowing deep link:', { key, appId });\n\n try {\n const data = bplistCreator(plistData);\n // Write the updated plist back to disk\n await fs.promises.writeFile(plistPath, data);\n } catch (error: any) {\n Log.warn(`Could not update simulator linking permissions: ${error.message}`);\n }\n}\n\nconst updateSimulatorLinkingPermissionsAsyncMemo = memoize(updateSimulatorLinkingPermissionsAsync);\n\n/** Open a URL on a device. The url can have any protocol. */\nexport async function openUrlAsync(\n device: Partial<DeviceContext>,\n options: { url: string; appId?: string }\n): Promise<void> {\n if (options.appId) {\n await profile(\n updateSimulatorLinkingPermissionsAsyncMemo,\n 'updateSimulatorLinkingPermissionsAsync'\n )({ udid: device.udid }, options);\n }\n\n try {\n // Skip logging since this is likely to fail.\n await simctlAsync(['openurl', resolveId(device), options.url]);\n } catch (error: any) {\n if (!error.stderr?.match(/Unable to lookup in current state: Shut/)) {\n throw error;\n }\n\n // If the device was in a weird in-between state (\"Shutting Down\" or \"Shutdown\"), then attempt to reboot it and try again.\n // This can happen when quitting the Simulator app, and immediately pressing `i` to reopen the project.\n\n // First boot the simulator\n await bootDeviceAsync({ udid: resolveId(device) });\n\n // Finally, try again...\n return await openUrlAsync(device, options);\n }\n}\n\n/** Open a simulator using a bundle identifier. If no app with a matching bundle identifier is installed then an error will be thrown. */\nexport async function openAppIdAsync(\n device: Partial<DeviceContext>,\n options: {\n appId: string;\n }\n): Promise<SpawnResult> {\n const results = await openAppIdInternalAsync(device, options);\n // Similar to 194, this is a conformance issue which indicates that the given device has no app that can handle our launch request.\n if (results.status === 4) {\n throw new CommandError('APP_NOT_INSTALLED', results.stderr);\n }\n return results;\n}\nasync function openAppIdInternalAsync(\n device: Partial<DeviceContext>,\n options: {\n appId: string;\n }\n): Promise<SpawnResult> {\n try {\n return await simctlAsync(['launch', resolveId(device), options.appId]);\n } catch (error: any) {\n if ('status' in error) {\n return error;\n }\n throw error;\n }\n}\n\n// This will only boot in headless mode if the Simulator app is not running.\nexport async function bootAsync(device: DeviceContext): Promise<Device | null> {\n await bootDeviceAsync(device);\n return isDeviceBootedAsync(device);\n}\n\n/** Returns a list of devices whose current state is 'Booted' as an array. */\nexport async function getBootedSimulatorsAsync(): Promise<Device[]> {\n const simulatorDeviceInfo = await getRuntimesAsync('devices');\n return Object.values(simulatorDeviceInfo.devices).flatMap((runtime) =>\n runtime.filter((device) => device.state === 'Booted')\n );\n}\n\n/** Returns the current device if its state is 'Booted'. */\nexport async function isDeviceBootedAsync(device: Partial<DeviceContext>): Promise<Device | null> {\n // Simulators can be booted even if the app isn't running :(\n const devices = await getBootedSimulatorsAsync();\n if (device.udid) {\n return devices.find((bootedDevice) => bootedDevice.udid === device.udid) ?? null;\n }\n\n return devices[0] ?? null;\n}\n\n/** Boot a device. */\nexport async function bootDeviceAsync(device: DeviceContext): Promise<void> {\n try {\n // Skip logging since this is likely to fail.\n await simctlAsync(['boot', device.udid]);\n } catch (error: any) {\n if (!error.stderr?.match(/Unable to boot device in current state: Booted/)) {\n throw error;\n }\n }\n}\n\n/** Install a binary file on the device. */\nexport async function installAsync(\n device: Partial<DeviceContext>,\n options: {\n /** Local absolute file path to an app binary that is built and provisioned for iOS simulators. */\n filePath: string;\n }\n): Promise<any> {\n return simctlAsync(['install', resolveId(device), options.filePath]);\n}\n\n/** Uninstall an app from the provided device. */\nexport async function uninstallAsync(\n device: Partial<DeviceContext>,\n options: {\n /** Bundle identifier */\n appId: string;\n }\n): Promise<any> {\n return simctlAsync(['uninstall', resolveId(device), options.appId]);\n}\n\nfunction parseSimControlJSONResults(input: string): any {\n try {\n return JSON.parse(input);\n } catch (error: any) {\n // Nov 15, 2020: Observed this can happen when opening the simulator and the simulator prompts the user to update the xcode command line tools.\n // Unexpected token I in JSON at position 0\n if (error.message.includes('Unexpected token')) {\n Log.error(`Apple's simctl returned malformed JSON:\\n${input}`);\n }\n throw error;\n }\n}\n\n/** Get all runtime devices given a certain type. */\nasync function getRuntimesAsync(\n type: 'devices' | 'devicetypes' | 'runtimes' | 'pairs',\n query?: string | 'available'\n): Promise<SimulatorDeviceList> {\n const result = await simctlAsync(['list', type, '--json', query]);\n const info = parseSimControlJSONResults(result.stdout) as SimulatorDeviceList;\n\n for (const runtime of Object.keys(info.devices)) {\n // Given a string like 'com.apple.CoreSimulator.SimRuntime.tvOS-13-4'\n const runtimeSuffix = runtime.split('com.apple.CoreSimulator.SimRuntime.').pop()!;\n // Create an array [tvOS, 13, 4]\n const [osType, ...osVersionComponents] = runtimeSuffix.split('-');\n // Join the end components [13, 4] -> '13.4'\n const osVersion = osVersionComponents.join('.');\n const sims = info.devices[runtime];\n if (sims) {\n for (const device of sims) {\n device.runtime = runtime;\n device.osVersion = osVersion;\n device.windowName = `${device.name} (${osVersion})`;\n device.osType = osType as OSType;\n }\n }\n }\n return info;\n}\n\n/** Return a list of iOS simulators. */\nexport async function getDevicesAsync(): Promise<Device[]> {\n const simulatorDeviceInfo = await getRuntimesAsync('devices');\n return Object.values(simulatorDeviceInfo.devices).flat();\n}\n\n/** Run a `simctl` command. */\nexport async function simctlAsync(\n args: (string | undefined)[],\n options?: SpawnOptions\n): Promise<SpawnResult> {\n try {\n return await xcrunAsync(['simctl', ...args], options);\n } catch (error) {\n if (isSpawnResultError(error)) {\n // TODO: Add more tips.\n // if (error.status === 115) {\n // }\n }\n throw error;\n }\n}\n\nfunction resolveId(device: Partial<DeviceContext>): string {\n return device.udid ?? 'booted';\n}\n"],"names":["bootAsync","bootDeviceAsync","getBootedSimulatorsAsync","getContainerPathAsync","getDevicesAsync","getInfoPlistValueAsync","installAsync","isDeviceBootedAsync","isOSType","openAppIdAsync","openUrlAsync","simctlAsync","uninstallAsync","debug","require","value","knownTypes","includes","Log","warn","join","device","appId","stdout","resolveId","trim","error","stderr","match","key","containerPath","ensuredContainerPath","output","spawnAsync","stdio","updateSimulatorLinkingPermissionsAsync","url","udid","scheme","URL","protocol","slice","message","plistPath","path","os","homedir","plistData","fs","existsSync","parsePlistAsync","data","bplistCreator","promises","writeFile","updateSimulatorLinkingPermissionsAsyncMemo","memoize","options","profile","results","openAppIdInternalAsync","status","CommandError","simulatorDeviceInfo","getRuntimesAsync","Object","values","devices","flatMap","runtime","filter","state","find","bootedDevice","filePath","parseSimControlJSONResults","input","JSON","parse","type","query","result","info","keys","runtimeSuffix","split","pop","osType","osVersionComponents","osVersion","sims","windowName","name","flat","args","xcrunAsync","isSpawnResultError"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../src/start/platforms/ios/simctl.ts"],"sourcesContent":["import type { SpawnOptions, SpawnResult } from '@expo/spawn-async';\nimport spawnAsync from '@expo/spawn-async';\nimport bplistCreator from 'bplist-creator';\nimport fs from 'fs';\nimport os from 'os';\nimport path from 'path';\n\nimport { isSpawnResultError, xcrunAsync } from './xcrun';\nimport * as Log from '../../../log';\nimport { CommandError } from '../../../utils/errors';\nimport { memoize } from '../../../utils/fn';\nimport { learnMore } from '../../../utils/link';\nimport { parsePlistAsync } from '../../../utils/plist';\nimport { profile } from '../../../utils/profile';\n\nconst debug = require('debug')('expo:simctl') as typeof console.log;\n\ntype DeviceState = 'Shutdown' | 'Booted';\n\nexport type OSType = 'iOS' | 'tvOS' | 'watchOS' | 'macOS' | 'xrOS';\n\nexport type Device = {\n availabilityError?: 'runtime profile not found';\n /** '/Users/name/Library/Developer/CoreSimulator/Devices/00E55DC0-0364-49DF-9EC6-77BE587137D4/data' */\n dataPath: string;\n /** @example `2811236352` */\n dataPathSize?: number;\n /** '/Users/name/Library/Logs/CoreSimulator/00E55DC0-0364-49DF-9EC6-77BE587137D4' */\n logPath: string;\n /** @example `479232` */\n logPathSize?: number;\n /** '00E55DC0-0364-49DF-9EC6-77BE587137D4' */\n udid: string;\n /** 'com.apple.CoreSimulator.SimRuntime.iOS-15-1' */\n runtime: string;\n /** If the device is \"available\" which generally means that the OS files haven't been deleted (this can happen when Xcode updates). */\n isAvailable: boolean;\n /** 'com.apple.CoreSimulator.SimDeviceType.iPhone-13-Pro' */\n deviceTypeIdentifier: string;\n state: DeviceState;\n /** 'iPhone 13 Pro' */\n name: string;\n /** Type of OS the device uses. */\n osType: OSType;\n /** '15.1' */\n osVersion: string;\n /** 'iPhone 13 Pro (15.1)' */\n windowName: string;\n};\n\ntype SimulatorDeviceList = {\n devices: {\n [runtime: string]: Device[];\n };\n};\n\ntype DeviceContext = Pick<Device, 'udid'>;\n\n/** Returns true if the given value is an `OSType`, if we don't recognize the value we continue anyways but warn. */\nexport function isOSType(value: any): value is OSType {\n if (!value || typeof value !== 'string') return false;\n\n const knownTypes = ['iOS', 'tvOS', 'watchOS', 'macOS'];\n if (!knownTypes.includes(value)) {\n Log.warn(`Unknown OS type: ${value}. Expected one of: ${knownTypes.join(', ')}`);\n }\n return true;\n}\n\n/**\n * Returns the local path for the installed tar.app. Returns null when the app isn't installed.\n *\n * @param device context for selecting a device.\n * @param props.appId bundle identifier for app.\n * @returns local file path to installed app binary, e.g. '/Users/evanbacon/Library/Developer/CoreSimulator/Devices/EFEEA6EF-E3F5-4EDE-9B72-29EAFA7514AE/data/Containers/Bundle/Application/FA43A0C6-C2AD-442D-B8B1-EAF3E88CF3BF/Exponent-2.21.3.tar.app'\n */\nexport async function getContainerPathAsync(\n device: Partial<DeviceContext>,\n {\n appId,\n }: {\n appId: string;\n }\n): Promise<string | null> {\n try {\n const { stdout } = await simctlAsync(['get_app_container', resolveId(device), appId]);\n return stdout.trim();\n } catch (error: any) {\n if (error.stderr?.match(/No such file or directory/)) {\n return null;\n }\n throw error;\n }\n}\n\n/** Return a value from an installed app's Info.plist. */\nexport async function getInfoPlistValueAsync(\n device: Partial<DeviceContext>,\n {\n appId,\n key,\n containerPath,\n }: {\n appId: string;\n key: string;\n containerPath?: string;\n }\n): Promise<string | null> {\n const ensuredContainerPath = containerPath ?? (await getContainerPathAsync(device, { appId }));\n if (ensuredContainerPath) {\n try {\n const { output } = await spawnAsync(\n 'defaults',\n ['read', `${ensuredContainerPath}/Info`, key],\n {\n stdio: 'pipe',\n }\n );\n return output.join('\\n').trim();\n } catch {\n return null;\n }\n }\n return null;\n}\n\n/** Rewrite the simulator permissions to allow opening deep links without needing to prompt the user first. */\nasync function updateSimulatorLinkingPermissionsAsync(\n device: Partial<DeviceContext>,\n { url, appId }: { url: string; appId?: string }\n) {\n if (!device.udid || !appId) {\n debug('Skipping deep link permissions as missing properties could not be found:', {\n url,\n appId,\n udid: device.udid,\n });\n return;\n }\n debug('Rewriting simulator permissions to support deep linking:', {\n url,\n appId,\n udid: device.udid,\n });\n let scheme: string;\n try {\n // Attempt to extract the scheme from the URL.\n scheme = new URL(url).protocol.slice(0, -1);\n } catch (error: any) {\n debug(`Could not parse the URL scheme: ${error.message}`);\n return;\n }\n\n // Get the hard-coded path to the simulator's scheme approval plist file.\n const plistPath = path.join(\n os.homedir(),\n `Library/Developer/CoreSimulator/Devices`,\n device.udid,\n `data/Library/Preferences/com.apple.launchservices.schemeapproval.plist`\n );\n\n const plistData = fs.existsSync(plistPath)\n ? // If the file exists, then read it in the bplist format.\n await parsePlistAsync(plistPath)\n : // The file doesn't exist when we first launch the simulator, but an empty object can be used to create it (June 2024 x Xcode 15.3).\n // Can be tested by launching a new simulator or by deleting the file and relaunching the simulator.\n {};\n\n debug('Allowed links:', plistData);\n const key = `com.apple.CoreSimulator.CoreSimulatorBridge-->${scheme}`;\n // Replace any existing value for the scheme with the new appId.\n plistData[key] = appId;\n debug('Allowing deep link:', { key, appId });\n\n try {\n const data = bplistCreator(plistData);\n // Write the updated plist back to disk\n await fs.promises.writeFile(plistPath, data);\n } catch (error: any) {\n Log.warn(`Could not update simulator linking permissions: ${error.message}`);\n }\n}\n\nconst updateSimulatorLinkingPermissionsAsyncMemo = memoize(updateSimulatorLinkingPermissionsAsync);\n\n/** Open a URL on a device. The url can have any protocol. */\nexport async function openUrlAsync(\n device: Partial<DeviceContext>,\n options: { url: string; appId?: string }\n): Promise<void> {\n if (options.appId) {\n await profile(\n updateSimulatorLinkingPermissionsAsyncMemo,\n 'updateSimulatorLinkingPermissionsAsync'\n )({ udid: device.udid }, options);\n }\n\n try {\n // Skip logging since this is likely to fail.\n await simctlAsync(['openurl', resolveId(device), options.url]);\n } catch (error: any) {\n if (!error.stderr?.match(/Unable to lookup in current state: Shut/)) {\n throw error;\n }\n\n // If the device was in a weird in-between state (\"Shutting Down\" or \"Shutdown\"), then attempt to reboot it and try again.\n // This can happen when quitting the Simulator app, and immediately pressing `i` to reopen the project.\n\n // First boot the simulator\n await bootDeviceAsync({ udid: resolveId(device) });\n\n // Finally, try again...\n return await openUrlAsync(device, options);\n }\n}\n\n/** Open a simulator using a bundle identifier. If no app with a matching bundle identifier is installed then an error will be thrown. */\nexport async function openAppIdAsync(\n device: Partial<DeviceContext>,\n options: {\n appId: string;\n }\n): Promise<SpawnResult> {\n const results = await openAppIdInternalAsync(device, options);\n // Similar to 194, this is a conformance issue which indicates that the given device has no app that can handle our launch request.\n if (results.status === 4) {\n throw new CommandError('APP_NOT_INSTALLED', results.stderr);\n }\n return results;\n}\nasync function openAppIdInternalAsync(\n device: Partial<DeviceContext>,\n options: {\n appId: string;\n }\n): Promise<SpawnResult> {\n try {\n return await simctlAsync(['launch', resolveId(device), options.appId]);\n } catch (error: any) {\n if ('status' in error) {\n return error;\n }\n throw error;\n }\n}\n\n// This will only boot in headless mode if the Simulator app is not running.\nexport async function bootAsync(device: DeviceContext): Promise<Device | null> {\n await bootDeviceAsync(device);\n return isDeviceBootedAsync(device);\n}\n\n/** Returns a list of devices whose current state is 'Booted' as an array. */\nexport async function getBootedSimulatorsAsync(): Promise<Device[]> {\n const simulatorDeviceInfo = await getRuntimesAsync('devices');\n return Object.values(simulatorDeviceInfo.devices).flatMap((runtime) =>\n runtime.filter((device) => device.state === 'Booted')\n );\n}\n\n/** Returns the current device if its state is 'Booted'. */\nexport async function isDeviceBootedAsync(device: Partial<DeviceContext>): Promise<Device | null> {\n // Simulators can be booted even if the app isn't running :(\n const devices = await getBootedSimulatorsAsync();\n if (device.udid) {\n return devices.find((bootedDevice) => bootedDevice.udid === device.udid) ?? null;\n }\n\n return devices[0] ?? null;\n}\n\n/** Boot a device. */\nexport async function bootDeviceAsync(device: DeviceContext): Promise<void> {\n try {\n // Skip logging since this is likely to fail.\n await simctlAsync(['boot', device.udid]);\n } catch (error: any) {\n if (!error.stderr?.match(/Unable to boot device in current state: Booted/)) {\n error.message += `\\n${learnMore('https://docs.expo.dev/workflow/ios-simulator/#troubleshooting', { learnMoreMessage: 'Troubleshooting guide' })}`;\n throw error;\n }\n }\n}\n\n/** Install a binary file on the device. */\nexport async function installAsync(\n device: Partial<DeviceContext>,\n options: {\n /** Local absolute file path to an app binary that is built and provisioned for iOS simulators. */\n filePath: string;\n }\n): Promise<any> {\n return simctlAsync(['install', resolveId(device), options.filePath]);\n}\n\n/** Uninstall an app from the provided device. */\nexport async function uninstallAsync(\n device: Partial<DeviceContext>,\n options: {\n /** Bundle identifier */\n appId: string;\n }\n): Promise<any> {\n return simctlAsync(['uninstall', resolveId(device), options.appId]);\n}\n\nfunction parseSimControlJSONResults(input: string): any {\n try {\n return JSON.parse(input);\n } catch (error: any) {\n // Nov 15, 2020: Observed this can happen when opening the simulator and the simulator prompts the user to update the xcode command line tools.\n // Unexpected token I in JSON at position 0\n if (error.message.includes('Unexpected token')) {\n Log.error(`Apple's simctl returned malformed JSON:\\n${input}`);\n }\n throw error;\n }\n}\n\n/** Get all runtime devices given a certain type. */\nasync function getRuntimesAsync(\n type: 'devices' | 'devicetypes' | 'runtimes' | 'pairs',\n query?: string | 'available'\n): Promise<SimulatorDeviceList> {\n const result = await simctlAsync(['list', type, '--json', query]);\n const info = parseSimControlJSONResults(result.stdout) as SimulatorDeviceList;\n\n for (const runtime of Object.keys(info.devices)) {\n // Given a string like 'com.apple.CoreSimulator.SimRuntime.tvOS-13-4'\n const runtimeSuffix = runtime.split('com.apple.CoreSimulator.SimRuntime.').pop()!;\n // Create an array [tvOS, 13, 4]\n const [osType, ...osVersionComponents] = runtimeSuffix.split('-');\n // Join the end components [13, 4] -> '13.4'\n const osVersion = osVersionComponents.join('.');\n const sims = info.devices[runtime];\n if (sims) {\n for (const device of sims) {\n device.runtime = runtime;\n device.osVersion = osVersion;\n device.windowName = `${device.name} (${osVersion})`;\n device.osType = osType as OSType;\n }\n }\n }\n return info;\n}\n\n/** Return a list of iOS simulators. */\nexport async function getDevicesAsync(): Promise<Device[]> {\n const simulatorDeviceInfo = await getRuntimesAsync('devices');\n return Object.values(simulatorDeviceInfo.devices).flat();\n}\n\n/** Run a `simctl` command. */\nexport async function simctlAsync(\n args: (string | undefined)[],\n options?: SpawnOptions\n): Promise<SpawnResult> {\n try {\n return await xcrunAsync(['simctl', ...args], options);\n } catch (error) {\n if (isSpawnResultError(error)) {\n // TODO: Add more tips.\n // if (error.status === 115) {\n // }\n }\n throw error;\n }\n}\n\nfunction resolveId(device: Partial<DeviceContext>): string {\n return device.udid ?? 'booted';\n}\n"],"names":["bootAsync","bootDeviceAsync","getBootedSimulatorsAsync","getContainerPathAsync","getDevicesAsync","getInfoPlistValueAsync","installAsync","isDeviceBootedAsync","isOSType","openAppIdAsync","openUrlAsync","simctlAsync","uninstallAsync","debug","require","value","knownTypes","includes","Log","warn","join","device","appId","stdout","resolveId","trim","error","stderr","match","key","containerPath","ensuredContainerPath","output","spawnAsync","stdio","updateSimulatorLinkingPermissionsAsync","url","udid","scheme","URL","protocol","slice","message","plistPath","path","os","homedir","plistData","fs","existsSync","parsePlistAsync","data","bplistCreator","promises","writeFile","updateSimulatorLinkingPermissionsAsyncMemo","memoize","options","profile","results","openAppIdInternalAsync","status","CommandError","simulatorDeviceInfo","getRuntimesAsync","Object","values","devices","flatMap","runtime","filter","state","find","bootedDevice","learnMore","learnMoreMessage","filePath","parseSimControlJSONResults","input","JSON","parse","type","query","result","info","keys","runtimeSuffix","split","pop","osType","osVersionComponents","osVersion","sims","windowName","name","flat","args","xcrunAsync","isSpawnResultError"],"mappings":"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"}
|
|
@@ -8,21 +8,46 @@ Object.defineProperty(exports, "DevToolsPlugin", {
|
|
|
8
8
|
return DevToolsPlugin;
|
|
9
9
|
}
|
|
10
10
|
});
|
|
11
|
+
function _nodefs() {
|
|
12
|
+
const data = /*#__PURE__*/ _interop_require_default(require("node:fs"));
|
|
13
|
+
_nodefs = function() {
|
|
14
|
+
return data;
|
|
15
|
+
};
|
|
16
|
+
return data;
|
|
17
|
+
}
|
|
11
18
|
const _DevToolsPluginschema = require("./DevToolsPlugin.schema");
|
|
12
19
|
const _DevToolsPluginCliExtensionExecutor = require("./DevToolsPluginCliExtensionExecutor");
|
|
13
20
|
const _DevToolsPluginManager = require("./DevToolsPluginManager");
|
|
21
|
+
const _dir = require("../../utils/dir");
|
|
22
|
+
function _interop_require_default(obj) {
|
|
23
|
+
return obj && obj.__esModule ? obj : {
|
|
24
|
+
default: obj
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const maybeRealpath = (target)=>{
|
|
28
|
+
try {
|
|
29
|
+
return _nodefs().default.realpathSync(target);
|
|
30
|
+
} catch {
|
|
31
|
+
return target;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
14
34
|
class DevToolsPlugin {
|
|
15
35
|
constructor(plugin, projectRoot){
|
|
16
36
|
this.plugin = plugin;
|
|
17
37
|
this.projectRoot = projectRoot;
|
|
18
38
|
this._executor = undefined;
|
|
19
|
-
// Validate configuration schema
|
|
20
39
|
const result = _DevToolsPluginschema.PluginSchema.safeParse(plugin);
|
|
21
40
|
if (!result.success) {
|
|
22
41
|
throw new Error(`Invalid plugin configuration: ${result.error.message}`, {
|
|
23
42
|
cause: result.error
|
|
24
43
|
});
|
|
25
44
|
}
|
|
45
|
+
if (plugin.webpageRoot != null) {
|
|
46
|
+
const webpageRoot = maybeRealpath(plugin.webpageRoot);
|
|
47
|
+
if (!(0, _dir.isPathInside)(webpageRoot, plugin.packageRoot)) {
|
|
48
|
+
throw new Error(`webpageRoot (${plugin.webpageRoot}) is not inside packageRoot (${plugin.packageRoot}).`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
26
51
|
}
|
|
27
52
|
get packageName() {
|
|
28
53
|
return this.plugin.packageName;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/start/server/DevToolsPlugin.ts"],"sourcesContent":["import type { DevToolsPluginInfo } from './DevToolsPlugin.schema';\nimport { PluginSchema } from './DevToolsPlugin.schema';\nimport { DevToolsPluginCliExtensionExecutor } from './DevToolsPluginCliExtensionExecutor';\nimport { DevToolsPluginEndpoint } from './DevToolsPluginManager';\n\n/**\n * Class that represents a DevTools plugin with CLI and/or web extensions\n *\n * Responsibilities:\n * - Validates plugin configuration against schema\n * - Provides access to plugin metadata (name, description\n * , endpoints)\n * - Manages CLI command execution via DevToolsPluginExecutor\n * - Lazily initializes executor when needed\n * - Constructs web endpoint URLs based on server configuration\n */\nexport class DevToolsPlugin {\n constructor(\n private plugin: DevToolsPluginInfo,\n public readonly projectRoot: string\n ) {\n
|
|
1
|
+
{"version":3,"sources":["../../../../src/start/server/DevToolsPlugin.ts"],"sourcesContent":["import fs from 'node:fs';\n\nimport type { DevToolsPluginInfo } from './DevToolsPlugin.schema';\nimport { PluginSchema } from './DevToolsPlugin.schema';\nimport { DevToolsPluginCliExtensionExecutor } from './DevToolsPluginCliExtensionExecutor';\nimport { DevToolsPluginEndpoint } from './DevToolsPluginManager';\nimport { isPathInside } from '../../utils/dir';\n\nconst maybeRealpath = (target: string): string => {\n try {\n return fs.realpathSync(target);\n } catch {\n return target;\n }\n};\n\n/**\n * Class that represents a DevTools plugin with CLI and/or web extensions\n *\n * Responsibilities:\n * - Validates plugin configuration against schema\n * - Provides access to plugin metadata (name, description\n * , endpoints)\n * - Manages CLI command execution via DevToolsPluginExecutor\n * - Lazily initializes executor when needed\n * - Constructs web endpoint URLs based on server configuration\n */\nexport class DevToolsPlugin {\n constructor(\n private plugin: DevToolsPluginInfo,\n public readonly projectRoot: string\n ) {\n const result = PluginSchema.safeParse(plugin);\n if (!result.success) {\n throw new Error(`Invalid plugin configuration: ${result.error.message}`, {\n cause: result.error,\n });\n }\n\n if (plugin.webpageRoot != null) {\n const webpageRoot = maybeRealpath(plugin.webpageRoot);\n if (!isPathInside(webpageRoot, plugin.packageRoot)) {\n throw new Error(\n `webpageRoot (${plugin.webpageRoot}) is not inside packageRoot (${plugin.packageRoot}).`\n );\n }\n }\n }\n\n private _executor: DevToolsPluginCliExtensionExecutor | undefined = undefined;\n\n get packageName(): string {\n return this.plugin.packageName;\n }\n\n get packageRoot(): string {\n return this.plugin.packageRoot;\n }\n\n get webpageEndpoint(): string | undefined {\n return this.plugin?.webpageRoot\n ? `${DevToolsPluginEndpoint}/${this.plugin?.packageName}`\n : undefined;\n }\n\n get webpageRoot(): string | undefined {\n return this.plugin?.webpageRoot;\n }\n\n get description(): string {\n return this.plugin.cliExtensions?.description ?? '';\n }\n\n get cliExtensions(): DevToolsPluginInfo['cliExtensions'] {\n return this.plugin.cliExtensions;\n }\n\n get executor(): DevToolsPluginCliExtensionExecutor | undefined {\n if (!this.plugin.cliExtensions?.entryPoint) {\n return undefined;\n }\n\n if (!this._executor) {\n this._executor = new DevToolsPluginCliExtensionExecutor(this.plugin, this.projectRoot);\n }\n\n return this._executor;\n }\n}\n"],"names":["DevToolsPlugin","maybeRealpath","target","fs","realpathSync","plugin","projectRoot","_executor","undefined","result","PluginSchema","safeParse","success","Error","error","message","cause","webpageRoot","isPathInside","packageRoot","packageName","webpageEndpoint","DevToolsPluginEndpoint","description","cliExtensions","executor","entryPoint","DevToolsPluginCliExtensionExecutor"],"mappings":";;;;+BA2BaA;;;eAAAA;;;;gEA3BE;;;;;;sCAGc;oDACsB;uCACZ;qBACV;;;;;;AAE7B,MAAMC,gBAAgB,CAACC;IACrB,IAAI;QACF,OAAOC,iBAAE,CAACC,YAAY,CAACF;IACzB,EAAE,OAAM;QACN,OAAOA;IACT;AACF;AAaO,MAAMF;IACX,YACE,AAAQK,MAA0B,EAClC,AAAgBC,WAAmB,CACnC;aAFQD,SAAAA;aACQC,cAAAA;aAmBVC,YAA4DC;QAjBlE,MAAMC,SAASC,kCAAY,CAACC,SAAS,CAACN;QACtC,IAAI,CAACI,OAAOG,OAAO,EAAE;YACnB,MAAM,IAAIC,MAAM,CAAC,8BAA8B,EAAEJ,OAAOK,KAAK,CAACC,OAAO,EAAE,EAAE;gBACvEC,OAAOP,OAAOK,KAAK;YACrB;QACF;QAEA,IAAIT,OAAOY,WAAW,IAAI,MAAM;YAC9B,MAAMA,cAAchB,cAAcI,OAAOY,WAAW;YACpD,IAAI,CAACC,IAAAA,iBAAY,EAACD,aAAaZ,OAAOc,WAAW,GAAG;gBAClD,MAAM,IAAIN,MACR,CAAC,aAAa,EAAER,OAAOY,WAAW,CAAC,6BAA6B,EAAEZ,OAAOc,WAAW,CAAC,EAAE,CAAC;YAE5F;QACF;IACF;IAIA,IAAIC,cAAsB;QACxB,OAAO,IAAI,CAACf,MAAM,CAACe,WAAW;IAChC;IAEA,IAAID,cAAsB;QACxB,OAAO,IAAI,CAACd,MAAM,CAACc,WAAW;IAChC;IAEA,IAAIE,kBAAsC;YACjC,cAC0B;QADjC,OAAO,EAAA,eAAA,IAAI,CAAChB,MAAM,qBAAX,aAAaY,WAAW,IAC3B,GAAGK,6CAAsB,CAAC,CAAC,GAAE,gBAAA,IAAI,CAACjB,MAAM,qBAAX,cAAae,WAAW,EAAE,GACvDZ;IACN;IAEA,IAAIS,cAAkC;YAC7B;QAAP,QAAO,eAAA,IAAI,CAACZ,MAAM,qBAAX,aAAaY,WAAW;IACjC;IAEA,IAAIM,cAAsB;YACjB;QAAP,OAAO,EAAA,6BAAA,IAAI,CAAClB,MAAM,CAACmB,aAAa,qBAAzB,2BAA2BD,WAAW,KAAI;IACnD;IAEA,IAAIC,gBAAqD;QACvD,OAAO,IAAI,CAACnB,MAAM,CAACmB,aAAa;IAClC;IAEA,IAAIC,WAA2D;YACxD;QAAL,IAAI,GAAC,6BAAA,IAAI,CAACpB,MAAM,CAACmB,aAAa,qBAAzB,2BAA2BE,UAAU,GAAE;YAC1C,OAAOlB;QACT;QAEA,IAAI,CAAC,IAAI,CAACD,SAAS,EAAE;YACnB,IAAI,CAACA,SAAS,GAAG,IAAIoB,sEAAkC,CAAC,IAAI,CAACtB,MAAM,EAAE,IAAI,CAACC,WAAW;QACvF;QAEA,OAAO,IAAI,CAACC,SAAS;IACvB;AACF"}
|
|
@@ -23,6 +23,7 @@ function _path() {
|
|
|
23
23
|
return data;
|
|
24
24
|
}
|
|
25
25
|
const _DevToolsPluginCliExtensionResults = require("./DevToolsPluginCliExtensionResults");
|
|
26
|
+
const _dir = require("../../utils/dir");
|
|
26
27
|
function _interop_require_default(obj) {
|
|
27
28
|
return obj && obj.__esModule ? obj : {
|
|
28
29
|
default: obj
|
|
@@ -45,27 +46,50 @@ class DevToolsPluginCliExtensionExecutor {
|
|
|
45
46
|
command,
|
|
46
47
|
args
|
|
47
48
|
});
|
|
48
|
-
return new Promise(
|
|
49
|
-
// Set up the command and its arguments
|
|
50
|
-
const tool = _path().default.join(this.plugin.packageRoot, this.plugin.cliExtensions.entryPoint);
|
|
51
|
-
const child = this.spawnFunc('node', [
|
|
52
|
-
tool,
|
|
53
|
-
command,
|
|
54
|
-
`${JSON.stringify(args)}`,
|
|
55
|
-
`${metroServerOrigin}`
|
|
56
|
-
], {
|
|
57
|
-
cwd: this.projectRoot,
|
|
58
|
-
env: {
|
|
59
|
-
...process.env
|
|
60
|
-
}
|
|
61
|
-
});
|
|
49
|
+
return new Promise((resolve)=>{
|
|
62
50
|
let finished = false;
|
|
51
|
+
let timeout;
|
|
63
52
|
const pluginResults = new _DevToolsPluginCliExtensionResults.DevToolsPluginCliExtensionResults(onOutput);
|
|
53
|
+
// process.execPath instead of 'node' so the child can't be redirected by a PATH shim.
|
|
54
|
+
let child;
|
|
55
|
+
try {
|
|
56
|
+
child = this.spawnFunc(process.execPath, [
|
|
57
|
+
this.resolvedEntryPoint,
|
|
58
|
+
command,
|
|
59
|
+
`${JSON.stringify(args)}`,
|
|
60
|
+
`${metroServerOrigin}`
|
|
61
|
+
], {
|
|
62
|
+
cwd: this.projectRoot,
|
|
63
|
+
env: {
|
|
64
|
+
...process.env
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
} catch (err) {
|
|
68
|
+
var _err_toString;
|
|
69
|
+
// spawn can throw synchronously; resolve with an error result instead of hanging.
|
|
70
|
+
pluginResults.append((err == null ? void 0 : (_err_toString = err.toString) == null ? void 0 : _err_toString.call(err)) ?? String(err), 'error');
|
|
71
|
+
resolve(pluginResults.getOutput());
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const finishOnTruncation = ()=>{
|
|
75
|
+
if (pluginResults.isTruncated() && !finished) {
|
|
76
|
+
finished = true;
|
|
77
|
+
if (timeout) clearTimeout(timeout);
|
|
78
|
+
child.kill('SIGKILL');
|
|
79
|
+
resolve(pluginResults.getOutput());
|
|
80
|
+
}
|
|
81
|
+
};
|
|
64
82
|
// Collect output/error data
|
|
65
|
-
child.stdout.on('data', (data)=>
|
|
66
|
-
|
|
83
|
+
child.stdout.on('data', (data)=>{
|
|
84
|
+
pluginResults.append(data.toString());
|
|
85
|
+
finishOnTruncation();
|
|
86
|
+
});
|
|
87
|
+
child.stderr.on('data', (data)=>{
|
|
88
|
+
pluginResults.append(data.toString(), 'error');
|
|
89
|
+
finishOnTruncation();
|
|
90
|
+
});
|
|
67
91
|
// Setup timeout
|
|
68
|
-
|
|
92
|
+
timeout = setTimeout(()=>{
|
|
69
93
|
if (!finished) {
|
|
70
94
|
finished = true;
|
|
71
95
|
child.kill('SIGKILL');
|
|
@@ -75,14 +99,14 @@ class DevToolsPluginCliExtensionExecutor {
|
|
|
75
99
|
}, this.timeoutMs);
|
|
76
100
|
child.on('close', (code)=>{
|
|
77
101
|
if (finished) return;
|
|
78
|
-
clearTimeout(timeout);
|
|
102
|
+
if (timeout) clearTimeout(timeout);
|
|
79
103
|
finished = true;
|
|
80
104
|
pluginResults.exit(code);
|
|
81
105
|
resolve(pluginResults.getOutput());
|
|
82
106
|
});
|
|
83
107
|
child.on('error', (err)=>{
|
|
84
108
|
if (finished) return;
|
|
85
|
-
clearTimeout(timeout);
|
|
109
|
+
if (timeout) clearTimeout(timeout);
|
|
86
110
|
finished = true;
|
|
87
111
|
pluginResults.append(err.toString(), 'error');
|
|
88
112
|
resolve(pluginResults.getOutput());
|
|
@@ -93,6 +117,12 @@ class DevToolsPluginCliExtensionExecutor {
|
|
|
93
117
|
if (!((_this_plugin_cliExtensions = this.plugin.cliExtensions) == null ? void 0 : _this_plugin_cliExtensions.entryPoint)) {
|
|
94
118
|
throw new Error(`Plugin ${this.plugin.packageName} has no CLI extensions`);
|
|
95
119
|
}
|
|
120
|
+
// Reject entryPoints that escape packageRoot (e.g. "../../other-pkg/dist/cli.js").
|
|
121
|
+
const resolved = _path().default.resolve(this.plugin.packageRoot, this.plugin.cliExtensions.entryPoint);
|
|
122
|
+
if (!(0, _dir.isPathInside)(resolved, this.plugin.packageRoot)) {
|
|
123
|
+
throw new Error(`Plugin ${this.plugin.packageName} entryPoint "${this.plugin.cliExtensions.entryPoint}" ` + `escapes packageRoot (${this.plugin.packageRoot}); must be a relative path inside the package.`);
|
|
124
|
+
}
|
|
125
|
+
this.resolvedEntryPoint = resolved;
|
|
96
126
|
}
|
|
97
127
|
validate({ command, args }) {
|
|
98
128
|
var _this_plugin_cliExtensions, _commandElement_parameters;
|
|
@@ -106,12 +136,17 @@ class DevToolsPluginCliExtensionExecutor {
|
|
|
106
136
|
// Quick check to see if the lengths match
|
|
107
137
|
throw new Error(`Expected ${paramLength} parameter(s), but got ${argsLength} argument(s) for the command "${command}".`);
|
|
108
138
|
}
|
|
109
|
-
const
|
|
139
|
+
const argsObj = args ?? {};
|
|
110
140
|
for (const param of commandElement.parameters ?? []){
|
|
111
|
-
|
|
112
|
-
if (!found) {
|
|
141
|
+
if (!Object.prototype.hasOwnProperty.call(argsObj, param.name)) {
|
|
113
142
|
throw new Error(`Parameter "${param.name}" not found in command "${command}" of plugin ${this.plugin.packageName}`);
|
|
114
143
|
}
|
|
144
|
+
// Enforce declared parameter type; don't rely on upstream Zod validation alone.
|
|
145
|
+
const expected = param.type === 'confirm' ? 'boolean' : param.type === 'number' ? 'number' : 'string';
|
|
146
|
+
const actual = typeof argsObj[param.name];
|
|
147
|
+
if (actual !== expected) {
|
|
148
|
+
throw new Error(`Parameter "${param.name}" of "${command}" expected ${expected} (declared "${param.type}"), got ${actual}.`);
|
|
149
|
+
}
|
|
115
150
|
}
|
|
116
151
|
}
|
|
117
152
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/start/server/DevToolsPluginCliExtensionExecutor.ts"],"sourcesContent":["import { spawn } from 'child_process';\nimport path from 'path';\n\nimport type {\n DevToolsPluginExecutorArguments,\n DevToolsPluginInfo,\n DevToolsPluginOutput,\n} from './DevToolsPlugin.schema';\nimport { DevToolsPluginCliExtensionResults } from './DevToolsPluginCliExtensionResults';\n\nconst DEFAULT_TIMEOUT_MS = 10_000; // 10 seconds\n\n/**\n * Class that executes CLI Extension commands for a given plugin\n *\n * ## Responsibilities:\n * - Verifies that requested commands exist and have correct parameters\n * - Validates that provided arguments match expected parameter schema\n * - Spawns and manages child processes for command execution\n * - Captures and streams stdout/stderr data from executed commands\n * - Manages process errors, timeouts, and unexpected failures\n * - Enforces execution time limits to prevent hanging processes\n * - Configures proper execution environment with color support\n * - Ensures proper cleanup of processes and timeouts\n * - Provides structured output with exit codes and error states\n */\nexport class DevToolsPluginCliExtensionExecutor {\n constructor(\n private plugin: DevToolsPluginInfo,\n private projectRoot: string,\n private spawnFunc: typeof spawn = spawn, // Used for injection when testing,\n private timeoutMs = DEFAULT_TIMEOUT_MS // Timeout for command execution\n ) {\n // Validate that this is a plugin with cli extensions\n if (!this.plugin.cliExtensions?.entryPoint) {\n throw new Error(`Plugin ${this.plugin.packageName} has no CLI extensions`);\n }\n }\n\n public validate({ command, args }: Omit<DevToolsPluginExecutorArguments, 'metroServerOrigin'>) {\n const commandElement = this.plugin.cliExtensions?.commands.find((c) => c.name === command);\n if (!commandElement) {\n throw new Error(`Command \"${command}\" not found in plugin ${this.plugin.packageName}`);\n }\n\n const paramLength = commandElement.parameters?.length ?? 0;\n const argsLength = Object.keys(args ?? {}).length;\n if (paramLength !== argsLength) {\n // Quick check to see if the lengths match\n throw new Error(\n `Expected ${paramLength} parameter(s), but got ${argsLength} argument(s) for the command \"${command}\".`\n );\n }\n\n const argsKeys = Object.keys(args ?? {});\n for (const param of commandElement.parameters ?? []) {\n const found = argsKeys.find((key) => key === param.name);\n if (!found) {\n throw new Error(\n `Parameter \"${param.name}\" not found in command \"${command}\" of plugin ${this.plugin.packageName}`\n );\n }\n }\n }\n\n /** this function is used for testing and showing the command in UI */\n public getCommandString = ({\n command,\n args,\n }: Omit<DevToolsPluginExecutorArguments, 'metroServerOrigin'>) => {\n return `node ${this.plugin.cliExtensions!.entryPoint} ${command}${Object.keys(args ?? {}).length > 0 ? ' ' + JSON.stringify(args) : ''}`;\n };\n\n public execute = async ({\n command,\n args,\n metroServerOrigin,\n onOutput,\n }: DevToolsPluginExecutorArguments): Promise<DevToolsPluginOutput> => {\n this.validate({ command, args });\n return new Promise<DevToolsPluginOutput>(async (resolve) => {\n // Set up the command and its arguments\n const tool = path.join(this.plugin.packageRoot, this.plugin.cliExtensions!.entryPoint);\n const child = this.spawnFunc(\n 'node',\n [tool, command, `${JSON.stringify(args)}`, `${metroServerOrigin}`],\n {\n cwd: this.projectRoot,\n env: { ...process.env },\n }\n );\n\n let finished = false;\n const pluginResults = new DevToolsPluginCliExtensionResults(onOutput);\n\n // Collect output/error data\n child.stdout.on('data', (data) => pluginResults.append(data.toString()));\n child.stderr.on('data', (data) => pluginResults.append(data.toString(), 'error'));\n\n // Setup timeout\n const timeout = setTimeout(() => {\n if (!finished) {\n finished = true;\n child.kill('SIGKILL');\n pluginResults.append('Command timed out', 'error');\n resolve(pluginResults.getOutput());\n }\n }, this.timeoutMs);\n\n child.on('close', (code: number) => {\n if (finished) return;\n clearTimeout(timeout);\n finished = true;\n pluginResults.exit(code);\n resolve(pluginResults.getOutput());\n });\n\n child.on('error', (err: Error) => {\n if (finished) return;\n clearTimeout(timeout);\n finished = true;\n pluginResults.append(err.toString(), 'error');\n resolve(pluginResults.getOutput());\n });\n });\n };\n}\n"],"names":["DevToolsPluginCliExtensionExecutor","DEFAULT_TIMEOUT_MS","plugin","projectRoot","spawnFunc","spawn","timeoutMs","getCommandString","command","args","cliExtensions","entryPoint","Object","keys","length","JSON","stringify","execute","metroServerOrigin","onOutput","validate","Promise","resolve","tool","path","join","packageRoot","child","cwd","env","process","finished","pluginResults","DevToolsPluginCliExtensionResults","stdout","on","data","append","toString","stderr","timeout","setTimeout","kill","getOutput","code","clearTimeout","exit","err","Error","packageName","commandElement","commands","find","c","name","paramLength","parameters","argsLength","argsKeys","param","found","key"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../src/start/server/DevToolsPluginCliExtensionExecutor.ts"],"sourcesContent":["import { spawn, type ChildProcessWithoutNullStreams } from 'child_process';\nimport path from 'path';\n\nimport type {\n DevToolsPluginExecutorArguments,\n DevToolsPluginInfo,\n DevToolsPluginOutput,\n} from './DevToolsPlugin.schema';\nimport { DevToolsPluginCliExtensionResults } from './DevToolsPluginCliExtensionResults';\nimport { isPathInside } from '../../utils/dir';\n\nconst DEFAULT_TIMEOUT_MS = 10_000; // 10 seconds\n\n/**\n * Class that executes CLI Extension commands for a given plugin\n *\n * ## Responsibilities:\n * - Verifies that requested commands exist and have correct parameters\n * - Validates that provided arguments match expected parameter schema\n * - Spawns and manages child processes for command execution\n * - Captures and streams stdout/stderr data from executed commands\n * - Manages process errors, timeouts, and unexpected failures\n * - Enforces execution time limits to prevent hanging processes\n * - Configures proper execution environment with color support\n * - Ensures proper cleanup of processes and timeouts\n * - Provides structured output with exit codes and error states\n */\nexport class DevToolsPluginCliExtensionExecutor {\n private readonly resolvedEntryPoint: string;\n\n constructor(\n private plugin: DevToolsPluginInfo,\n private projectRoot: string,\n private spawnFunc: typeof spawn = spawn, // Used for injection when testing,\n private timeoutMs = DEFAULT_TIMEOUT_MS // Timeout for command execution\n ) {\n // Validate that this is a plugin with cli extensions\n if (!this.plugin.cliExtensions?.entryPoint) {\n throw new Error(`Plugin ${this.plugin.packageName} has no CLI extensions`);\n }\n // Reject entryPoints that escape packageRoot (e.g. \"../../other-pkg/dist/cli.js\").\n const resolved = path.resolve(this.plugin.packageRoot, this.plugin.cliExtensions.entryPoint);\n if (!isPathInside(resolved, this.plugin.packageRoot)) {\n throw new Error(\n `Plugin ${this.plugin.packageName} entryPoint \"${this.plugin.cliExtensions.entryPoint}\" ` +\n `escapes packageRoot (${this.plugin.packageRoot}); must be a relative path inside the package.`\n );\n }\n this.resolvedEntryPoint = resolved;\n }\n\n public validate({ command, args }: Omit<DevToolsPluginExecutorArguments, 'metroServerOrigin'>) {\n const commandElement = this.plugin.cliExtensions?.commands.find((c) => c.name === command);\n if (!commandElement) {\n throw new Error(`Command \"${command}\" not found in plugin ${this.plugin.packageName}`);\n }\n\n const paramLength = commandElement.parameters?.length ?? 0;\n const argsLength = Object.keys(args ?? {}).length;\n if (paramLength !== argsLength) {\n // Quick check to see if the lengths match\n throw new Error(\n `Expected ${paramLength} parameter(s), but got ${argsLength} argument(s) for the command \"${command}\".`\n );\n }\n\n const argsObj = (args ?? {}) as Record<string, unknown>;\n for (const param of commandElement.parameters ?? []) {\n if (!Object.prototype.hasOwnProperty.call(argsObj, param.name)) {\n throw new Error(\n `Parameter \"${param.name}\" not found in command \"${command}\" of plugin ${this.plugin.packageName}`\n );\n }\n // Enforce declared parameter type; don't rely on upstream Zod validation alone.\n const expected =\n param.type === 'confirm' ? 'boolean' : param.type === 'number' ? 'number' : 'string';\n const actual = typeof argsObj[param.name];\n if (actual !== expected) {\n throw new Error(\n `Parameter \"${param.name}\" of \"${command}\" expected ${expected} (declared \"${param.type}\"), got ${actual}.`\n );\n }\n }\n }\n\n /** this function is used for testing and showing the command in UI */\n public getCommandString = ({\n command,\n args,\n }: Omit<DevToolsPluginExecutorArguments, 'metroServerOrigin'>) => {\n return `node ${this.plugin.cliExtensions!.entryPoint} ${command}${Object.keys(args ?? {}).length > 0 ? ' ' + JSON.stringify(args) : ''}`;\n };\n\n public execute = async ({\n command,\n args,\n metroServerOrigin,\n onOutput,\n }: DevToolsPluginExecutorArguments): Promise<DevToolsPluginOutput> => {\n this.validate({ command, args });\n return new Promise<DevToolsPluginOutput>((resolve) => {\n let finished = false;\n let timeout: ReturnType<typeof setTimeout> | undefined;\n const pluginResults = new DevToolsPluginCliExtensionResults(onOutput);\n\n // process.execPath instead of 'node' so the child can't be redirected by a PATH shim.\n let child: ChildProcessWithoutNullStreams;\n try {\n child = this.spawnFunc(\n process.execPath,\n [this.resolvedEntryPoint, command, `${JSON.stringify(args)}`, `${metroServerOrigin}`],\n {\n cwd: this.projectRoot,\n env: { ...process.env },\n }\n );\n } catch (err: any) {\n // spawn can throw synchronously; resolve with an error result instead of hanging.\n pluginResults.append(err?.toString?.() ?? String(err), 'error');\n resolve(pluginResults.getOutput());\n return;\n }\n\n const finishOnTruncation = () => {\n if (pluginResults.isTruncated() && !finished) {\n finished = true;\n if (timeout) clearTimeout(timeout);\n child.kill('SIGKILL');\n resolve(pluginResults.getOutput());\n }\n };\n\n // Collect output/error data\n child.stdout.on('data', (data) => {\n pluginResults.append(data.toString());\n finishOnTruncation();\n });\n child.stderr.on('data', (data) => {\n pluginResults.append(data.toString(), 'error');\n finishOnTruncation();\n });\n\n // Setup timeout\n timeout = setTimeout(() => {\n if (!finished) {\n finished = true;\n child.kill('SIGKILL');\n pluginResults.append('Command timed out', 'error');\n resolve(pluginResults.getOutput());\n }\n }, this.timeoutMs);\n\n child.on('close', (code: number) => {\n if (finished) return;\n if (timeout) clearTimeout(timeout);\n finished = true;\n pluginResults.exit(code);\n resolve(pluginResults.getOutput());\n });\n\n child.on('error', (err: Error) => {\n if (finished) return;\n if (timeout) clearTimeout(timeout);\n finished = true;\n pluginResults.append(err.toString(), 'error');\n resolve(pluginResults.getOutput());\n });\n });\n };\n}\n"],"names":["DevToolsPluginCliExtensionExecutor","DEFAULT_TIMEOUT_MS","plugin","projectRoot","spawnFunc","spawn","timeoutMs","getCommandString","command","args","cliExtensions","entryPoint","Object","keys","length","JSON","stringify","execute","metroServerOrigin","onOutput","validate","Promise","resolve","finished","timeout","pluginResults","DevToolsPluginCliExtensionResults","child","process","execPath","resolvedEntryPoint","cwd","env","err","append","toString","String","getOutput","finishOnTruncation","isTruncated","clearTimeout","kill","stdout","on","data","stderr","setTimeout","code","exit","Error","packageName","resolved","path","packageRoot","isPathInside","commandElement","commands","find","c","name","paramLength","parameters","argsLength","argsObj","param","prototype","hasOwnProperty","call","expected","type","actual"],"mappings":"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"}
|
|
@@ -8,17 +8,46 @@ Object.defineProperty(exports, "DevToolsPluginCliExtensionResults", {
|
|
|
8
8
|
return DevToolsPluginCliExtensionResults;
|
|
9
9
|
}
|
|
10
10
|
});
|
|
11
|
+
function _nodebuffer() {
|
|
12
|
+
const data = require("node:buffer");
|
|
13
|
+
_nodebuffer = function() {
|
|
14
|
+
return data;
|
|
15
|
+
};
|
|
16
|
+
return data;
|
|
17
|
+
}
|
|
11
18
|
const _DevToolsPluginschema = require("./DevToolsPlugin.schema");
|
|
19
|
+
// Cap accumulated output at V8's max single-string length to bound heap growth.
|
|
20
|
+
const MAX_OUTPUT_LENGTH = _nodebuffer().constants.MAX_STRING_LENGTH;
|
|
12
21
|
class DevToolsPluginCliExtensionResults {
|
|
13
22
|
constructor(onOutput){
|
|
14
23
|
this.onOutput = onOutput;
|
|
15
24
|
this._output = [];
|
|
25
|
+
this._totalLength = 0;
|
|
26
|
+
this._truncated = false;
|
|
16
27
|
}
|
|
17
28
|
append(output, level = 'info') {
|
|
29
|
+
if (this._truncated) return;
|
|
30
|
+
if (this._totalLength + output.length > MAX_OUTPUT_LENGTH) {
|
|
31
|
+
this._truncated = true;
|
|
32
|
+
const message = {
|
|
33
|
+
type: 'text',
|
|
34
|
+
text: `Output truncated: plugin exceeded V8's max string length (${MAX_OUTPUT_LENGTH} chars). Reduce output, paginate, or write to a file.`,
|
|
35
|
+
level: 'error'
|
|
36
|
+
};
|
|
37
|
+
this._output.push(message);
|
|
38
|
+
this.onOutput == null ? void 0 : this.onOutput.call(this, [
|
|
39
|
+
message
|
|
40
|
+
]);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
this._totalLength += output.length;
|
|
18
44
|
const results = this.parseOutputText(output, level);
|
|
19
45
|
this._output.push(...results);
|
|
20
46
|
this.onOutput == null ? void 0 : this.onOutput.call(this, results);
|
|
21
47
|
}
|
|
48
|
+
isTruncated() {
|
|
49
|
+
return this._truncated;
|
|
50
|
+
}
|
|
22
51
|
exit(code) {
|
|
23
52
|
if (code === 0) return;
|
|
24
53
|
this.append(`Process exited with code ${code}`, 'error');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/start/server/DevToolsPluginCliExtensionResults.ts"],"sourcesContent":["import type { DevToolsPluginOutput } from './DevToolsPlugin.schema';\nimport { DevToolsPluginOutputSchema } from './DevToolsPlugin.schema';\n\n/**\n * Class that collects and manages output from executed plugin commands\n *\n * Responsibilities:\n * - Collects output data from executed commands\n * - Parses and validates output data against expected schema\n * - Provides methods to append new output entries\n * - Handles exit codes and appends relevant messages\n * - Handles streaming of output via optional callback\n */\nexport class DevToolsPluginCliExtensionResults {\n constructor(private onOutput?: (output: DevToolsPluginOutput) => void) {}\n\n private _output: DevToolsPluginOutput = [];\n\n public append(output: string, level: 'info' | 'warning' | 'error' = 'info') {\n const results = this.parseOutputText(output, level);\n this._output.push(...results);\n this.onOutput?.(results);\n }\n\n public exit(code: number) {\n if (code === 0) return;\n this.append(`Process exited with code ${code}`, 'error');\n }\n\n public getOutput(): DevToolsPluginOutput {\n return this._output;\n }\n\n private parseOutputText(\n txt: string,\n level: 'info' | 'warning' | 'error' = 'info'\n ): DevToolsPluginOutput {\n // Validate against schema\n try {\n const result = DevToolsPluginOutputSchema.safeParse(JSON.parse(txt));\n if (!result.success) {\n return [\n {\n type: 'text',\n text: `Invalid JSON: ${result.error.issues.map((issue) => issue.message).join(', ')}`,\n level: 'error',\n },\n ];\n }\n return result.data;\n } catch {\n // Not JSON, treat as plain text\n const lines = txt.split('\\n');\n const results: DevToolsPluginOutput = [];\n for (const line of lines) {\n if (line) {\n results.push({ type: 'text', text: line, level });\n }\n }\n return results;\n }\n }\n}\n"],"names":["DevToolsPluginCliExtensionResults","onOutput","_output","append","output","level","results","parseOutputText","
|
|
1
|
+
{"version":3,"sources":["../../../../src/start/server/DevToolsPluginCliExtensionResults.ts"],"sourcesContent":["import { constants as bufferConstants } from 'node:buffer';\n\nimport type { DevToolsPluginOutput } from './DevToolsPlugin.schema';\nimport { DevToolsPluginOutputSchema } from './DevToolsPlugin.schema';\n\n// Cap accumulated output at V8's max single-string length to bound heap growth.\nconst MAX_OUTPUT_LENGTH = bufferConstants.MAX_STRING_LENGTH;\n\n/**\n * Class that collects and manages output from executed plugin commands\n *\n * Responsibilities:\n * - Collects output data from executed commands\n * - Parses and validates output data against expected schema\n * - Provides methods to append new output entries\n * - Handles exit codes and appends relevant messages\n * - Handles streaming of output via optional callback\n */\nexport class DevToolsPluginCliExtensionResults {\n constructor(private onOutput?: (output: DevToolsPluginOutput) => void) {}\n\n private _output: DevToolsPluginOutput = [];\n private _totalLength = 0;\n private _truncated = false;\n\n public append(output: string, level: 'info' | 'warning' | 'error' = 'info') {\n if (this._truncated) return;\n if (this._totalLength + output.length > MAX_OUTPUT_LENGTH) {\n this._truncated = true;\n const message = {\n type: 'text' as const,\n text: `Output truncated: plugin exceeded V8's max string length (${MAX_OUTPUT_LENGTH} chars). Reduce output, paginate, or write to a file.`,\n level: 'error' as const,\n };\n this._output.push(message);\n this.onOutput?.([message]);\n return;\n }\n this._totalLength += output.length;\n const results = this.parseOutputText(output, level);\n this._output.push(...results);\n this.onOutput?.(results);\n }\n\n public isTruncated(): boolean {\n return this._truncated;\n }\n\n public exit(code: number) {\n if (code === 0) return;\n this.append(`Process exited with code ${code}`, 'error');\n }\n\n public getOutput(): DevToolsPluginOutput {\n return this._output;\n }\n\n private parseOutputText(\n txt: string,\n level: 'info' | 'warning' | 'error' = 'info'\n ): DevToolsPluginOutput {\n // Validate against schema\n try {\n const result = DevToolsPluginOutputSchema.safeParse(JSON.parse(txt));\n if (!result.success) {\n return [\n {\n type: 'text',\n text: `Invalid JSON: ${result.error.issues.map((issue) => issue.message).join(', ')}`,\n level: 'error',\n },\n ];\n }\n return result.data;\n } catch {\n // Not JSON, treat as plain text\n const lines = txt.split('\\n');\n const results: DevToolsPluginOutput = [];\n for (const line of lines) {\n if (line) {\n results.push({ type: 'text', text: line, level });\n }\n }\n return results;\n }\n }\n}\n"],"names":["DevToolsPluginCliExtensionResults","MAX_OUTPUT_LENGTH","bufferConstants","MAX_STRING_LENGTH","onOutput","_output","_totalLength","_truncated","append","output","level","length","message","type","text","push","results","parseOutputText","isTruncated","exit","code","getOutput","txt","result","DevToolsPluginOutputSchema","safeParse","JSON","parse","success","error","issues","map","issue","join","data","lines","split","line"],"mappings":";;;;+BAkBaA;;;eAAAA;;;;yBAlBgC;;;;;;sCAGF;AAE3C,gFAAgF;AAChF,MAAMC,oBAAoBC,uBAAe,CAACC,iBAAiB;AAYpD,MAAMH;IACX,YAAY,AAAQI,QAAiD,CAAE;aAAnDA,WAAAA;aAEZC,UAAgC,EAAE;aAClCC,eAAe;aACfC,aAAa;IAJmD;IAMjEC,OAAOC,MAAc,EAAEC,QAAsC,MAAM,EAAE;QAC1E,IAAI,IAAI,CAACH,UAAU,EAAE;QACrB,IAAI,IAAI,CAACD,YAAY,GAAGG,OAAOE,MAAM,GAAGV,mBAAmB;YACzD,IAAI,CAACM,UAAU,GAAG;YAClB,MAAMK,UAAU;gBACdC,MAAM;gBACNC,MAAM,CAAC,0DAA0D,EAAEb,kBAAkB,qDAAqD,CAAC;gBAC3IS,OAAO;YACT;YACA,IAAI,CAACL,OAAO,CAACU,IAAI,CAACH;YAClB,IAAI,CAACR,QAAQ,oBAAb,IAAI,CAACA,QAAQ,MAAb,IAAI,EAAY;gBAACQ;aAAQ;YACzB;QACF;QACA,IAAI,CAACN,YAAY,IAAIG,OAAOE,MAAM;QAClC,MAAMK,UAAU,IAAI,CAACC,eAAe,CAACR,QAAQC;QAC7C,IAAI,CAACL,OAAO,CAACU,IAAI,IAAIC;QACrB,IAAI,CAACZ,QAAQ,oBAAb,IAAI,CAACA,QAAQ,MAAb,IAAI,EAAYY;IAClB;IAEOE,cAAuB;QAC5B,OAAO,IAAI,CAACX,UAAU;IACxB;IAEOY,KAAKC,IAAY,EAAE;QACxB,IAAIA,SAAS,GAAG;QAChB,IAAI,CAACZ,MAAM,CAAC,CAAC,yBAAyB,EAAEY,MAAM,EAAE;IAClD;IAEOC,YAAkC;QACvC,OAAO,IAAI,CAAChB,OAAO;IACrB;IAEQY,gBACNK,GAAW,EACXZ,QAAsC,MAAM,EACtB;QACtB,0BAA0B;QAC1B,IAAI;YACF,MAAMa,SAASC,gDAA0B,CAACC,SAAS,CAACC,KAAKC,KAAK,CAACL;YAC/D,IAAI,CAACC,OAAOK,OAAO,EAAE;gBACnB,OAAO;oBACL;wBACEf,MAAM;wBACNC,MAAM,CAAC,cAAc,EAAES,OAAOM,KAAK,CAACC,MAAM,CAACC,GAAG,CAAC,CAACC,QAAUA,MAAMpB,OAAO,EAAEqB,IAAI,CAAC,OAAO;wBACrFvB,OAAO;oBACT;iBACD;YACH;YACA,OAAOa,OAAOW,IAAI;QACpB,EAAE,OAAM;YACN,gCAAgC;YAChC,MAAMC,QAAQb,IAAIc,KAAK,CAAC;YACxB,MAAMpB,UAAgC,EAAE;YACxC,KAAK,MAAMqB,QAAQF,MAAO;gBACxB,IAAIE,MAAM;oBACRrB,QAAQD,IAAI,CAAC;wBAAEF,MAAM;wBAAQC,MAAMuB;wBAAM3B;oBAAM;gBACjD;YACF;YACA,OAAOM;QACT;IACF;AACF"}
|
|
@@ -17,15 +17,25 @@ async function addMcpCapabilities(mcpServer, devServerManager) {
|
|
|
17
17
|
const plugins = await devServerManager.devtoolsPluginManager.queryPluginsAsync();
|
|
18
18
|
for (const plugin of plugins){
|
|
19
19
|
if (plugin.cliExtensions) {
|
|
20
|
-
const
|
|
20
|
+
const mcpCommands = (plugin.cliExtensions.commands ?? []).filter((p)=>{
|
|
21
21
|
var _p_environments;
|
|
22
22
|
return (_p_environments = p.environments) == null ? void 0 : _p_environments.includes('mcp');
|
|
23
23
|
});
|
|
24
|
-
if (
|
|
24
|
+
if (mcpCommands.length === 0) {
|
|
25
25
|
continue;
|
|
26
26
|
}
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
// Build an MCP-scoped descriptor so the schema enum and the executor's
|
|
28
|
+
// existence check both reject commands that were not declared MCP-enabled.
|
|
29
|
+
const mcpPlugin = {
|
|
30
|
+
packageName: plugin.packageName,
|
|
31
|
+
packageRoot: plugin.packageRoot,
|
|
32
|
+
cliExtensions: {
|
|
33
|
+
...plugin.cliExtensions,
|
|
34
|
+
commands: mcpCommands
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
const schema = (0, _createMCPDevToolsExtensionSchema.createMCPDevToolsExtensionSchema)(mcpPlugin);
|
|
38
|
+
debug(`Installing MCP CLI extension for plugin: ${plugin.packageName} - found ${mcpCommands.length} commands`);
|
|
29
39
|
mcpServer.registerTool(plugin.packageName, {
|
|
30
40
|
title: plugin.packageName,
|
|
31
41
|
description: plugin.description,
|
|
@@ -36,7 +46,7 @@ async function addMcpCapabilities(mcpServer, devServerManager) {
|
|
|
36
46
|
try {
|
|
37
47
|
const { command, ...args } = parameters;
|
|
38
48
|
const metroServerOrigin = devServerManager.getDefaultDevServer().getJsInspectorBaseUrl();
|
|
39
|
-
const results = await new _DevToolsPluginCliExtensionExecutor.DevToolsPluginCliExtensionExecutor(
|
|
49
|
+
const results = await new _DevToolsPluginCliExtensionExecutor.DevToolsPluginCliExtensionExecutor(mcpPlugin, devServerManager.projectRoot).execute({
|
|
40
50
|
command,
|
|
41
51
|
args,
|
|
42
52
|
metroServerOrigin
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/start/server/MCPDevToolsPluginCLIExtensions.ts"],"sourcesContent":["import type { DevServerManager } from './DevServerManager';\nimport { DevToolsPluginOutputSchema } from './DevToolsPlugin.schema';\nimport { DevToolsPluginCliExtensionExecutor } from './DevToolsPluginCliExtensionExecutor';\nimport type { McpServer } from './MCP';\nimport { createMCPDevToolsExtensionSchema } from './createMCPDevToolsExtensionSchema';\nimport { Log } from '../../log';\n\nconst debug = require('debug')('expo:start:server:devtools:mcp');\n\nexport async function addMcpCapabilities(mcpServer: McpServer, devServerManager: DevServerManager) {\n const plugins = await devServerManager.devtoolsPluginManager.queryPluginsAsync();\n\n for (const plugin of plugins) {\n if (plugin.cliExtensions) {\n const
|
|
1
|
+
{"version":3,"sources":["../../../../src/start/server/MCPDevToolsPluginCLIExtensions.ts"],"sourcesContent":["import type { DevServerManager } from './DevServerManager';\nimport type { DevToolsPluginInfo } from './DevToolsPlugin.schema';\nimport { DevToolsPluginOutputSchema } from './DevToolsPlugin.schema';\nimport { DevToolsPluginCliExtensionExecutor } from './DevToolsPluginCliExtensionExecutor';\nimport type { McpServer } from './MCP';\nimport { createMCPDevToolsExtensionSchema } from './createMCPDevToolsExtensionSchema';\nimport { Log } from '../../log';\n\nconst debug = require('debug')('expo:start:server:devtools:mcp');\n\nexport async function addMcpCapabilities(mcpServer: McpServer, devServerManager: DevServerManager) {\n const plugins = await devServerManager.devtoolsPluginManager.queryPluginsAsync();\n\n for (const plugin of plugins) {\n if (plugin.cliExtensions) {\n const mcpCommands = (plugin.cliExtensions.commands ?? []).filter((p) =>\n p.environments?.includes('mcp')\n );\n if (mcpCommands.length === 0) {\n continue;\n }\n\n // Build an MCP-scoped descriptor so the schema enum and the executor's\n // existence check both reject commands that were not declared MCP-enabled.\n const mcpPlugin: DevToolsPluginInfo = {\n packageName: plugin.packageName,\n packageRoot: plugin.packageRoot,\n cliExtensions: {\n ...plugin.cliExtensions,\n commands: mcpCommands,\n },\n };\n\n const schema = createMCPDevToolsExtensionSchema(mcpPlugin);\n\n debug(\n `Installing MCP CLI extension for plugin: ${plugin.packageName} - found ${mcpCommands.length} commands`\n );\n\n mcpServer.registerTool(\n plugin.packageName,\n {\n title: plugin.packageName,\n description: plugin.description,\n inputSchema: { parameters: schema },\n },\n async ({ parameters }) => {\n try {\n const { command, ...args } = parameters;\n\n const metroServerOrigin = devServerManager\n .getDefaultDevServer()\n .getJsInspectorBaseUrl();\n\n const results = await new DevToolsPluginCliExtensionExecutor(\n mcpPlugin,\n devServerManager.projectRoot\n ).execute({ command, args, metroServerOrigin });\n\n const parsedResults = DevToolsPluginOutputSchema.safeParse(results);\n if (parsedResults.success === false) {\n throw new Error(\n `Invalid output from CLI command: ${parsedResults.error.issues\n .map((issue) => issue.message)\n .join(', ')}`\n );\n }\n return {\n content: parsedResults.data\n .map((line) => {\n const { type } = line;\n if (type === 'text') {\n return { type, text: line.text, level: line.level, url: line.url };\n } else if (line.type === 'image' || line.type === 'audio') {\n // We could present this as a resource_link, but it seems not to be well supported in MCP clients,\n // so we'll return a text with the link instead.\n return {\n type: 'text',\n text: `${type} resource: ${line.url}${line.text ? ' (' + line.text + ')' : ''}`,\n } as const;\n }\n return null;\n })\n .filter((line): line is Exclude<typeof line, null> => line !== null),\n };\n } catch (e: any) {\n Log.error('Error executing MCP CLI command:', e);\n return {\n content: [{ type: 'text', text: `Error executing command: ${e.toString()}` }],\n isError: true,\n };\n }\n }\n );\n }\n }\n}\n"],"names":["addMcpCapabilities","debug","require","mcpServer","devServerManager","plugins","devtoolsPluginManager","queryPluginsAsync","plugin","cliExtensions","mcpCommands","commands","filter","p","environments","includes","length","mcpPlugin","packageName","packageRoot","schema","createMCPDevToolsExtensionSchema","registerTool","title","description","inputSchema","parameters","command","args","metroServerOrigin","getDefaultDevServer","getJsInspectorBaseUrl","results","DevToolsPluginCliExtensionExecutor","projectRoot","execute","parsedResults","DevToolsPluginOutputSchema","safeParse","success","Error","error","issues","map","issue","message","join","content","data","line","type","text","level","url","e","Log","toString","isError"],"mappings":";;;;+BAUsBA;;;eAAAA;;;sCARqB;oDACQ;kDAEF;qBAC7B;AAEpB,MAAMC,QAAQC,QAAQ,SAAS;AAExB,eAAeF,mBAAmBG,SAAoB,EAAEC,gBAAkC;IAC/F,MAAMC,UAAU,MAAMD,iBAAiBE,qBAAqB,CAACC,iBAAiB;IAE9E,KAAK,MAAMC,UAAUH,QAAS;QAC5B,IAAIG,OAAOC,aAAa,EAAE;YACxB,MAAMC,cAAc,AAACF,CAAAA,OAAOC,aAAa,CAACE,QAAQ,IAAI,EAAE,AAAD,EAAGC,MAAM,CAAC,CAACC;oBAChEA;wBAAAA,kBAAAA,EAAEC,YAAY,qBAAdD,gBAAgBE,QAAQ,CAAC;;YAE3B,IAAIL,YAAYM,MAAM,KAAK,GAAG;gBAC5B;YACF;YAEA,uEAAuE;YACvE,2EAA2E;YAC3E,MAAMC,YAAgC;gBACpCC,aAAaV,OAAOU,WAAW;gBAC/BC,aAAaX,OAAOW,WAAW;gBAC/BV,eAAe;oBACb,GAAGD,OAAOC,aAAa;oBACvBE,UAAUD;gBACZ;YACF;YAEA,MAAMU,SAASC,IAAAA,kEAAgC,EAACJ;YAEhDhB,MACE,CAAC,yCAAyC,EAAEO,OAAOU,WAAW,CAAC,SAAS,EAAER,YAAYM,MAAM,CAAC,SAAS,CAAC;YAGzGb,UAAUmB,YAAY,CACpBd,OAAOU,WAAW,EAClB;gBACEK,OAAOf,OAAOU,WAAW;gBACzBM,aAAahB,OAAOgB,WAAW;gBAC/BC,aAAa;oBAAEC,YAAYN;gBAAO;YACpC,GACA,OAAO,EAAEM,UAAU,EAAE;gBACnB,IAAI;oBACF,MAAM,EAAEC,OAAO,EAAE,GAAGC,MAAM,GAAGF;oBAE7B,MAAMG,oBAAoBzB,iBACvB0B,mBAAmB,GACnBC,qBAAqB;oBAExB,MAAMC,UAAU,MAAM,IAAIC,sEAAkC,CAC1DhB,WACAb,iBAAiB8B,WAAW,EAC5BC,OAAO,CAAC;wBAAER;wBAASC;wBAAMC;oBAAkB;oBAE7C,MAAMO,gBAAgBC,gDAA0B,CAACC,SAAS,CAACN;oBAC3D,IAAII,cAAcG,OAAO,KAAK,OAAO;wBACnC,MAAM,IAAIC,MACR,CAAC,iCAAiC,EAAEJ,cAAcK,KAAK,CAACC,MAAM,CAC3DC,GAAG,CAAC,CAACC,QAAUA,MAAMC,OAAO,EAC5BC,IAAI,CAAC,OAAO;oBAEnB;oBACA,OAAO;wBACLC,SAASX,cAAcY,IAAI,CACxBL,GAAG,CAAC,CAACM;4BACJ,MAAM,EAAEC,IAAI,EAAE,GAAGD;4BACjB,IAAIC,SAAS,QAAQ;gCACnB,OAAO;oCAAEA;oCAAMC,MAAMF,KAAKE,IAAI;oCAAEC,OAAOH,KAAKG,KAAK;oCAAEC,KAAKJ,KAAKI,GAAG;gCAAC;4BACnE,OAAO,IAAIJ,KAAKC,IAAI,KAAK,WAAWD,KAAKC,IAAI,KAAK,SAAS;gCACzD,kGAAkG;gCAClG,gDAAgD;gCAChD,OAAO;oCACLA,MAAM;oCACNC,MAAM,GAAGD,KAAK,WAAW,EAAED,KAAKI,GAAG,GAAGJ,KAAKE,IAAI,GAAG,OAAOF,KAAKE,IAAI,GAAG,MAAM,IAAI;gCACjF;4BACF;4BACA,OAAO;wBACT,GACCvC,MAAM,CAAC,CAACqC,OAA6CA,SAAS;oBACnE;gBACF,EAAE,OAAOK,GAAQ;oBACfC,QAAG,CAACd,KAAK,CAAC,oCAAoCa;oBAC9C,OAAO;wBACLP,SAAS;4BAAC;gCAAEG,MAAM;gCAAQC,MAAM,CAAC,yBAAyB,EAAEG,EAAEE,QAAQ,IAAI;4BAAC;yBAAE;wBAC7EC,SAAS;oBACX;gBACF;YACF;QAEJ;IACF;AACF"}
|