Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: rename wallet files for better alignment #98

Merged
merged 6 commits into from
Oct 7, 2024
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
45 changes: 45 additions & 0 deletions .github/workflows/link-checker.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Link checker

on:
schedule:
- cron: '0 0 * * 0' # Runs at midnight every Sunday
workflow_dispatch:

permissions:
contents: write

jobs:
link-checker:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 18

- name: Cache npm dependencies
uses: actions/cache@v4
with:
path: viewer/node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-

- name: Install dependencies
run: cd viewer && npm install

- name: Validate links
run: cd viewer && node scripts/link-checker.mjs

# Deploy to local repo
- name: Deploy
uses: s0/git-publish-subdir-action@develop
env:
REPO: self
BRANCH: errors
FOLDER: errors
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
3 changes: 2 additions & 1 deletion .github/workflows/newsletter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ on:
inputs:
email:
description: 'Email address to send the newsletter to'
required: true

jobs:
generate-newsletter:
Expand Down Expand Up @@ -40,7 +41,7 @@ jobs:
EMAIL_PASSWORD: ${{ secrets.EMAIL_PASSWORD }}
EMAIL_RECIPIENT: ${{ github.event.inputs.email }}
NODE_ENV: ${{ vars.NODE_ENV }}
EMAIL_STORE: true
EMAIL_STORE: true

- name: Upload newsletter artifact
uses: actions/upload-artifact@v4
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
errors
117 changes: 117 additions & 0 deletions viewer/scripts/link-checker.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// this script checks if all the links in the json files are still reachable
import { readdirSync, readFileSync, writeFileSync, mkdirSync, existsSync, rmSync } from 'fs';
import axios from 'axios';
import { join, dirname } from 'path';

let counter = 0;
let validFiles = 0;
let invalidFiles = 0;
const errorLog = {};

async function isLinkReachable(url, filePath, jsonPath) {
try {
const response = await axios.get(url, {
timeout: 10000, // 10 seconds timeout
headers: { 'User-Agent': 'Mozilla/5.0 (compatible; LinkChecker/1.0)' },
maxRedirects: 5
});
return response.status >= 200 && response.status < 400;
} catch (error) {
if (error.code === 'ECONNABORTED') {
console.log(`Request timed out for URL: ${url} in file: ${filePath} at path: ${jsonPath}`);
} else {
console.log(`Error reaching URL: ${url} in file: ${filePath} at path: ${jsonPath} - ${error.message}`);
}
counter++;
if (!errorLog[filePath]) {
errorLog[filePath] = {};
}
errorLog[filePath][jsonPath] = url;
return false;
}
}

async function checkLinksInObject(obj, filePath, currentPath = '') {
const promises = [];
let hasUnreachableLinks = false;

function collectPromises(obj, path) {
for (const key in obj) {
const newPath = path ? `${path}.${key}` : key;
if (typeof obj[key] === 'object' && obj[key] !== null) {
collectPromises(obj[key], newPath);
} else if (typeof obj[key] === 'string' && obj[key].includes('http')) {
promises.push(
isLinkReachable(obj[key], filePath, newPath).then(isReachable => {
if (!isReachable) {
console.log(`Unreachable link found in ${filePath} at path: ${newPath}: ${obj[key]}`);
hasUnreachableLinks = true;
}
})
);
}
}
}

collectPromises(obj, currentPath);
await Promise.all(promises);

return !hasUnreachableLinks;
}

async function validateFolder(folder) {
if(!existsSync(folder)) {
return;
}
const files = readdirSync(folder);
const promises = files
.filter(file => file.endsWith('.json'))
.map(async file => {
const content = JSON.parse(readFileSync(`${folder}/${file}`, 'utf8'));
const isValid = await checkLinksInObject(content, `${folder}/${file}`);
if (isValid) {
validFiles++;
} else {
invalidFiles++;
}
});

await Promise.all(promises);
}

const folders = ['case-studies', 'wallets', 'dependencies'];

