Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions debug-github-repos.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,44 @@
"npmPkg": "@react-native-community/datetimepicker",
"expo": true,
"newArchitecture": true
},
{ "githubUrl": "https://github.com/stripe/stripe-react-native",
"npmPkg": "@stripe/stripe-react-native",
"ios": true,
"android": true,
"expo": true
},
{
"githubUrl": "https://github.com/Purii/react-native-tableview-simple",
"npmPkg": "react-native-tableview-simple",
"ios": true,
"android": true,
"expo": true
},
{
"githubUrl": "https://github.com/vonovak/react-navigation-header-buttons",
"npmPkg": "react-navigation-header-buttons",
"ios": true,
"android": true,
"expo": true
},
{
"githubUrl": "https://github.com/gabrielmoncea/react-native-template",
"npmPkg": "@gabrielmoncea/react-native-template",
"ios": true,
"android": true,
"template": true
},
{
"githubUrl": "https://github.com/BabylonJS/BabylonReactNative/tree/master/Modules/@babylonjs/react-native",
"npmPkg": "@babylonjs/react-native",
"examples": [
"https://github.com/BabylonJS/BabylonReactNative/tree/master/Apps",
"https://github.com/BabylonJS/BabylonReactNativeSample",
"https://github.com/runtothedoor/rotating-cube-demo-babylon-rxn"
],
"ios": true,
"android": true,
"windows": true
}
]
48 changes: 48 additions & 0 deletions expo-react-native.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"expoSdkVersions": [
"46.0.0",
"45.0.0",
"44.0.0",
"43.0.0"
],
"reactNativeVersions": [
"0.70.1",
"0.70.0",
"0.69.5",
"0.69.4",
"0.69.3",
"0.69.2",
"0.69.1",
"0.69.0",
"0.68.4",
"0.68.3",
"0.68.2",
"0.68.1",
"0.68.0",
"0.67.4",
"0.67.3",
"0.67.2",
"0.67.1",
"0.67.0",
"0.66.4",
"0.66.3",
"0.64.3",
"0.66.2",
"0.66.1",
"0.66.0",
"0.65.1",
"0.65.0",
"0.64.2",
"0.64.1",
"0.62.3",
"0.64.0",
"0.63.4",
"0.63.3",
"0.63.2",
"0.63.1",
"0.63.0",
"0.62.2",
"0.62.1",
"0.62.0"
]
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"lodash": "^4.17.21",
"next": "12.2.0",
"node-emoji": "^1.11.0",
"npm-registry-fetch": "^13.1.1",
"react": "18.2.0",
"react-content-loader": "^6.2.0",
"react-dom": "18.2.0",
Expand All @@ -42,6 +43,7 @@
"react-native-svg": "^12.4.4",
"react-native-web": "^0.18.7",
"react-native-web-hooks": "^3.0.2",
"semver": "^7.3.7",
"use-debounce": "^8.0.4"
},
"devDependencies": {
Expand Down
108 changes: 108 additions & 0 deletions pages/api/versions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { NextApiRequest, NextApiResponse } from 'next';

import data from '../../../assets/data.json';

type RequestQuery = {
reactNativeVersion: string;
expoSdkVersion: string;
packages: string[];
};

type PackageInfo = {
npmPackage: string;
sdkVersion?: string;
versionRange: string | null;
};
type ExpoVersionsData = {
sdkVersions: {
[keyof: string]: {
facebookReactNativeVersion: string;
};
};
};

const EXPO_API_V2 = 'https://exp.host';

async function fetchExpoVersionsAsync(): Promise<{ data: ExpoVersionsData }> {
const url = new URL('/--/api/v2/versions/latest', EXPO_API_V2);
const req = await fetch(url);
return req.json();
}

async function fetchNativeModulesAsync(sdkVersion: string): Promise<{ data: PackageInfo[] }> {
const url = new URL(`/--/api/v2/sdks/${sdkVersion}/native-modules`, EXPO_API_V2);
const req = await fetch(url);
return req.json();
}

function lookupPackageForReactNative(rnVersion: string, packages: string[]) {
return data.libraries
.filter(lib => packages.includes(lib.npmPkg) && lib.rnVersions?.[rnVersion])
.map(lib => ({
npmPackage: lib.npmPkg,
versionRange: lib.rnVersions[rnVersion],
}));
}

async function processRequestAsync(data: RequestQuery): Promise<PackageInfo[]> {
const { expoSdkVersion, reactNativeVersion, packages } = data;
if (expoSdkVersion) {
const { data } = await fetchNativeModulesAsync(expoSdkVersion);
const resolvedPackage = new Set();
const result = data.filter(({ npmPackage }) => {
if (packages.includes(npmPackage)) {
resolvedPackage.add(npmPackage);
return true;
}
return false;
});

const missingPackages = packages.filter(pkg => !resolvedPackage.has(pkg));
if (missingPackages.length === 0) {
// All packages are resolved by Expo Native Modules.
return result;
}

// Continue looking for missing package based on RN version.
let rnVersion = reactNativeVersion;
if (!rnVersion) {
const { data: versionsData } = await fetchExpoVersionsAsync();
if (!versionsData.sdkVersions[expoSdkVersion]) {
throw new Error(`invalid expoSdkVersion: ${expoSdkVersion}`);
}

({ facebookReactNativeVersion: rnVersion } = versionsData.sdkVersions[expoSdkVersion]);
}
return result.concat(lookupPackageForReactNative(rnVersion, missingPackages));
} else if (reactNativeVersion) {
return lookupPackageForReactNative(reactNativeVersion, packages);
}

return [];
}

export const config = {
api: {},
};

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const body = req.query as RequestQuery;
try {
const packages = await processRequestAsync(body);

res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.json({
...body,
packages: packages.map(({ npmPackage, sdkVersion, versionRange }) => ({
npmPackage,
sdkVersion,
versionRange,
})),
});
} catch {
res.statusCode = 500;
res.setHeader('Content-Type', 'application/json');
res.json({ error: 'Internal server error' });
}
}
23 changes: 23 additions & 0 deletions scripts/build-and-score-data.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import chunk from 'lodash/chunk';
import path from 'path';

