Skip to content
Merged
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
160 changes: 80 additions & 80 deletions .github/workflow-scripts/__tests__/firebaseUtils-test.js

Large diffs are not rendered by default.

18 changes: 9 additions & 9 deletions .github/workflow-scripts/__tests__/notifyDiscord-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ describe('sendMessageToDiscord', () => {

it('should throw an error if webhook URL is missing', async () => {
await expect(sendMessageToDiscord(null, {})).rejects.toThrow(
'Discord webhook URL is missing',
'Discord webhook URL is missing'
);
});

Expand All @@ -134,7 +134,7 @@ describe('sendMessageToDiscord', () => {
});

const webhook = 'https://discord.com/api/webhooks/123/abc';
const message = {content: 'Test message'};
const message = { content: 'Test message' };

await expect(sendMessageToDiscord(webhook, message)).resolves.not.toThrow();

Expand All @@ -149,7 +149,7 @@ describe('sendMessageToDiscord', () => {

// Verify console.log was called
expect(console.log).toHaveBeenCalledWith(
'Successfully sent message to Discord',
'Successfully sent message to Discord'
);
});

Expand All @@ -162,15 +162,15 @@ describe('sendMessageToDiscord', () => {
});

const webhook = 'https://discord.com/api/webhooks/123/abc';
const message = {content: 'Test message'};
const message = { content: 'Test message' };

await expect(sendMessageToDiscord(webhook, message)).rejects.toThrow(
'HTTP status code: 400',
'HTTP status code: 400'
);

// Verify console.error was called
expect(console.error).toHaveBeenCalledWith(
'Failed to send message to Discord: 400 Bad Request',
'Failed to send message to Discord: 400 Bad Request'
);
});

Expand All @@ -180,10 +180,10 @@ describe('sendMessageToDiscord', () => {
global.fetch.mockRejectedValueOnce(networkError);

const webhook = 'https://discord.com/api/webhooks/123/abc';
const message = {content: 'Test message'};
const message = { content: 'Test message' };

await expect(sendMessageToDiscord(webhook, message)).rejects.toThrow(
'Network error',
'Network error'
);
});
});
});
33 changes: 18 additions & 15 deletions .github/workflow-scripts/collectNightlyOutcomes.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,19 @@
* @format
*/

const fs = require('fs');
const path = require('path');
const {
prepareFailurePayload,
prepareComparisonPayload,
sendMessageToDiscord,
} = require('./notifyDiscord');
const fs = require('node:fs');
const path = require('node:path');

const {
FirebaseClient,
compareResults,
getTodayDate,
} = require('./firebaseUtils');
const {
prepareFailurePayload,
prepareComparisonPayload,
sendMessageToDiscord,
} = require('./notifyDiscord');