(async () => {
const errorsDir = '../errors';
if (!existsSync(errorsDir)) {
mkdirSync(errorsDir);
} else {
// delete all files and subdirectories in the errors folder
const files = readdirSync(errorsDir);
for (const file of files) {
rmSync(join(errorsDir, file), { recursive: true, force: true });
}
}

for (const folder of folders) {
counter = 0;
validFiles = 0;
invalidFiles = 0;
await validateFolder('../' + folder);
console.log(`Total unreachable links in ${folder}: ${counter}`);
console.log(`Valid JSON files in ${folder}: ${validFiles}`);
console.log(`Invalid JSON files in ${folder}: ${invalidFiles}`);
}

console.log('\nError Log:');
for (const [filePath, errors] of Object.entries(errorLog)) {
const relativePath = filePath.replace('../', '');
const errorFilePath = join(errorsDir, relativePath);
const errorDir = dirname(errorFilePath);

if (!existsSync(errorDir)) {
mkdirSync(errorDir, { recursive: true });
}
writeFileSync(errorFilePath, JSON.stringify(errors, null, 2));
}
})();
43 changes: 35 additions & 8 deletions viewer/scripts/validate.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,34 @@ addFormats(ajv);
const dependencyIds = [];
const walletIds = [];

// Helper function to normalize filenames
function normalizeFilename(filename) {
return filename.toLowerCase().replace(/\s+/g, '-');
}

// Check files in a folder to ensure they meet the desired format
function checkFilesInFolder(folder) {
if(!existsSync(folder)) {
console.info(`No files found in ${folder}`);
return;
}
const files = readdirSync(folder);
files.forEach(file => {
const newFileName = normalizeFilename(file);
if (file !== newFileName) {
throw new Error(`Invalid filename: ${file} should be ${newFileName}`);
}
});
}

// validate dependencies
function validateDependencies() {
checkFilesInFolder('../dependencies');
const validate = ajv.compile(JSON.parse(readFileSync('src/assets/dependency.schema.json')));
const files = readdirSync('../dependencies');
const files = readdirSync('../dependencies').map(normalizeFilename);
let success = true;
files.map(file => {
const dependency = JSON.parse(readFileSync(`../dependencies/${file}`))
const dependency = JSON.parse(readFileSync(`../dependencies/${file}`));
if(!validate(dependency)) {
console.error(`Error validating ${file}:`);
console.error(JSON.stringify(validate.errors, null, 2));
Expand All @@ -32,13 +53,14 @@ function validateDependencies() {
}

async function validateWallets() {
checkFilesInFolder('../wallets');
const profileSIGSchema = await axios.get('https://openwallet-foundation.github.io/credential-format-comparison-sig/assets/schemas/fields.json').then(res => res.data);
ajv.addSchema(profileSIGSchema, "https://openwallet-foundation.github.io/credential-format-comparison-sig/assets/schemas/fields.json");
const validate = ajv.compile(JSON.parse(readFileSync('src/assets/schema.json')));
const files = readdirSync('../wallets');
const files = readdirSync('../wallets').map(normalizeFilename);
let success = true;
files.map(file => {
const wallet = JSON.parse(readFileSync(`../wallets/${file}`))
const wallet = JSON.parse(readFileSync(`../wallets/${file}`));
if(!validate(wallet)) {
console.error(`Error validating ${file}:`);
console.error(JSON.stringify(validate.errors, null, 2));
Expand All @@ -55,7 +77,6 @@ async function validateWallets() {
}
}
}

});
if(success) {
console.info('All wallets are valid');
Expand All @@ -66,16 +87,20 @@ async function validateWallets() {
}

function validateCaseStudies() {
if(!existsSync("../case-studies")) {
return;
}
checkFilesInFolder('../case-studies');
const validate = ajv.compile(JSON.parse(readFileSync('src/assets/case-study.schema.json')));
// needed in case no folder is there
if(!existsSync('../case-studies')) {
console.info('No case studies found');
return;
}
const files = readdirSync('../case-studies');
const files = readdirSync('../case-studies').map(normalizeFilename);
let success = true;
files.map(file => {
const caseStudy = JSON.parse(readFileSync(`../case-studies/${file}`))
const caseStudy = JSON.parse(readFileSync(`../case-studies/${file}`));
if(!validate(caseStudy)) {
console.error(`Error validating ${file}:`);
console.error(JSON.stringify(validate.errors, null, 2));
Expand All @@ -85,7 +110,7 @@ function validateCaseStudies() {
caseStudy.references.forEach(element => {
if(!walletIds.includes(element)) {
console.error(`Referenced wallet ${element} not found in wallets`);
success = false
success = false;
}
});
});
Expand All @@ -97,6 +122,8 @@ function validateCaseStudies() {
}
}

checkFilesInFolder('../dependencies');
validateDependencies();
await validateWallets();
checkFilesInFolder('../case-studies');
validateCaseStudies();
Loading