import debugGithubRepos from '../debug-github-repos.json';
import dataVersions from '../expo-react-native.json';
import githubRepos from '../react-native-libraries.json';
import * as Strings from '../util/strings';
import { calculateDirectoryScore, calculatePopularityScore } from './calculate-score';
import { fetchGithubData, fetchGithubRateLimit, loadGitHubLicenses } from './fetch-github-data';
import { fetchNpmData, fetchNpmDataBulk } from './fetch-npm-data';
import fetchReadmeImages from './fetch-readme-images';
import { fetchVersionsData } from './fetch-versions-data';

// Uses debug-github-repos.json instead, so we have less repositories to crunch
// each time we run the script
Expand Down Expand Up @@ -107,6 +109,27 @@ const buildAndScoreData = async () => {
}
);

console.log('\n** Fetch NPM Package JSON');
data = await Promise.all(
data.map(async project => {
if (project.npm.downloads === undefined) {
console.log(`Skipping ${project.npmPkg} because it doesn't exist on NPM`);
return project;
}

const versionsData = await fetchVersionsData(project.npmPkg, [
{
name: 'react-native',
versions: dataVersions.reactNativeVersions,
},
]);
return {
...project,
rnVersions: versionsData?.[0]?.supports ?? {},
};
})
);

console.log('\n** Calculating Directory Score');
data = data.map(project => {
try {
Expand Down
70 changes: 70 additions & 0 deletions scripts/fetch-versions-data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import npmFetch from 'npm-registry-fetch';
import path from 'path';
import semver from 'semver';

const DEPS_IN_ORDER = ['devDependencies', 'peerDependencies', 'dependencies'];
const REGEXP_VERSION = /^\d+\.\d+\.\d+$/;

function isStableVersion(ver) {
return REGEXP_VERSION.exec(ver) !== null;
}

function fillSupportVersions(packageManifest, dependencies) {
if (!packageManifest) {
return () => false;
}

const versionOfDeps = dependencies.reduce((acc, dep) => {
DEPS_IN_ORDER.forEach(depType => {
const version = packageManifest[depType]?.[dep];
if (version) {
acc[dep] = version;
}
});
return acc;
}, {});

return depData => {
const requiredVersion = versionOfDeps?.[depData.name];
if (requiredVersion && depData.unsupported.length > 0) {
depData.unsupported = depData.unsupported.filter(version => {
if (semver.satisfies(version, requiredVersion)) {
if (!depData.supports) {
depData.supports = {};
}
depData.supports[version] = packageManifest.version;
return false;
}

return true;
});
}

return depData.unsupported.length === 0;
};
}

export const fetchVersionsData = async (npmPackage, packages) => {
const deps = packages.map(({ name }) => name);
const data = packages.map(({ name, versions: unsupported }) => ({
name,
unsupported,
}));

const manifest = await npmFetch.json('/' + path.join(npmPackage, 'latest'));
if (!data.every(fillSupportVersions(manifest, deps))) {
// Fetch manifest for all versions processing.
const metadata = await npmFetch.json(npmPackage);

const versions = Object.keys(metadata.versions).filter(isStableVersion).reverse();
for (const version of versions) {
if (data.every(fillSupportVersions(metadata.versions[version], deps))) {
break;
}
}
}

return data
.filter(({ supports }) => !!supports)
.map(({ name, supports }) => ({ name, supports }));
};
Loading