Skip to content

Commit

Permalink
Add script to check if all external links are valid (#2983)
Browse files Browse the repository at this point in the history
  • Loading branch information
Nateowami authored Feb 4, 2025
1 parent e509775 commit 360515e
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 46 deletions.
83 changes: 83 additions & 0 deletions scripts/check_external_urls.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#!/usr/bin/env -S deno run --allow-net

// This script checks that all links from the ExternalUrls class are valid. It can optionally take a help URL as an
// argument, so that it can be tested against a non-production copy of the help site.
//
// Suggested usage:
//
// Check against production:
// ./check_external_urls.mts
//
// Check against the Netlify preview build:
// ./check_external_urls.mts https://github-action-preview--scriptureforgehelp.netlify.app
//
// Check against a local copy of the help site:
// ./check_external_urls.mts http://localhost:8000

import { ExternalUrls } from "../src/SIL.XForge.Scripture/ClientApp/src/xforge-common/external-url-class.ts";
import locales from "../src/SIL.XForge.Scripture/locales.json" with { type: "json" };

let helpUrl = "https://help.scriptureforge.org";

if (Deno.args.length == 1) {
helpUrl = Deno.args[0];
} else if (Deno.args.length > 1) {
console.error("Usage: check_external_urls.mts [help URL]");
Deno.exit(1);
}

console.log(`Using help URL: ${helpUrl}`);

const urlsToCheck = new Set<string>();

for (const locale of locales) {
if (!locale.helps) continue;

const externalUrls = new ExternalUrls(
{ locale: { helps: locale.helps } },
{ helpUrl, defaultLocaleHelpString: "en" }
);

// Enumerate properties where the value is a string
for (const value of Object.values(externalUrls)) {
if (typeof value === "string") {
urlsToCheck.add(value);
}
}

// Enumerate getters
for (const descriptor of Object.values(Object.getOwnPropertyDescriptors(Object.getPrototypeOf(externalUrls)))) {
if ("get" in descriptor && typeof descriptor.get === "function") {
urlsToCheck.add(descriptor.get.call(externalUrls));
}
}
}

const results = await Promise.all(
Array.from(urlsToCheck).map(async url => {
const response = await fetch(url);
return { url, status: response.status, body: await response.text() };
})
);

let failure = false;
for (const result of results) {
if (result.status !== 200) {
failure = true;
console.error(`Error: ${result.url} returned status ${result.status}`);
} else if (
// Check for anchor in the URL, but skip RoboHelp pages, as they use anchors oddly
result.url.includes("#") &&
!result.url.includes("/manual") &&
!result.body.includes(`id="${result.url.split("#")[1]}"`)
) {
failure = true;
console.error(`Error: ${result.url} returned body that does not contain the expected anchor`);
}
}

if (failure) {
Deno.exit(1);
} else {
console.log("All links are valid");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
interface I18nServiceLike {
locale: { helps?: string };
}

/**
* This class has been carefully constructed to not import any Angular modules, so it can be imported into other
* scripts. The ExternalUrlService class is an Angular service that extends this and should be used in the Angular
* world.
*/
export class ExternalUrls {
paratext = 'https://paratext.org/';
transcelerator = 'https://software.sil.org/transcelerator/';
communitySupport = 'https://community.scripture.software.sil.org/c/scripture-forge/19';
announcementPage = 'https://software.sil.org/scriptureforge/news/';

constructor(
private readonly i18n: I18nServiceLike,
private readonly options: { helpUrl: string; defaultLocaleHelpString: string }
) {}

get helps(): string {
const localeUrlPortion = this.i18n.locale.helps || this.options.defaultLocaleHelpString;
return localeUrlPortion === '' ? this.options.helpUrl : `${this.options.helpUrl}/${localeUrlPortion}`;
}

get manual(): string {
return this.helps + '/manual';
}

get autoDrafts(): string {
return this.helps + '/understanding-drafts';
}

get rolesHelpPage(): string {
return this.manual + '/#t=concepts%2Froles.htm';
}

get transceleratorImportHelpPage(): string {
return this.helps + '/adding-questions#1850d745ac9e8003815fc894b8baaeb7';
}

get csvImportHelpPage(): string {
return this.helps + '/adding-questions#1850d745ac9e8085960dd88b648f0c7a';
}

get chapterAudioHelpPage(): string {
return this.helps + '/adding-questions#1850d745ac9e80e795f3d611356e74d5';
}

get sharingSettingsHelpPage(): string {
return this.helps + '/managing-checkers#1850d745ac9e8097ad4efcb063fc2603';
}

get graphite(): string {
return 'https://graphite.sil.org/';
}
}
Original file line number Diff line number Diff line change
@@ -1,52 +1,14 @@
import { Injectable } from '@angular/core';
import { environment } from '../environments/environment';
import { ExternalUrls } from './external-url-class';
import { I18nService } from './i18n.service';

@Injectable({
providedIn: 'root'
})
export class ExternalUrlService {
paratext = 'https://paratext.org/';
transcelerator = 'https://software.sil.org/transcelerator/';
communitySupport = 'https://community.scripture.software.sil.org/c/scripture-forge/19';
announcementPage = 'https://software.sil.org/scriptureforge/news/';

constructor(private readonly i18n: I18nService) {}

get helps(): string {
const localeUrlPortion = this.i18n.locale.helps || I18nService.defaultLocale.helps!;
return localeUrlPortion === '' ? environment.helps : `${environment.helps}/${localeUrlPortion}`;
}

get manual(): string {
return this.helps + '/manual';
}

get autoDrafts(): string {
return this.helps + '/understanding-drafts';
}

get rolesHelpPage(): string {
return this.manual + '/#t=concepts%2Froles.htm';
}

get transceleratorImportHelpPage(): string {
return this.helps + '/adding-questions#1850d745ac9e8003815fc894b8baaeb7';
}

get csvImportHelpPage(): string {
return this.helps + '/adding-questions#1850d745ac9e8085960dd88b648f0c7a';
}

get chapterAudioHelpPage(): string {
return this.helps + '/adding-questions#1850d745ac9e80e795f3d611356e74d5';
}

get sharingSettingsHelpPage(): string {
return this.helps + '/managing-checkers#1850d745ac9e8097ad4efcb063fc2603';
}

get graphite(): string {
return 'https://graphite.sil.org/';
/**
* This service class extends the ExternalUrls class to make it injectable.
*/
@Injectable({ providedIn: 'root' })
export class ExternalUrlService extends ExternalUrls {
constructor(i18n: I18nService) {
super(i18n, { helpUrl: environment.helps, defaultLocaleHelpString: I18nService.defaultLocale.helps! });
}
}

0 comments on commit 360515e

Please sign in to comment.