diff --git a/.gitignore b/.gitignore index 6704566..fc82696 100644 --- a/.gitignore +++ b/.gitignore @@ -102,3 +102,6 @@ dist # TernJS port file .tern-port + +# Local build log database +buildslogs.db* diff --git a/cpp-build-results.js b/cpp-build-results.js index 6f33e86..a149f73 100644 --- a/cpp-build-results.js +++ b/cpp-build-results.js @@ -10,10 +10,11 @@ const {DynamoDBClient, ScanCommand, QueryCommand} = require ('@aws-sdk/client-dy class CppBuildResultsView { /** * - * @param {BuildLogging} logging + * @param {BuildLogging} logging * @param {BuildAnnotations} annotations + * @param {LibraryRepos} libraryrepos */ - constructor(logging, annotations, conanfunc, compilernames, compilersemvers) { + constructor(logging, annotations, conanfunc, compilernames, compilersemvers, libraryrepos) { this.results_view = pug.compileFile('views/library_build_results.pug'); this.logging = logging; @@ -21,6 +22,7 @@ class CppBuildResultsView { this.conanfunc = conanfunc; this.compilernames = compilernames; this.compilersemvers = compilersemvers; + this.libraryrepos = libraryrepos; this.ddbClient = new DynamoDBClient({region: 'us-east-1'}); } @@ -107,25 +109,15 @@ class CppBuildResultsView { const clang_semvers = succeeded_compilers.filter(result => result.compiler === 'clang').map(result => semver.parse(result.compiler_semver)).filter(Boolean); const min_clang_ver = _.first(semver.sort(clang_semvers)); - let repo_owner = ""; - let repo_name = ""; - if (library === "fmt") { - repo_owner = "fmtlib"; - repo_name = "fmt" - } else if (library === "beman_iterator_interface") { - repo_owner = "bemanproject"; - repo_name = "iterator_interface" - } else { - repo_owner = "notsupported" - repo_name = "notsupported" - } - + const repo = this.libraryrepos ? this.libraryrepos.get('c++', library, library_version) : null; + return await this.results_view({ lib: { - commit_url: `https://github.com/${repo_owner}/${repo_name}/commit/${commit_hash}`, + commit_url: repo ? `${repo.commit_url_prefix}${commit_hash}` : '', commit_hash: commit_hash, - repo_owner, - repo_name, + repo_owner: repo ? repo.owner : '', + repo_name: repo ? repo.name : '', + repo_host: repo ? repo.host : '', name: `${library}`, version: `${library_version}` }, diff --git a/docs/api.md b/docs/api.md index a190c58..0b383eb 100644 --- a/docs/api.md +++ b/docs/api.md @@ -3,12 +3,22 @@ The proxy forwards all /v1 and /v2 API calls to Conan, everything there should be as usual. +Note that the Conan passthrough only ever lists packages that *exist*. If a library/compiler +combination is absent from `/v1` (or from `/binaries`), that says nothing about why: it may never +have been attempted, or it may have been attempted and failed. The endpoints under +[Build failures and logs](#build-failures-and-logs) are what tell those two cases apart. + ## Listings ### GET `/libraries` To get a list of available libraries and versions, this includes library and version information from Compiler-Explorer. +### GET `/libraries/cpp`, `/libraries/rust`, `/libraries/fortran`, `/libraries/go` + +The same document as `/libraries`, filtered down to the libraries of one language. Anything not +tagged with that language is left out; the entries themselves are unchanged. + ### GET `/binaries/:libraryid/:version` To get a list of available binaries and for what compiler and configuration they were built. @@ -81,6 +91,25 @@ Returns: } ``` +### GET `/annotations/:libraryid/:version` + +Every annotation held for a library version, in one call, rather than one build hash at a time. + +Returns an array with one entry per build hash found on disk, each holding the `annotation` (the +same document the call below returns) and the `buildinfo` read from Conan's own `conaninfo.txt`: + +``` +[ + { + "buildhash": "16ee6b51398740e37d34e117ee3defa26bd4104e", + "annotation": { "commithash": "7eddbfed", "cxx11": true, ... }, + "buildinfo": { ... } + } +] +``` + +Returns an empty array if the library version has no packages at all. + ### GET `/annotations/:libraryid/:version/:buildhash` Annotations are json documents that are saved next to a certain library version and build. @@ -102,6 +131,144 @@ Returns: ``` +## Downloads + +These redirect (302) to the package tarball on the Conan server rather than serving it themselves. + +Packages for proprietary compilers (Intel ICC, MSVC, QNX, EDG) are never handed out. The +five-argument form below rejects them with a 403; the three-argument form usually returns 404 +instead, because those compilers have more than one binary per library version and so fail its +single-binary check before the restriction is even consulted. + +### GET `/downloadcshared/:libraryid/:version` + +Redirects to the `cshared` build of a library version — the one built for use from C. Only works +when exactly one `cshared` binary exists for that version; returns 404 otherwise. + +### GET `/downloadpkg/:libraryid/:version/:compiler` + +Redirects to the package built by a given compiler, e.g. `/downloadpkg/fmt/10.0.0/g131`. + +This only resolves when that compiler has exactly one binary for the version. A library built for +several architectures or standard libraries will have more than one, so use the five-argument form +below for those. Returns 404 when nothing matches. + +### GET `/downloadpkg/:library/:library_version/:compiler_version/:arch/:libcxx` + +Redirects to the package for one fully specified build combination, e.g. +`/downloadpkg/fmt/10.0.0/g131/x86_64/libstdc%2B%2B`. + +`libcxx` needs url-encoding: `libstdc++` becomes `libstdc%2B%2B`. Combinations with no architecture +or no libcxx are addressed with a literal space (`%20`) in that position, since an empty path +segment would not route. + +Returns 404 when nothing matches. + + +## Build failures and logs + +A build that is missing from Conan was either never attempted or attempted and failed. These are +the endpoints that say which. + +### GET `/failedbuilds/:library/:library_version` + +The build combinations that were attempted for a library version and failed, one record per +combination: + +``` +[ + { + "compiler": "gcc", + "compiler_version": "g161", + "arch": "x86_64", + "libcxx": "libstdc++", + "compiler_flags": "", + "commithash": "6.7.0" + } +] +``` + +The absence of a combination here means it was never attempted; its presence means it was attempted +and failed. That distinction is the point of this endpoint — Conan itself cannot tell you the +difference, because it only ever lists packages that exist. + +Only failures are returned; successful builds are not listed. Use the `commithash` from a record +here as the `:commithash` for `/getlogging_forcommit` below. + +### GET `/getlogging/:library/:library_version/:arch/:dt` + +The stored build log for a library version, architecture and build timestamp, served as a text +attachment. + +`:dt` is the `build_dt` of the build, in `YYYYMMDDHHMMSS` form, as returned by `/allfailedbuilds`. +An architecture that is empty is addressed with a literal space (`%20`). If several builds share +that timestamp and architecture, their logs are concatenated, separated by +`--- More entries ---`. Returns 404 when no log is stored, and logs trimmed by the age-based +cleanup come back as the single line `Log eliminated due to age`. + +Both logging endpoints send `Content-Type: text/html` regardless of content; it is the +`Content-Disposition` header that makes browsers save it as a `.txt` file. + +### GET `/getlogging_forcommit/:library/:library_version/:commithash/:compiler_version/:arch/:libcxx` + +The stored build log for one exact build combination, served as a text attachment. This is usually +the one you want after `/failedbuilds` has told you which combination failed. + +``` +/getlogging_forcommit/qt/6.7.0/6.7.0/g161/x86_64/libstdc%2B%2B +``` + +Two things to watch: + +* `libcxx` needs url-encoding — `libstdc++` is `libstdc%2B%2B` — and an empty `arch` or `libcxx` is + addressed with a literal space (`%20`). +* `:commithash` is whatever was recorded with `/buildfailed`, which for a released library version + is frequently just the version string (`6.7.0`) rather than a git hash. Take it from the + `/failedbuilds` record or from `/whathasfailedbefore`, both of which return it, instead of + guessing. + +What comes back is the whole captured build output, which begins with the generated `#!/bin/sh` +build script — compiler paths, flags, the `cmake` invocation — and only then the compiler output. +The first screenful is therefore never the failure; the logs run to hundreds of kilobytes, so skip +to the end. + +Returns 404 when no log is stored for that combination. + +### GET `/cpp_library_build_results/:library/:library_version/:commit_hash` + +An **HTML** page (not JSON) summarising how one commit of a library built across every compiler, +with per-compiler links to the log or to the package. Build history comes from the DynamoDB +`library-build-history` table, so this covers more than the recent failures in the local database. + +``` +/cpp_library_build_results/fmt/trunk/7eddbfed +``` + +A `?allcompilers=1` query parameter is accepted and currently has no effect — the compressed view +it used to select is commented out in the template. + +### GET `/cpp_compiler_build_results/:compiler_id` + +An **HTML** page (not JSON), the same data from the other direction: how every library fared for +one compiler, e.g. `/cpp_compiler_build_results/g161`. Also sourced from DynamoDB, and covers all +architecture/libcxx variants of that compiler id. + + +## Service + +### GET `/healthcheck` + +Returns `OK`. No authentication. + +### GET `/reinitialize` + +Re-reads everything the proxy caches at startup: the compiler and library lists from +Compiler-Explorer, the packages on the Conan server, the library repository mapping and the +compiler variants. Returns `done` once that has finished. + +No authentication, and it does real work on every call, so do not put it behind anything that +retries. + ## Secure API The following calls require authentication. This is done by posting to `/login` and using the resulting token in the `Authentication` header. diff --git a/html/gh-commits.js b/html/gh-commits.js index b5ee63e..a47dd68 100644 --- a/html/gh-commits.js +++ b/html/gh-commits.js @@ -1,6 +1,6 @@ -import { Octokit, App } from "https://esm.sh/octokit"; +import { Octokit } from "https://esm.sh/octokit"; -async function list_last_10(owner, repo, lib_name, lib_version) { +async function list_last_10(morediv, owner, repo, lib_name, lib_version) { const octokit = new Octokit({ // auth: 'YOUR-TOKEN' }); @@ -13,7 +13,6 @@ async function list_last_10(owner, repo, lib_name, lib_version) { } }); - const morediv = $(".more-commits"); for (const item of list.data) { const shorthash = item.sha.substring(0, 8); @@ -31,4 +30,8 @@ async function list_last_10(owner, repo, lib_name, lib_version) { } } -list_last_10(window.repo_owner, window.repo_name, window.lib_name, window.lib_version); +const morediv = $(".more-commits"); +const config = morediv.length ? morediv[0].dataset : null; +if (config && config.repoOwner && config.repoName) { + list_last_10(morediv, config.repoOwner, config.repoName, config.libName, config.libVersion); +} diff --git a/index.js b/index.js index ec8f64e..486dc69 100644 --- a/index.js +++ b/index.js @@ -6,6 +6,7 @@ const { CppBuildResultsView } = require('./cpp-build-results'), { CppCompilerBuildResultsView, CompilerVariantsCache } = require('./cpp-compiler-build-results'), { is_restricted_compiler } = require('./compiler-restrictions'), + { LibraryRepos } = require('./library-repos'), path = require('path'), express = require('express'), { expressjwt } = require('express-jwt'), @@ -39,6 +40,7 @@ let modifiedDt = null; const annotations = new BuildAnnotations(conanserverroot); const buildlogging = new BuildLogging(buildlogspath); +const libraryRepos = new LibraryRepos(); const compilerVariantsCache = new CompilerVariantsCache([ 'fmt#10.0.0#10.0.0', 'zlib#1.3.1#1.3.1', @@ -194,6 +196,15 @@ async function refreshCELibraries() { allGoLibrariesAndVersions = go; } +// A stale repo map only costs us a missing link, so never let this take the server down. +async function refreshLibraryRepos() { + try { + await libraryRepos.refresh(); + } catch (e) { + console.error('libraryRepos refresh failed:', e); + } +} + function newProxy() { const proxy = httpProxy.createProxyServer({}); @@ -397,6 +408,7 @@ function main() { await refreshCECompilers(); await refreshCELibraries(); await refreshConanLibraries(true); + await refreshLibraryRepos(); await compilerVariantsCache.refresh(); modifiedDt = new Date(); res.send('done'); @@ -557,7 +569,7 @@ function main() { } if (!found) - res.send(404); + res.sendStatus(404); } catch (e) { console.error(e); res.send(e); @@ -744,7 +756,8 @@ function main() { new RemoteAnnotations(conanserverurl), getConanBinaries, compilernames, - compilersemvers + compilersemvers, + libraryRepos ); res.send(await view.get(req.params.library, req.params.library_version, req.params.commit_hash, req.query.allcompilers === '1')); }) @@ -774,7 +787,7 @@ function main() { buildlogging.connect().then(refreshCECompilers).then(refreshCELibraries).then(() => { return refreshConanLibraries(true); -}).then(() => { +}).then(refreshLibraryRepos).then(() => { compilerVariantsCache.refresh().catch(e => console.error('compilerVariantsCache initial refresh failed:', e)); return main(); }); diff --git a/library-repos.js b/library-repos.js new file mode 100644 index 0000000..bc7e0f6 --- /dev/null +++ b/library-repos.js @@ -0,0 +1,232 @@ +const + https = require('https'), + yaml = require('js-yaml'); + +const LIBRARIES_YAML_URL = 'https://raw.githubusercontent.com/compiler-explorer/infra/main/bin/yaml/libraries.yaml'; + +// Hosting types used by libraries.yaml, and how each one spells a commit url. +const HOSTS = { + github: { base: 'https://github.com', commit_path: 'commit' }, + gitlab: { base: 'https://gitlab.com', commit_path: '-/commit' }, + bitbucket: { base: 'https://bitbucket.org', commit_path: 'commits' }, +}; + +const FETCH_TIMEOUT_MS = 10000; + +const TEMPLATE_RE = /\{\{(.*?)\}\}/g; +const CONTEXT_RE = /^context\[(-?\d+)\]$/; +const REPLACE_RE = /^replace\(\s*'([^']*)'\s*,\s*'([^']*)'\s*(?:,\s*(\d+)\s*)?\)$/; + +function normalize_id(library_id) { + return library_id.replace(/-/g, '_').toLowerCase(); +} + +function normalize_version(version) { + return String(version).replace(/^v/, ''); +} + +// `targets` is a list of version strings, or of {name, url} objects for the tarball libraries. +function target_names(node) { + if (!Array.isArray(node.targets)) return []; + + return node.targets + .map((target) => (target && typeof target === 'object' ? target.name : target)) + .filter((name) => typeof name === 'string'); +} + +// A library's own versions plus those of the build variants nested below it, which share its repo. +function all_target_names(node) { + let names = target_names(node); + for (const value of Object.values(node)) { + if (value && typeof value === 'object' && !Array.isArray(value)) { + names = names.concat(all_target_names(value)); + } + } + return names; +} + +function replace_n(str, search, replacement, count) { + if (!search) return str; + + let result = ''; + let rest = str; + let done = 0; + while (done < count) { + const idx = rest.indexOf(search); + if (idx < 0) break; + result += rest.slice(0, idx) + replacement; + rest = rest.slice(idx + search.length); + done++; + } + return result + rest; +} + +/** + * infra expands libraries.yaml with full jinja2; we only support the tiny subset that + * `repo:` values actually use today (`context[-1]`, optionally piped through `replace`). + * Anything else yields null, which just means we show no repo link for that library. + */ +function expand_template(value, context) { + if (!value.includes('{{')) return value; + if (value.includes('{%') || value.includes('{#')) return null; + + let failed = false; + const expanded = value.replace(TEMPLATE_RE, (_match, expression) => { + const parts = expression.split('|').map((part) => part.trim()); + + const context_match = CONTEXT_RE.exec(parts[0]); + if (!context_match) { + failed = true; + return ''; + } + + const idx = parseInt(context_match[1], 10); + let result = idx < 0 ? context[context.length + idx] : context[idx]; + if (result === undefined) { + failed = true; + return ''; + } + + for (const filter of parts.slice(1)) { + const replace_match = REPLACE_RE.exec(filter); + if (!replace_match) { + failed = true; + return ''; + } + const count = replace_match[3] === undefined ? Infinity : parseInt(replace_match[3], 10); + result = replace_n(result, replace_match[1], replace_match[2], count); + } + + return result; + }); + + return failed ? null : expanded; +} + +function fetch_url(url) { + return new Promise((resolve, reject) => { + // Startup waits on this, so never let a hung connection hold the server back. + const req = https.get(url, { headers: { Accept: 'text/plain' }, timeout: FETCH_TIMEOUT_MS }, (resp) => { + if (resp.statusCode !== 200) { + resp.resume(); + reject(new Error(`${url} returned ${resp.statusCode}`)); + return; + } + let data = ''; + resp.on('data', (chunk) => data += chunk); + resp.on('end', () => resolve(data)); + }); + req.on('timeout', () => { + req.destroy(new Error(`${url} timed out after ${FETCH_TIMEOUT_MS}ms`)); + }); + req.on('error', reject); + }); +} + +/** + * Resolves a library id to its upstream repository, using infra's libraries.yaml as the + * source of truth. Entries there are nested (groups of libraries) and inherit `repo`/`type` + * from their ancestors, so we walk the tree and record every node that declares `targets`. + */ +class LibraryRepos { + constructor(url) { + this.url = url || LIBRARIES_YAML_URL; + this.by_language = {}; + } + + make_entry(properties, context) { + if (!properties.repo) return null; + + const host = HOSTS[properties.type]; + if (!host) return null; + + const repo = expand_template(properties.repo, context); + if (!repo) return null; + + const parts = repo.split('/'); + if (parts.length !== 2 || !parts[0] || !parts[1]) return null; + + return { + owner: parts[0], + name: parts[1], + host: properties.type, + commit_url_prefix: `${host.base}/${repo}/${host.commit_path}/`, + }; + } + + add_candidate(maps, library_id, candidate) { + if (!library_id) return; + + for (const [map, key] of [[maps.exact, library_id], [maps.normalized, normalize_id(library_id)]]) { + const candidates = map.get(key); + if (candidates) candidates.push(candidate); + else map.set(key, [candidate]); + } + } + + collect(node, context, inherited, maps) { + const properties = { ...inherited }; + for (const key of ['repo', 'type']) { + if (typeof node[key] === 'string') properties[key] = node[key]; + } + + // A node with `targets` is a library, and anything nested below it is a build variant of + // that same library (eastl.built, proxy.microsoft.older) rather than a library of its own. + if ('targets' in node) { + const entry = this.make_entry(properties, context); + if (entry) { + const candidate = { entry, versions: new Set(all_target_names(node).map(normalize_version)) }; + this.add_candidate(maps, context[context.length - 1], candidate); + + // A parent without `targets` is either a group of libraries (beman, nightly) or a + // library whose versions are split over several repos (proxy, which builds <= 4.0.1 + // from microsoft/proxy and the rest from ngcpp/proxy). Only the latter makes the + // parent key a library id, and the two are indistinguishable here, so register + // under it as well and let the version decide between the candidates. + if (context.length > 2) this.add_candidate(maps, context[context.length - 2], candidate); + } + return; + } + + for (const [key, value] of Object.entries(node)) { + if (value && typeof value === 'object' && !Array.isArray(value)) { + this.collect(value, context.concat([key]), properties, maps); + } + } + } + + async refresh() { + const parsed = yaml.load(await fetch_url(this.url)); + const languages = (parsed && parsed.libraries) || {}; + + const by_language = {}; + for (const [language, libraries] of Object.entries(languages)) { + const maps = { exact: new Map(), normalized: new Map() }; + for (const [library_id, node] of Object.entries(libraries)) { + if (node && typeof node === 'object' && !Array.isArray(node)) { + this.collect(node, [language, library_id], {}, maps); + } + } + by_language[language] = maps; + } + + this.by_language = by_language; + } + + get(language, library_id, library_version) { + const maps = this.by_language[language]; + if (!maps || !library_id) return null; + + const candidates = maps.exact.get(library_id) || maps.normalized.get(normalize_id(library_id)); + if (!candidates) return null; + if (candidates.length === 1 || library_version === undefined) return candidates[0].entry; + + const version = normalize_version(library_version); + const match = candidates.find((candidate) => candidate.versions.has(version)); + return (match || candidates[0]).entry; + } +} + +module.exports = { + LibraryRepos +}; diff --git a/package-lock.json b/package-lock.json index a65be13..c126643 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "express-jwt": "^8.4.1", "http-proxy": "^1.18.1", "ini": "^4.1.3", + "js-yaml": "^4.3.1", "pug": "^3.0.3", "semver": "^7.6.3", "sql-template-strings": "^2.2.2", @@ -967,6 +968,30 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "3.15.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.1.tgz", + "integrity": "sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/@fortawesome/fontawesome-free": { "version": "6.7.2", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.7.2.tgz", @@ -1743,14 +1768,10 @@ } }, "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" }, "node_modules/array-buffer-byte-length": { "version": "1.0.2", @@ -3098,6 +3119,30 @@ "node": ">=10" } }, + "node_modules/eslint/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/eslint/node_modules/js-yaml": { + "version": "3.15.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.1.tgz", + "integrity": "sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/espree": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", @@ -4431,14 +4476,22 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", - "dev": true, + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" diff --git a/package.json b/package.json index 33cba12..40f2aad 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "index.js", "scripts": { "start": "node index.js", - "lint": "npx eslint index.js build-annotations.js build-logging.js --fix", + "lint": "npx eslint index.js build-annotations.js build-logging.js library-repos.js --fix", "devtest": "cross-env CESECRET=123456789 CEPASSWORD=1234 node index.js", "test": "echo \"Error: no test specified\" && exit 1" }, @@ -29,6 +29,7 @@ "express-jwt": "^8.4.1", "http-proxy": "^1.18.1", "ini": "^4.1.3", + "js-yaml": "^4.3.1", "pug": "^3.0.3", "semver": "^7.6.3", "sql-template-strings": "^2.2.2", diff --git a/views/library_build_results.pug b/views/library_build_results.pug index d30e8ac..f008013 100644 --- a/views/library_build_results.pug +++ b/views/library_build_results.pug @@ -5,7 +5,10 @@ block prepend content div(class='libname') Library:  | #{lib.name} #{lib.version} div(class='commit') Commit:  - a(href=lib.commit_url) #{lib.commit_hash} + if lib.commit_url + a(href=lib.commit_url) #{lib.commit_hash} + else + span #{lib.commit_hash} div(class='summary') div(class='totals') Success rate: #{summary.succeeded} / #{summary.total} @@ -54,10 +57,15 @@ block prepend content a(href=compiler.package_url target='_blank') i(class='fa-solid fa-paperclip') - div - h3 more commits + if lib.repo_host == 'github' + div + h3 more commits - ul(class="more-commits") + ul(class="more-commits" + data-repo-owner=lib.repo_owner + data-repo-name=lib.repo_name + data-lib-name=lib.name + data-lib-version=lib.version) script | if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { @@ -65,18 +73,8 @@ block prepend content | table.classList.add('table-dark'); | }); | } - script - | if ("#{lib.name}" === "fmt") { - | window.repo_owner = "fmtlib"; - | window.repo_name = "fmt" - | } else if ("#{lib.name}" === "beman_iterator_interface") { - | window.repo_owner = "bemanproject"; - | window.repo_name = "iterator_interface" - | } else { - | console.log("Library not yet supported"); - | } - | window.lib_name = "#{lib.name}"; - | window.lib_version = "#{lib.version}"; - script(src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous") - script(type="module" src="/gh-commits.js") + if lib.repo_host == 'github' + script(src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous") + + script(type="module" src="/gh-commits.js")