function readOutcomes() {
const baseDir = '/tmp';
Expand All @@ -29,19 +30,21 @@ function readOutcomes() {
fs.readdirSync(fullPath).forEach(subFile => {
const subFullPath = path.join(fullPath, subFile);
if (subFullPath.endsWith('outcome')) {
const [library, status, url] = String(fs.readFileSync(subFullPath, 'utf8'))
const [library, status, url] = String(
fs.readFileSync(subFullPath, 'utf8')
)
.trim()
.split('|');
const platform = subFile.includes('android') ? 'Android' : 'iOS';
const runUrl = status.trim() === 'failure' ? url : undefined;
console.log(
`[${platform}] ${library} completed with status ${status}`,
`[${platform}] ${library} completed with status ${status}`
);
outcomes.push({
library: library.trim(),
platform,
status: status.trim(),
runUrl
runUrl,
});
}
});
Expand All @@ -56,7 +59,7 @@ function readOutcomes() {
library: library.trim(),
platform,
status: status.trim(),
runUrl
runUrl,
});
}
});
Expand All @@ -69,7 +72,7 @@ function printFailures(outcomes) {
outcomes.forEach(entry => {
if (entry.status !== 'success') {
console.log(
`❌ [${entry.platform}] ${entry.library} failed with status ${entry.status}`,
`❌ [${entry.platform}] ${entry.library} failed with status ${entry.status}`
);
failedLibraries.push({
library: entry.library,
Expand Down Expand Up @@ -127,7 +130,7 @@ async function collectResults(discordWebHook) {

// Get the most recent previous results for comparison
console.log(`Looking for most recent previous results before ${today}...`);
const {results: previousResults, date: previousDate} =
const { results: previousResults, date: previousDate } =
await firebaseClient.getLatestResults(today);

let broken = [];
Expand All @@ -141,11 +144,11 @@ async function collectResults(discordWebHook) {
recovered = comparison.recovered;

console.log(
`Found ${broken.length} newly broken jobs and ${recovered.length} recovered jobs compared to ${previousDate}`,
`Found ${broken.length} newly broken jobs and ${recovered.length} recovered jobs compared to ${previousDate}`
);
} else {
console.log(
'No previous results found for comparison - this might be the first run or no recent data available',
'No previous results found for comparison - this might be the first run or no recent data available'
);
}

Expand Down
17 changes: 8 additions & 9 deletions .github/workflow-scripts/firebaseUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class FirebaseClient {
async authenticate() {
if (!this.email || !this.password) {
throw new Error(
'Firebase credentials not found in environment variables',
'Firebase credentials not found in environment variables'
);
}

Expand All @@ -36,11 +36,10 @@ class FirebaseClient {
'identitytoolkit.googleapis.com',
`/v1/accounts:signInWithPassword?key=${this.apiKey}`,
'POST',
authData,
authData
);

this.idToken = response.idToken;
return;
}

/**
Expand Down Expand Up @@ -105,16 +104,16 @@ class FirebaseClient {
const checkDateStr = checkDate.toISOString().split('T')[0];

console.log(
`Checking for results on ${checkDateStr} (${daysBack} days back)...`,
`Checking for results on ${checkDateStr} (${daysBack} days back)...`
);

try {
const results = await this.getResults(checkDateStr);
if (results && results.length > 0) {
console.log(
`Found results from ${checkDateStr} (${daysBack} days back)`,
`Found results from ${checkDateStr} (${daysBack} days back)`
);
return {results, date: checkDateStr};
return { results, date: checkDateStr };
}
} catch (error) {
console.log(`No results found for ${checkDateStr}: ${error.message}`);
Expand All @@ -123,9 +122,9 @@ class FirebaseClient {
}

console.log(
`No previous results found within the last ${maxDaysBack} days`,
`No previous results found within the last ${maxDaysBack} days`
);
return {results: null, date: null};
return { results: null, date: null };
}

async makeRequest(hostname, path, method, data = null) {
Expand Down Expand Up @@ -231,7 +230,7 @@ function compareResults(currentResults, previousResults) {
}
}

return {broken, recovered};
return { broken, recovered };
}

/**
Expand Down
4 changes: 2 additions & 2 deletions .github/workflow-scripts/notifyDiscord.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ async function sendMessageToDiscord(webHook, message) {
} else {
const errorText = await response.text();
console.error(
`Failed to send message to Discord: ${response.status} ${errorText}`,
`Failed to send message to Discord: ${response.status} ${errorText}`
);
throw new Error(`HTTP status code: ${response.status}`);
}
Expand Down Expand Up @@ -125,7 +125,7 @@ function prepareComparisonPayload(broken, recovered) {
}
}

return {content};
return { content };
}

// Export the functions using CommonJS syntax
Expand Down
6 changes: 4 additions & 2 deletions .github/workflows/test-js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ jobs:
node-version: 24
cache: 'yarn'
- name: Install deps
run: npm install
run: yarn install --frozen-lockfile
- name: Lint code
run: yarn lint
- name: Run tests
run: npm test
run: yarn test
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Automated GitHub Actions workflows for testing React Native ecosystem libraries
This repository contains GitHub Actions workflows that automatically test **popular React Native OSS libraries** against React Native **nightly builds**. The system runs daily to catch breaking changes early and ensure ecosystem compatibility with upcoming React Native releases.

Specifically this repo will:

- Run a daily job **every night at 4:15 AM UTC** via GitHub Actions scheduled workflows
- Testing the latest `react-native@nightly` build against a list of popular libraries, such as:
- `react-native-async-storage`
Expand All @@ -20,15 +21,17 @@ Specifically this repo will:
- Send a Discord message for failure alerts and status updates
- Store results in Firebase for historical tracking and run comparison to identify newly broken or recovered libraries

#### How to apply?
### How to apply?

If you're a library maintainer, you can now sign up to be part of our nightly testing to make sure your library will keep on working. Read more in the Discussions and Proposals discussion:
* https://github.com/react-native-community/discussions-and-proposals/discussions/931

- https://github.com/react-native-community/discussions-and-proposals/discussions/931

### Website

The test results are also published on the website, which is available on the following address:
* https://react-native-community.github.io/nightly-tests/

- https://react-native-community.github.io/nightly-tests/

To learn more about website app, see [the README file](./website/README.md) in the `website` directory.

Expand Down
84 changes: 84 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import jsPlugin from '@eslint/js';
import jsonPlugin from '@eslint/json';
import markdownPlugin from '@eslint/markdown';
import { defineConfig, globalIgnores } from 'eslint/config';
import importPlugin from 'eslint-plugin-import';
import jestPlugin from 'eslint-plugin-jest';
import prettierPlugin from 'eslint-plugin-prettier/recommended';
import globals from 'globals';

export default defineConfig([
globalIgnores(['website']),

prettierPlugin,
importPlugin.flatConfigs.recommended,

{
rules: {
'prettier/prettier': [
'error',
{
arrowParens: 'avoid',
bracketSameLine: true,
printWidth: 80,
singleQuote: true,
trailingComma: 'es5',
endOfLine: 'auto',
},
],
},
},

{
files: ['**/*.{js,mjs}'],
plugins: {
js: jsPlugin,
},
languageOptions: {
globals: globals.node,
ecmaVersion: 'latest',
sourceType: 'module',
},
extends: ['js/recommended'],
rules: {
'import/no-unresolved': 'off',
'import/enforce-node-protocol-usage': ['error', 'always'],
'import/order': [
'error',
{
groups: [['external', 'builtin'], 'internal', ['parent', 'sibling']],
'newlines-between': 'always',
alphabetize: {
order: 'asc',
},
},
],
},
},

{
files: ['**/*-test.js'],
plugins: { jest: jestPlugin },
languageOptions: {
globals: jestPlugin.environments.globals.globals,
},
...jestPlugin.configs['flat/recommended'],
},

{
files: ['**/*.json'],
language: 'json/json',
plugins: {
json: jsonPlugin,
},
extends: ['json/recommended'],
},

{
files: ['**/*.md'],
plugins: {
markdown: markdownPlugin,
},
extends: ['markdown/recommended'],
},
]);
2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module.exports = {
testEnvironment: 'node'
testEnvironment: 'node',
};
4 changes: 1 addition & 3 deletions libraries.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,7 @@
"type": "string"
},
"description": "Array of GitHub usernames. We will use it in the future to communicate with library maintainers (e.g. ping for build failures).",
"examples": [
["frodo", "bilbo", "pippin", "merry", "sam"]
]
"examples": [["frodo", "bilbo", "pippin", "merry", "sam"]]
},
"notes": {
"type": "string",
Expand Down
Loading