Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
a5ba746625
|
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "artix-mlg",
|
"name": "artix-mlg",
|
||||||
"version": "0.2.7",
|
"version": "0.2.8",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "artix-mlg",
|
"name": "artix-mlg",
|
||||||
"version": "0.2.7",
|
"version": "0.2.8",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"country-code-lookup": "0.1.3",
|
"country-code-lookup": "0.1.3",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "artix-mlg",
|
"name": "artix-mlg",
|
||||||
"version": "0.2.7",
|
"version": "0.2.8",
|
||||||
"description": "mirrorlist generator for Artix Linux",
|
"description": "mirrorlist generator for Artix Linux",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"artix",
|
"artix",
|
||||||
|
|||||||
178
src/artix-mlg.ts
Normal file
178
src/artix-mlg.ts
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
import path from 'path';
|
||||||
|
import fsp from 'fs/promises';
|
||||||
|
import { resolveCountry } from './resolveCountry.js';
|
||||||
|
import { processUrl, getProtocolId } from './processUrl.js';
|
||||||
|
import { generateMirrorMd } from './markdown.js';
|
||||||
|
import { generateMirrorlist } from './mirrorlist.js';
|
||||||
|
import type { MirrorProfile, MirrorInput } from './mirrorProfile.ts';
|
||||||
|
import type { UrlComponents } from './processUrl.js';
|
||||||
|
import type { MirrorProfilesByMirrorName } from './markdown.js';
|
||||||
|
|
||||||
|
const inputFile = process.env['INPUT'] || path.join(process.cwd(), 'mirrors.json');
|
||||||
|
const fixtureFile = process.env['FIXTURE'] || path.join(process.cwd(), 'mirrors.fixture.json');
|
||||||
|
const mirrorList = process.env['MIRRORLIST'] || path.join(process.cwd(), 'mirrorlist');
|
||||||
|
const mirrorMd = process.env['MIRRORMD'] || path.join(process.cwd(), 'mirrors.md');
|
||||||
|
const mdHeadFile = process.env['MDHEADER'] || path.join(process.cwd(), 'head.md');
|
||||||
|
|
||||||
|
interface ArtixMlgOptions {
|
||||||
|
fixture: FixtureObject[];
|
||||||
|
mirrors?: MirrorMap;
|
||||||
|
mirrorUrls?: MirrorUrl[];
|
||||||
|
mirrorProfilesByMirrorName?: MirrorProfilesByMirrorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FixtureObject {
|
||||||
|
pk: number;
|
||||||
|
model: string;
|
||||||
|
fields: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Mirror extends FixtureObject {
|
||||||
|
model: "mirrors.Mirror"
|
||||||
|
fields: {
|
||||||
|
name: string;
|
||||||
|
tier: number;
|
||||||
|
upstream: null | number;
|
||||||
|
admin_email: string;
|
||||||
|
alternate_email: string;
|
||||||
|
public: boolean;
|
||||||
|
active: boolean;
|
||||||
|
isos: boolean;
|
||||||
|
rsync_user: string;
|
||||||
|
rsync_password: string;
|
||||||
|
bug: null;
|
||||||
|
notes: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MirrorUrl extends FixtureObject {
|
||||||
|
model: "mirrors.MirrorUrl"
|
||||||
|
fields: {
|
||||||
|
url: string;
|
||||||
|
protocol: number;
|
||||||
|
mirror: number;
|
||||||
|
country: string;
|
||||||
|
has_ipv4: boolean;
|
||||||
|
has_ipv6: boolean;
|
||||||
|
active: boolean;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type MirrorMap = { [name: string]: Mirror };
|
||||||
|
|
||||||
|
class ArtixMlg {
|
||||||
|
private mirrorCounter = 0;
|
||||||
|
private mirrorUrls: MirrorUrl[];
|
||||||
|
private mirrorProfilesByMirrorName: MirrorProfilesByMirrorName;
|
||||||
|
private mirrors: MirrorMap;
|
||||||
|
|
||||||
|
constructor(options: ArtixMlgOptions) {
|
||||||
|
this.mirrors = options.mirrors || this.setMirrors(options.fixture);
|
||||||
|
this.mirrorUrls = options.mirrorUrls || [];
|
||||||
|
this.mirrorProfilesByMirrorName = options.mirrorProfilesByMirrorName || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
async run() {
|
||||||
|
const input: MirrorInput = JSON.parse(await fsp.readFile(inputFile, { encoding: 'utf-8' }));
|
||||||
|
|
||||||
|
input.mirrors.forEach(this.processMirrorProfile);
|
||||||
|
input.mirrors.forEach(this.updateUpstream);
|
||||||
|
|
||||||
|
await fsp.writeFile(fixtureFile, JSON.stringify(this.composeMirrorFixture(), null, 4));
|
||||||
|
await fsp.writeFile(mirrorList, generateMirrorlist(input.mirrors?.filter(m => m.public && m.active && !m.suppress) || []));
|
||||||
|
await fsp.writeFile(mirrorMd, await generateMirrorMd(mdHeadFile, this.mirrorProfilesByMirrorName));
|
||||||
|
}
|
||||||
|
|
||||||
|
setMirrors(fixture: FixtureObject[]) {
|
||||||
|
const mirrors: MirrorMap = {};
|
||||||
|
fixture.filter(x => x.model === 'mirrors.Mirror').forEach((mirror: Mirror) => {
|
||||||
|
this.mirrorCounter = Math.max(this.mirrorCounter, mirror.pk);
|
||||||
|
mirror.fields.active = false;
|
||||||
|
mirror.fields.public = false;
|
||||||
|
mirror.fields.isos = false;
|
||||||
|
mirrors[mirror.fields.name] = mirror;
|
||||||
|
});
|
||||||
|
return mirrors;
|
||||||
|
}
|
||||||
|
|
||||||
|
getMirror(name: string): Mirror {
|
||||||
|
return this.mirrors[name] = {
|
||||||
|
pk: this.mirrors[name]?.pk || ++this.mirrorCounter,
|
||||||
|
model: 'mirrors.Mirror',
|
||||||
|
fields: {
|
||||||
|
name: name,
|
||||||
|
tier: -1,
|
||||||
|
upstream: null,
|
||||||
|
admin_email: '',
|
||||||
|
alternate_email: '',
|
||||||
|
public: true,
|
||||||
|
active: false,
|
||||||
|
isos: false,
|
||||||
|
rsync_user: '',
|
||||||
|
rsync_password: '',
|
||||||
|
bug: null,
|
||||||
|
notes: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateMirror(m: Mirror, profile: MirrorProfile, url: UrlComponents) {
|
||||||
|
m.fields.tier = Math.max(m.fields.tier, profile.tier);
|
||||||
|
m.fields.admin_email = profile.admin_email || m.fields.admin_email;
|
||||||
|
m.fields.alternate_email = profile.alternate_email || m.fields.alternate_email;
|
||||||
|
m.fields.notes = profile.notes || m.fields.notes;
|
||||||
|
m.fields.active ||= profile.active;
|
||||||
|
m.fields.public &&= profile.public;
|
||||||
|
m.fields.isos ||= !!profile.stable_isos || !!profile.weekly_isos;
|
||||||
|
if (url.protocol === 'rsync') {
|
||||||
|
m.fields.rsync_user = profile.rsync_user || '';
|
||||||
|
m.fields.rsync_password = profile.rsync_password || '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
processMirrorProfile = (m: MirrorProfile, index: number) => {
|
||||||
|
const url: UrlComponents = processUrl(m.url);
|
||||||
|
const mirror: Mirror = this.getMirror(m.force_mirror_name || url.name);
|
||||||
|
this.updateMirror(mirror, m, url);
|
||||||
|
const mirrorUrl: MirrorUrl = {
|
||||||
|
pk: index + 1,
|
||||||
|
model: 'mirrors.MirrorUrl',
|
||||||
|
fields: {
|
||||||
|
url: url.partial,
|
||||||
|
protocol: getProtocolId(url.protocol),
|
||||||
|
mirror: mirror.pk,
|
||||||
|
country: resolveCountry(m.country)?.iso2 || undefined,
|
||||||
|
// populate ip fields with `mirrorresolv`
|
||||||
|
has_ipv4: false,
|
||||||
|
has_ipv6: false,
|
||||||
|
active: m.active
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.mirrorUrls.push(mirrorUrl);
|
||||||
|
this.pushMirrorProfile(m.force_mirror_name || url.name, m);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateUpstream = (m: MirrorProfile) => {
|
||||||
|
const url: UrlComponents = processUrl(m.url);
|
||||||
|
const mirror: Mirror = this.mirrors[m.force_mirror_name || url.name];
|
||||||
|
mirror.fields.upstream = (m.upstream && this.mirrors[m.upstream]?.pk) || mirror.fields.upstream;
|
||||||
|
}
|
||||||
|
|
||||||
|
pushMirrorProfile(name: string, m: MirrorProfile) {
|
||||||
|
const list: MirrorProfile[] = this.mirrorProfilesByMirrorName[name] ||= [];
|
||||||
|
list.push(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
composeMirrorFixture(): FixtureObject[] {
|
||||||
|
const fixture: FixtureObject[] = [];
|
||||||
|
for (let mirrorName in this.mirrors) {
|
||||||
|
fixture.push(this.mirrors[mirrorName]);
|
||||||
|
}
|
||||||
|
fixture.push.apply(fixture, this.mirrorUrls);
|
||||||
|
return fixture;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ArtixMlg;
|
||||||
|
export { ArtixMlg };
|
||||||
|
export type { ArtixMlgOptions, FixtureObject, Mirror, MirrorUrl, MirrorMap };
|
||||||
274
src/index.ts
274
src/index.ts
@@ -1,263 +1,11 @@
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fsp from 'fs/promises';
|
import fsp from 'fs/promises';
|
||||||
import parseUrl from 'extract-tld';
|
|
||||||
import addrs from "email-addresses";
|
|
||||||
import { resolveCountry } from './resolveCountry.js';
|
|
||||||
import MirrorsByRegion from './mirrorsByRegion.js';
|
|
||||||
import type { PathLike } from 'fs';
|
import type { PathLike } from 'fs';
|
||||||
import type { MirrorProfile, MirrorInput } from './mirrorProfile.ts';
|
import { ArtixMlg, type ArtixMlgOptions, type FixtureObject } from './artix-mlg.js';
|
||||||
|
|
||||||
const inputFile = process.env['INPUT'] || path.join(process.cwd(), 'mirrors.json');
|
|
||||||
const fixtureFile = process.env['FIXTURE'] || path.join(process.cwd(), 'mirrors.fixture.json');
|
const fixtureFile = process.env['FIXTURE'] || path.join(process.cwd(), 'mirrors.fixture.json');
|
||||||
const mirrorList = process.env['MIRRORLIST'] || path.join(process.cwd(), 'mirrorlist');
|
|
||||||
const mirrorMd = process.env['MIRRORMD'] || path.join(process.cwd(), 'mirrors.md');
|
|
||||||
const mdHeadFile = process.env['MDHEADER'] || path.join(process.cwd(), 'head.md');
|
|
||||||
const verbose = !!process.env['VERBOSE'];
|
|
||||||
|
|
||||||
const protocolId: Record<Protocol, number> = {
|
async function tryReadFileOrDefault<T>(f: PathLike, d: T, verbose: boolean = false): Promise<T> {
|
||||||
http: 1,
|
|
||||||
rsync: 3,
|
|
||||||
https: 5,
|
|
||||||
ftp: 9
|
|
||||||
}
|
|
||||||
|
|
||||||
type Protocol = 'http' | 'https' | 'rsync' | 'ftp';
|
|
||||||
|
|
||||||
interface FixtureObject {
|
|
||||||
pk: number;
|
|
||||||
model: string;
|
|
||||||
fields: unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Mirror extends FixtureObject {
|
|
||||||
model: "mirrors.Mirror"
|
|
||||||
fields: {
|
|
||||||
name: string;
|
|
||||||
tier: number;
|
|
||||||
upstream: null | number;
|
|
||||||
admin_email: string;
|
|
||||||
alternate_email: string;
|
|
||||||
public: boolean;
|
|
||||||
active: boolean;
|
|
||||||
isos: boolean;
|
|
||||||
rsync_user: string;
|
|
||||||
rsync_password: string;
|
|
||||||
bug: null;
|
|
||||||
notes: string;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface MirrorUrl extends FixtureObject {
|
|
||||||
model: "mirrors.MirrorUrl"
|
|
||||||
fields: {
|
|
||||||
url: string;
|
|
||||||
protocol: number;
|
|
||||||
mirror: number;
|
|
||||||
country: string;
|
|
||||||
has_ipv4: boolean;
|
|
||||||
has_ipv6: boolean;
|
|
||||||
active: boolean;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface UrlComponents {
|
|
||||||
full: string;
|
|
||||||
name: string;
|
|
||||||
partial: string;
|
|
||||||
protocol: Protocol;
|
|
||||||
mirrorListable: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
type MirrorMap = { [name: string]: Mirror };
|
|
||||||
|
|
||||||
let mirrorCounter = 0;
|
|
||||||
|
|
||||||
function setMirrors(fixture: FixtureObject[]) {
|
|
||||||
const mirrors: MirrorMap = {};
|
|
||||||
fixture.filter(x => x.model === 'mirrors.Mirror').forEach((mirror: Mirror) => {
|
|
||||||
mirrorCounter = Math.max(mirrorCounter, mirror.pk);
|
|
||||||
mirror.fields.active = false;
|
|
||||||
mirror.fields.public = false;
|
|
||||||
mirror.fields.isos = false;
|
|
||||||
mirrors[mirror.fields.name] = mirror;
|
|
||||||
});
|
|
||||||
return mirrors;
|
|
||||||
}
|
|
||||||
|
|
||||||
function processUrl(url: string): UrlComponents {
|
|
||||||
const mirrorListable: Protocol[] = ['http', 'https'];
|
|
||||||
const protocol = url.split(':')[0] as Protocol;
|
|
||||||
return {
|
|
||||||
name: parseUrl.parseUrl(url)?.domain,
|
|
||||||
full: url,
|
|
||||||
protocol,
|
|
||||||
partial: url.split('$repo')[0],
|
|
||||||
mirrorListable: mirrorListable.indexOf(protocol) >= 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getMirror(name: string): Mirror {
|
|
||||||
return mirrors[name] = {
|
|
||||||
pk: mirrors[name]?.pk || ++mirrorCounter,
|
|
||||||
model: 'mirrors.Mirror',
|
|
||||||
fields: {
|
|
||||||
name: name,
|
|
||||||
tier: -1,
|
|
||||||
upstream: null,
|
|
||||||
admin_email: '',
|
|
||||||
alternate_email: '',
|
|
||||||
public: true,
|
|
||||||
active: false,
|
|
||||||
isos: false,
|
|
||||||
rsync_user: '',
|
|
||||||
rsync_password: '',
|
|
||||||
bug: null,
|
|
||||||
notes: ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateMirror(m: Mirror, profile: MirrorProfile, url: UrlComponents) {
|
|
||||||
m.fields.tier = Math.max(m.fields.tier, profile.tier);
|
|
||||||
m.fields.admin_email = profile.admin_email || m.fields.admin_email;
|
|
||||||
m.fields.alternate_email = profile.alternate_email || m.fields.alternate_email;
|
|
||||||
m.fields.notes = profile.notes || m.fields.notes;
|
|
||||||
m.fields.active ||= profile.active;
|
|
||||||
m.fields.public &&= profile.public;
|
|
||||||
m.fields.isos ||= !!profile.stable_isos || !!profile.weekly_isos;
|
|
||||||
if (url.protocol === 'rsync') {
|
|
||||||
m.fields.rsync_user = profile.rsync_user || '';
|
|
||||||
m.fields.rsync_password = profile.rsync_password || '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function processMirrorProfile(m: MirrorProfile, index: number) {
|
|
||||||
const url: UrlComponents = processUrl(m.url);
|
|
||||||
const mirror: Mirror = getMirror(m.force_mirror_name || url.name);
|
|
||||||
updateMirror(mirror, m, url);
|
|
||||||
const mirrorUrl: MirrorUrl = {
|
|
||||||
pk: index + 1,
|
|
||||||
model: 'mirrors.MirrorUrl',
|
|
||||||
fields: {
|
|
||||||
url: url.partial,
|
|
||||||
protocol: protocolId[url.protocol],
|
|
||||||
mirror: mirror.pk,
|
|
||||||
country: resolveCountry(m.country)?.iso2 || undefined,
|
|
||||||
// populate ip fields with `mirrorresolv`
|
|
||||||
has_ipv4: false,
|
|
||||||
has_ipv6: false,
|
|
||||||
active: m.active
|
|
||||||
}
|
|
||||||
};
|
|
||||||
mirrorUrls.push(mirrorUrl);
|
|
||||||
pushMirrorProfile(m.force_mirror_name || url.name, m);
|
|
||||||
}
|
|
||||||
|
|
||||||
function pushMirrorProfile(name: string, m: MirrorProfile) {
|
|
||||||
const list: MirrorProfile[] = mirrorProfilesByMirrorName[name] ||= [];
|
|
||||||
list.push(m);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateUpstream(m: MirrorProfile) {
|
|
||||||
const url: UrlComponents = processUrl(m.url);
|
|
||||||
const mirror: Mirror = mirrors[m.force_mirror_name || url.name];
|
|
||||||
mirror.fields.upstream = (m.upstream && mirrors[m.upstream]?.pk) || mirror.fields.upstream;
|
|
||||||
}
|
|
||||||
|
|
||||||
function composeMirrorFixture(): FixtureObject[] {
|
|
||||||
const fixture: FixtureObject[] = [];
|
|
||||||
for (let mirrorName in mirrors) {
|
|
||||||
fixture.push(mirrors[mirrorName]);
|
|
||||||
}
|
|
||||||
fixture.push.apply(fixture, mirrorUrls);
|
|
||||||
return fixture;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDateTime(): string {
|
|
||||||
const now = new Date();
|
|
||||||
return now.toISOString().split('T')[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateMirrorlist(mirrors: MirrorProfile[] = []): string {
|
|
||||||
const httpMirrors = mirrors.filter(m => processUrl(m.url).mirrorListable);
|
|
||||||
const mirrorsByRegion = new MirrorsByRegion(httpMirrors);
|
|
||||||
const lines: string[] = [
|
|
||||||
'##',
|
|
||||||
'## Artix Linux repository mirrorlist',
|
|
||||||
`## Generated on ${getDateTime()} by artix-mlg`,
|
|
||||||
'##',
|
|
||||||
'',
|
|
||||||
'# Artix mirrors',
|
|
||||||
'# Use rankmirrors(1) to get a list of the fastest mirrors for your location,',
|
|
||||||
'# e.g.: rankmirrors -v -n 5 /etc/pacman.d/mirrorlist',
|
|
||||||
'# Then put the resulting list on top of this file.',
|
|
||||||
'',
|
|
||||||
'# Default mirrors'
|
|
||||||
];
|
|
||||||
httpMirrors.filter(m => m.default).forEach(m => lines.push(`Server = ${m.url}`));
|
|
||||||
lines.push('');
|
|
||||||
mirrorsByRegion.printMirrors(lines);
|
|
||||||
return lines.join('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function generateMirrorMd(): Promise<string> {
|
|
||||||
async function tryReadHeader(): Promise<string[]> {
|
|
||||||
try {
|
|
||||||
return [await fsp.readFile(mdHeadFile, 'utf-8')];
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
if (verbose) {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function pushTableRowIfTruthy(lines: string[], label: string, value: string | undefined | null | false) {
|
|
||||||
if (value) {
|
|
||||||
lines.push(`| ${label} | ${value} |`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function findFirstWithChild<T, K extends keyof T>(profiles: T[], key: K): T[K] | undefined {
|
|
||||||
return profiles.find(p => !!p[key])?.[key];
|
|
||||||
}
|
|
||||||
function printEmail(email: string | undefined) {
|
|
||||||
if (!email) {
|
|
||||||
return email;
|
|
||||||
}
|
|
||||||
const addr = addrs.parseOneAddress(email)?.['address'];
|
|
||||||
return `[${email.replaceAll('<', '<').replaceAll('>', '>')}](mailto:${addr})`;
|
|
||||||
}
|
|
||||||
const lines: string[] = await tryReadHeader();
|
|
||||||
lines.push('# Mirrors\n\nContact or other information for the mirrors of our repositories and ISOs.\n');
|
|
||||||
for (let mirrorName in mirrorProfilesByMirrorName) {
|
|
||||||
const profiles: MirrorProfile[] = mirrorProfilesByMirrorName[mirrorName];
|
|
||||||
const activeProfiles: MirrorProfile[] = profiles.filter(p => p.active);
|
|
||||||
const urls: string[] = profiles.map(p => {
|
|
||||||
const url = p.url.split('$repo')[0];
|
|
||||||
return p.active ? url : `${url} (inactive)`;
|
|
||||||
});
|
|
||||||
const upstream = findFirstWithChild(activeProfiles, 'upstream');
|
|
||||||
lines.push(`### ${mirrorName}`);
|
|
||||||
lines.push(`| Mirror | ${mirrorName} |`);
|
|
||||||
lines.push('| ------ | ------------- |');
|
|
||||||
if (upstream) {
|
|
||||||
lines.push(`| Sync Source | [${upstream}](#${upstream.replaceAll('.', '')}) |`);
|
|
||||||
}
|
|
||||||
lines.push(`| URLs | ${urls.join('<br>')} |`);
|
|
||||||
pushTableRowIfTruthy(lines, 'Provides Stable ISO', findFirstWithChild(activeProfiles, 'stable_isos'));
|
|
||||||
pushTableRowIfTruthy(lines, 'Provides Weekly ISO', findFirstWithChild(activeProfiles, 'weekly_isos'));
|
|
||||||
// pushTableRowIfTruthy(lines, 'Bandwidth', findFirstWithChild(activeProfiles, 'bandwidth'));
|
|
||||||
// pushTableRowIfTruthy(lines, 'Frequency', findFirstWithChild(activeProfiles, 'frequency'));
|
|
||||||
// pushTableRowIfTruthy(lines, 'Hosted by', findFirstWithChild(activeProfiles, 'org'));
|
|
||||||
pushTableRowIfTruthy(lines, 'Location', findFirstWithChild(activeProfiles, 'country') || findFirstWithChild(profiles, 'country'));
|
|
||||||
pushTableRowIfTruthy(lines, 'Contact Details', printEmail(findFirstWithChild(activeProfiles, 'admin_email') || findFirstWithChild(profiles, 'admin_email')));
|
|
||||||
pushTableRowIfTruthy(lines, 'Altenate Contact Details', printEmail(findFirstWithChild(activeProfiles, 'alternate_email') || findFirstWithChild(profiles, 'alternate_email')));
|
|
||||||
lines.push('');
|
|
||||||
}
|
|
||||||
return lines.join('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function tryReadFileOrDefault<T>(f: PathLike, d: T): Promise<T> {
|
|
||||||
try {
|
try {
|
||||||
return JSON.parse(await fsp.readFile(f, { encoding: 'utf-8' }))
|
return JSON.parse(await fsp.readFile(f, { encoding: 'utf-8' }))
|
||||||
}
|
}
|
||||||
@@ -270,20 +18,12 @@ async function tryReadFileOrDefault<T>(f: PathLike, d: T): Promise<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const fixture: FixtureObject[] = await tryReadFileOrDefault<FixtureObject[]>(fixtureFile, []);
|
|
||||||
const mirrors: MirrorMap = setMirrors(fixture);
|
|
||||||
const mirrorUrls: MirrorUrl[] = [];
|
|
||||||
const mirrorProfilesByMirrorName: { [mirrorName: string]: MirrorProfile[] } = {};
|
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const input: MirrorInput = JSON.parse(await fsp.readFile(inputFile, { encoding: 'utf-8' }));
|
const options: ArtixMlgOptions = {
|
||||||
|
fixture: await tryReadFileOrDefault<FixtureObject[]>(fixtureFile, [], !!process.env['VERBOSE'])
|
||||||
input.mirrors.forEach(processMirrorProfile);
|
};
|
||||||
input.mirrors.forEach(updateUpstream);
|
const mlg = new ArtixMlg(options);
|
||||||
|
mlg.run();
|
||||||
await fsp.writeFile(fixtureFile, JSON.stringify(composeMirrorFixture(), null, 4));
|
|
||||||
await fsp.writeFile(mirrorList, generateMirrorlist(input.mirrors?.filter(m => m.public && m.active && !m.suppress) || []));
|
|
||||||
await fsp.writeFile(mirrorMd, await generateMirrorMd());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default main;
|
export default main;
|
||||||
|
|||||||
73
src/markdown.ts
Normal file
73
src/markdown.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import fsp from 'fs/promises';
|
||||||
|
import addrs from "email-addresses";
|
||||||
|
import type { PathLike } from 'fs';
|
||||||
|
import type { MirrorProfile } from './mirrorProfile.js';
|
||||||
|
|
||||||
|
const verbose = !!process.env['VERBOSE'];
|
||||||
|
|
||||||
|
type MirrorProfilesByMirrorName = { [mirrorName: string]: MirrorProfile[] };
|
||||||
|
|
||||||
|
async function tryReadHeader(mdHeadFile: PathLike): Promise<string[]> {
|
||||||
|
try {
|
||||||
|
return [await fsp.readFile(mdHeadFile, 'utf-8')];
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
if (verbose) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function pushTableRowIfTruthy(lines: string[], label: string, value: string | undefined | null | false) {
|
||||||
|
if (value) {
|
||||||
|
lines.push(`| ${label} | ${value} |`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function findFirstWithChild<T, K extends keyof T>(profiles: T[], key: K): T[K] | undefined {
|
||||||
|
return profiles.find(p => !!p[key])?.[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
function printEmail(email: string | undefined) {
|
||||||
|
if (!email) {
|
||||||
|
return email;
|
||||||
|
}
|
||||||
|
const addr = addrs.parseOneAddress(email)?.['address'];
|
||||||
|
return `[${email.replaceAll('<', '<').replaceAll('>', '>')}](mailto:${addr})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function generateMirrorMd(mdHeadFile: PathLike, mirrorProfilesByMirrorName: MirrorProfilesByMirrorName): Promise<string> {
|
||||||
|
const lines: string[] = await tryReadHeader(mdHeadFile);
|
||||||
|
lines.push('# Mirrors\n\nContact or other information for the mirrors of our repositories and ISOs.\n');
|
||||||
|
for (let mirrorName in mirrorProfilesByMirrorName) {
|
||||||
|
const profiles: MirrorProfile[] = mirrorProfilesByMirrorName[mirrorName];
|
||||||
|
const activeProfiles: MirrorProfile[] = profiles.filter(p => p.active);
|
||||||
|
const urls: string[] = profiles.map(p => {
|
||||||
|
const url = p.url.split('$repo')[0];
|
||||||
|
return p.active ? url : `${url} (inactive)`;
|
||||||
|
});
|
||||||
|
const upstream = findFirstWithChild(activeProfiles, 'upstream');
|
||||||
|
lines.push(`### ${mirrorName}`);
|
||||||
|
lines.push(`| Mirror | ${mirrorName} |`);
|
||||||
|
lines.push('| ------ | ------------- |');
|
||||||
|
if (upstream) {
|
||||||
|
lines.push(`| Sync Source | [${upstream}](#${upstream.replaceAll('.', '')}) |`);
|
||||||
|
}
|
||||||
|
lines.push(`| URLs | ${urls.join('<br>')} |`);
|
||||||
|
pushTableRowIfTruthy(lines, 'Provides Stable ISO', findFirstWithChild(activeProfiles, 'stable_isos'));
|
||||||
|
pushTableRowIfTruthy(lines, 'Provides Weekly ISO', findFirstWithChild(activeProfiles, 'weekly_isos'));
|
||||||
|
// pushTableRowIfTruthy(lines, 'Bandwidth', findFirstWithChild(activeProfiles, 'bandwidth'));
|
||||||
|
// pushTableRowIfTruthy(lines, 'Frequency', findFirstWithChild(activeProfiles, 'frequency'));
|
||||||
|
// pushTableRowIfTruthy(lines, 'Hosted by', findFirstWithChild(activeProfiles, 'org'));
|
||||||
|
pushTableRowIfTruthy(lines, 'Location', findFirstWithChild(activeProfiles, 'country') || findFirstWithChild(profiles, 'country'));
|
||||||
|
pushTableRowIfTruthy(lines, 'Contact Details', printEmail(findFirstWithChild(activeProfiles, 'admin_email') || findFirstWithChild(profiles, 'admin_email')));
|
||||||
|
pushTableRowIfTruthy(lines, 'Altenate Contact Details', printEmail(findFirstWithChild(activeProfiles, 'alternate_email') || findFirstWithChild(profiles, 'alternate_email')));
|
||||||
|
lines.push('');
|
||||||
|
}
|
||||||
|
return lines.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
export default generateMirrorMd;
|
||||||
|
export { generateMirrorMd };
|
||||||
|
export type { MirrorProfilesByMirrorName };
|
||||||
33
src/mirrorlist.ts
Normal file
33
src/mirrorlist.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import MirrorsByRegion from './mirrorsByRegion.js';
|
||||||
|
import {processUrl} from './processUrl.js';
|
||||||
|
import type {MirrorProfile} from './mirrorProfile.js';
|
||||||
|
|
||||||
|
function getDateTime(): string {
|
||||||
|
const now = new Date();
|
||||||
|
return now.toISOString().split('T')[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateMirrorlist(mirrors: MirrorProfile[] = []): string {
|
||||||
|
const httpMirrors = mirrors.filter(m => processUrl(m.url).mirrorListable);
|
||||||
|
const mirrorsByRegion = new MirrorsByRegion(httpMirrors);
|
||||||
|
const lines: string[] = [
|
||||||
|
'##',
|
||||||
|
'## Artix Linux repository mirrorlist',
|
||||||
|
`## Generated on ${getDateTime()} by artix-mlg`,
|
||||||
|
'##',
|
||||||
|
'',
|
||||||
|
'# Artix mirrors',
|
||||||
|
'# Use rankmirrors(1) to get a list of the fastest mirrors for your location,',
|
||||||
|
'# e.g.: rankmirrors -v -n 5 /etc/pacman.d/mirrorlist',
|
||||||
|
'# Then put the resulting list on top of this file.',
|
||||||
|
'',
|
||||||
|
'# Default mirrors'
|
||||||
|
];
|
||||||
|
httpMirrors.filter(m => m.default).forEach(m => lines.push(`Server = ${m.url}`));
|
||||||
|
lines.push('');
|
||||||
|
mirrorsByRegion.printMirrors(lines);
|
||||||
|
return lines.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
export default generateMirrorlist;
|
||||||
|
export { generateMirrorlist };
|
||||||
38
src/processUrl.ts
Normal file
38
src/processUrl.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import parseUrl from 'extract-tld';
|
||||||
|
|
||||||
|
const protocolId: Record<Protocol, number> = {
|
||||||
|
http: 1,
|
||||||
|
rsync: 3,
|
||||||
|
https: 5,
|
||||||
|
ftp: 9
|
||||||
|
}
|
||||||
|
|
||||||
|
type Protocol = 'http' | 'https' | 'rsync' | 'ftp';
|
||||||
|
|
||||||
|
interface UrlComponents {
|
||||||
|
full: string;
|
||||||
|
name: string;
|
||||||
|
partial: string;
|
||||||
|
protocol: Protocol;
|
||||||
|
mirrorListable: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function processUrl(url: string): UrlComponents {
|
||||||
|
const mirrorListable: Protocol[] = ['http', 'https'];
|
||||||
|
const protocol = url.split(':')[0] as Protocol;
|
||||||
|
return {
|
||||||
|
name: parseUrl.parseUrl(url)?.domain,
|
||||||
|
full: url,
|
||||||
|
protocol,
|
||||||
|
partial: url.split('$repo')[0],
|
||||||
|
mirrorListable: mirrorListable.indexOf(protocol) >= 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getProtocolId(protocol: Protocol): number {
|
||||||
|
return protocolId[protocol];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default processUrl;
|
||||||
|
export { processUrl, getProtocolId };
|
||||||
|
export type { Protocol, UrlComponents };
|
||||||
Reference in New Issue
Block a user