|
| 1 | +import * as fs from "fs-extra"; |
| 2 | +import * as path from "path"; |
| 3 | +import { glob } from "glob"; |
| 4 | +import { Framework, Platform } from "./types"; |
| 5 | +import { PackageJSON } from "../frameworks/compose/discover/runtime/node"; |
| 6 | + |
| 7 | +export interface App { |
| 8 | + platform: Platform; |
| 9 | + directory: string; |
| 10 | + frameworks?: Framework[]; |
| 11 | +} |
| 12 | + |
| 13 | +export function appDescription(a: App): string { |
| 14 | + if (a.directory === ".") { |
| 15 | + return `. (${a.platform.toLowerCase()})`; |
| 16 | + } |
| 17 | + return `${a.directory} (${a.platform.toLowerCase()})`; |
| 18 | +} |
| 19 | + |
| 20 | +/** Given a directory, determine the platform type */ |
| 21 | +export async function getPlatformFromFolder(dirPath: string): Promise<Platform> { |
| 22 | + const apps = await detectApps(dirPath); |
| 23 | + const hasWeb = apps.some((app) => app.platform === Platform.WEB); |
| 24 | + const hasAndroid = apps.some((app) => app.platform === Platform.ANDROID); |
| 25 | + const hasIOS = apps.some((app) => app.platform === Platform.IOS); |
| 26 | + const hasDart = apps.some((app) => app.platform === Platform.FLUTTER); |
| 27 | + if (!hasWeb && !hasAndroid && !hasIOS && !hasDart) { |
| 28 | + return Platform.NONE; |
| 29 | + } else if (hasWeb && !hasAndroid && !hasIOS && !hasDart) { |
| 30 | + return Platform.WEB; |
| 31 | + } else if (hasAndroid && !hasWeb && !hasIOS && !hasDart) { |
| 32 | + return Platform.ANDROID; |
| 33 | + } else if (hasIOS && !hasWeb && !hasAndroid && !hasDart) { |
| 34 | + return Platform.IOS; |
| 35 | + } else if (hasDart && !hasWeb && !hasIOS && !hasAndroid) { |
| 36 | + return Platform.FLUTTER; |
| 37 | + } |
| 38 | + // At this point, its not clear which platform the app directory is |
| 39 | + // because we found indicators for multiple platforms. |
| 40 | + return Platform.MULTIPLE; |
| 41 | +} |
| 42 | + |
| 43 | +/** Detects the apps in a given directory */ |
| 44 | +export async function detectApps(dirPath: string): Promise<App[]> { |
| 45 | + const packageJsonFiles = await detectFiles(dirPath, "package.json"); |
| 46 | + const pubSpecYamlFiles = await detectFiles(dirPath, "pubspec.yaml"); |
| 47 | + const srcMainFolders = await detectFiles(dirPath, "src/main/"); |
| 48 | + const xCodeProjects = await detectFiles(dirPath, "*.xcodeproj/"); |
| 49 | + const apps: App[] = [ |
| 50 | + ...(await Promise.all(packageJsonFiles.map(packageJsonToWebApp))), |
| 51 | + ...pubSpecYamlFiles.map((f) => ({ platform: Platform.FLUTTER, directory: path.dirname(f) })), |
| 52 | + ...srcMainFolders.map((f) => ({ |
| 53 | + platform: Platform.ANDROID, |
| 54 | + directory: path.dirname(path.dirname(f)), |
| 55 | + })), |
| 56 | + ...xCodeProjects.map((f) => ({ platform: Platform.IOS, directory: path.dirname(f) })), |
| 57 | + ]; |
| 58 | + return apps; |
| 59 | +} |
| 60 | + |
| 61 | +async function packageJsonToWebApp(packageJsonFile: string): Promise<App> { |
| 62 | + const packageJson = JSON.parse((await fs.readFile(packageJsonFile)).toString()); |
| 63 | + return { |
| 64 | + platform: Platform.WEB, |
| 65 | + directory: path.dirname(packageJsonFile), |
| 66 | + frameworks: getFrameworksFromPackageJson(packageJson), |
| 67 | + }; |
| 68 | +} |
| 69 | + |
| 70 | +export const WEB_FRAMEWORKS: Framework[] = ["react", "angular"]; |
| 71 | +export const WEB_FRAMEWORKS_SIGNALS: { [key in Framework]: string[] } = { |
| 72 | + react: ["react", "next"], |
| 73 | + angular: ["@angular/core"], |
| 74 | +}; |
| 75 | + |
| 76 | +export function getFrameworksFromPackageJson(packageJson: PackageJSON): Framework[] { |
| 77 | + const devDependencies = Object.keys(packageJson.devDependencies ?? {}); |
| 78 | + const dependencies = Object.keys(packageJson.dependencies ?? {}); |
| 79 | + const allDeps = Array.from(new Set([...devDependencies, ...dependencies])); |
| 80 | + return WEB_FRAMEWORKS.filter((framework) => |
| 81 | + WEB_FRAMEWORKS_SIGNALS[framework]!.find((dep) => allDeps.includes(dep)), |
| 82 | + ); |
| 83 | +} |
| 84 | + |
| 85 | +async function detectFiles(dirPath: string, filePattern: string): Promise<string[]> { |
| 86 | + const options = { |
| 87 | + cwd: dirPath, |
| 88 | + ignore: [ |
| 89 | + "**/dataconnect*/**", |
| 90 | + "**/node_modules/**", // Standard dependency directory |
| 91 | + "**/dist/**", // Common build output |
| 92 | + "**/build/**", // Common build output |
| 93 | + "**/out/**", // Another common build output |
| 94 | + "**/.next/**", // Next.js build directory |
| 95 | + "**/coverage/**", // Test coverage reports |
| 96 | + ], |
| 97 | + absolute: false, |
| 98 | + }; |
| 99 | + return glob(`{${filePattern},*/${filePattern},*/*/${filePattern}}`, options); |
| 100 | +} |
0 commit comments