diff --git a/.jshintrc b/.jshintrc index 589cec4..6459544 100644 --- a/.jshintrc +++ b/.jshintrc @@ -1,4 +1,5 @@ { + "module": true, "browser": true, "node": true, "esversion": 11, @@ -16,7 +17,6 @@ "plusplus": true, "undef": true, "unused": "vars", - "strict": true, "maxdepth": 4, "maxstatements": 100, "maxcomplexity": 20 diff --git a/build-classifier.js b/build-classifier.js new file mode 100644 index 0000000..4284f3c --- /dev/null +++ b/build-classifier.js @@ -0,0 +1,6 @@ +let BuildClassifier = {}; + +BuildClassifier._empty = true; + +export let _empty = BuildClassifier._empty; +export default BuildClassifier; diff --git a/host-targets.js b/host-targets.js index b2c5638..f462b3d 100644 --- a/host-targets.js +++ b/host-targets.js @@ -1,11 +1,10 @@ -'use strict'; - /** @typedef {import('./types.js').OsString} OsString */ /** @typedef {import('./types.js').LibcString} LibcString */ /** @typedef {import('./types.js').ArchString} ArchString */ /** @typedef {import('./types.js').TargetTriplet} TargetTriplet */ +/** @typedef {import('./types.js').TargetMatcher} TargetMatcher */ -var HostTargets = module.exports; +let HostTargets = {}; let reVersionOnly = /^[\d\.]+(-RELEASE)?$/; let reLeadingVer = /^\d([\d\.\-\+_])+/; @@ -64,6 +63,7 @@ let T = { }; // OS, Arch, Libc +/** @type {Object.} */ HostTargets.TERMS = { // agent webi: {}, @@ -137,19 +137,21 @@ HostTargets._MATCHERS = { }; /** - * @param {Object.<"os"|"arch"|"libc", String>} target + * @param {Object.<"os"|"arch"|"libc", String>} targetIsh * @param {Array} terms */ -HostTargets.termsToTarget = function (target, terms) { +HostTargets.termsToTarget = function (targetIsh, terms) { let bogoTerms = []; - Object.assign(target, { errors: [] }); + let target = Object.assign(targetIsh, { errors: [] }); for (let term of terms) { let lterm = term.toLowerCase(); let hints = HostTargets.TERMS[lterm]; if (hints) { - upsertHints(target, terms, term, hints); + let debugUa = terms.join(','); + let debugTerms = [term]; + upsertHints(target, debugUa, debugTerms, hints); continue; } @@ -199,15 +201,26 @@ HostTargets.termsToTarget = function (target, terms) { return bogoTerms; }; +/** + * @param {TargetTriplet} target + * @param {String} ua + * @param {Array} terms + * @param {TargetMatcher} hints + */ function upsertHints(target, ua, terms, hints) { if (!hints) { throw new Error("[SANITY FAIL] 'hints' not provided"); } + // TODO maybe use utility type 'keyof' + /** @type {["os","arch","libc","vendor"]} */ //@ts-expect-error let keys = Object.keys(hints); for (let key of keys) { if (!target[key]) { - target[key] = hints[key]; + if (hints[key]) { + //@ts-expect-error TODO + target[key] = hints[key]; + } } if (target[key] !== hints[key]) { let msg = `'${key}' already set to '${target[key]}', not updated to '${hints[key]}'`; @@ -222,11 +235,13 @@ function upsertHints(target, ua, terms, hints) { ignore = true; } else if (hints[key] === 'musl') { // musl can be installed on a GNU system + //@ts-expect-error - TODO find out if we depend on this libs vs libcs typo (we probably do) target.libs = [target.libc, 'musl']; ignore = true; } } if (!ignore) { + //@ts-expect-error target.errors.push({ [key]: hints[key], message: msg, terms: terms }); throw new Error(`${msg} for '${ua}' / '${terms}'`); } @@ -234,7 +249,16 @@ function upsertHints(target, ua, terms, hints) { } } -// Workaround for current (2023-q4) Android misclassification +/** + * @typedef HasErrors + * @prop {Array} errors + */ + +/** + * Workaround for current (2023-q4) Android misclassification + * @param {TargetTriplet & HasErrors} target + * @param {Array} terms + */ function upsertAndroidTerms(target, terms) { if (!target.android) { return; @@ -268,3 +292,9 @@ function upsertAndroidTerms(target, terms) { } } } + +export let TERMS = HostTargets.TERMS; +export let WATERFALL = HostTargets.WATERFALL; +export let _MATCHERS = HostTargets._MATCHERS; +export let termsToTarget = HostTargets.termsToTarget; +export default HostTargets; diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 0000000..8363fa6 --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,122 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2024", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "nodenext", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + "moduleResolution": "nodenext", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + "paths": { + "build-classifier": ["./build-classifier.js"], + "build-classifier/*": ["./*"] + }, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + "typeRoots": ["./typings","./node_modules/@types"], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "rewriteRelativeImportExtensions": true, /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "noUncheckedSideEffectImports": true, /* Check side effect imports. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + "noEmit": true, /* Disable emitting files from a compilation. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + // "outDir": "./", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + }, + "include": [ + "*.js", + "bin/**/*.js", + "lib/**/*.js", + "src/**/*.js", + "tests/**/*.js" + ], + "exclude": ["node_modules"] +} \ No newline at end of file diff --git a/lexver-test.js b/lexver-test.js index 8771b24..942b9c8 100644 --- a/lexver-test.js +++ b/lexver-test.js @@ -1,6 +1,6 @@ 'use strict'; -let Lexver = require('./lexver.js'); +import Lexver from './lexver.js'; async function main() { let matchVer = process.argv[2]; @@ -77,6 +77,7 @@ async function main() { let knownKeys = Object.keys(known); for (let key of knownKeys) { + //@ts-expect-error - ignore dynamic key errors in test if (selected[key].toString() !== known[key].toString()) { console.error('Expected:'); console.error(known); diff --git a/lexver.js b/lexver.js index d296850..66f96fa 100644 --- a/lexver.js +++ b/lexver.js @@ -1,6 +1,4 @@ -'use strict'; - -var Lexver = module.exports; +let Lexver = {}; // hotfix is post-stable let channels = ['alpha', 'beta', 'dev', 'pre', 'preview', 'rc', 'hotfix']; @@ -12,6 +10,12 @@ let channelsRePost; channelsRePost = new RegExp(`(${channelsOr})([\\.\\-\\+]?)(\\d+)`); } let channelsPrePlacer = '$1-$2'; +/** + * @param {unknown} _ + * @param {String} chan + * @param {String} sep + * @param {String} ver + */ let channelsPostPlacer = function (_, chan, sep, ver) { ver = ver.padStart(2, '0'); @@ -23,6 +27,11 @@ let digitsOnlyRe = /^\d+$/; // this is a special case, but if it gets complicated in the future, we'll drop it // and just treat it like a build hash and open an issue for to the maintainer let channelsReB = /(\.\d+b)(\d+)$/; +/** + * @param {unknown} _ + * @param {String} sep + * @param {String} ver + */ let channelsBPlacer = function (_, sep, ver) { ver = ver.padStart(2, '0'); let rel = `${sep}${ver}`; @@ -75,8 +84,8 @@ Lexver.sortedToTags = function (descVersions) { /** * Parse a semver or non-standard version and return a lexical version * Ex: 1.2beta-3 => 0001.0002.0000.0000-beta-03 - * @param {String} version - a semver or other version - * @param {Object} _opts - no public options + * @param {String} fullVersion - a semver or other version + * @param {Object} [_opts] - no public options * @param {Boolean} _opts._asPrefix - don't expand 1.0 to 1.0.0, etc * @returns {String} */ @@ -89,9 +98,11 @@ Lexver.parseVersion = function (fullVersion, _opts) { // 1.2beta1 => 1.2beta1 let rels = fullVersion.split('-'); + /** @type {String} */ //@ts-expect-error - even an empty string splits let version = rels.shift(); if (version.includes('+')) { let parts = version.split('+'); + /** @type {String} */ //@ts-expect-error - even an empty string splits version = parts.shift(); let build = parts.join(`${sortSuffixBuild}`); rels.unshift(`${sortSuffixBuild}${build}`); @@ -105,6 +116,7 @@ Lexver.parseVersion = function (fullVersion, _opts) { // 1.2-beta1-a => 1.2, beta1, a let subparts = version.split('-'); + /** @type {String} */ //@ts-expect-error - even an empty string splits version = subparts.shift(); // beta1, a => beta1-a @@ -117,6 +129,7 @@ Lexver.parseVersion = function (fullVersion, _opts) { let levels = version.split('.'); let digits = []; for (; levels.length; ) { + /** @type {String} */ //@ts-expect-error - even an empty string splits let level = levels.shift(); if (!digitsOnlyRe.test(level)) { levels.unshift(level); @@ -247,3 +260,10 @@ Lexver.matchSorted = function (versions, prefix) { }; return matchInfo; }; + +export let toTags = Lexver.toTags; +export let sortedToTags = Lexver.sortedToTags; +export let parseVersion = Lexver.parseVersion; +export let parsePrefix = Lexver.parsePrefix; +export let matchSorted = Lexver.matchSorted; +export default Lexver; diff --git a/lint-host-targets.js b/lint-host-targets.js index 856195b..559bb19 100644 --- a/lint-host-targets.js +++ b/lint-host-targets.js @@ -1,22 +1,34 @@ -'use strict'; +import HostTargets from './host-targets.js'; -let HostTargets = require('./host-targets.js'); +import Fs from 'node:fs/promises'; +import Path from 'node:path'; -let uaMap = require('./uas.json'); -let uas = Object.keys(uaMap); +/** @typedef {import('./types.js').TargetTriplet} TargetTriplet */ +/** @type {Object.} */ let partialsMap = {}; +/** @type {Object.} */ let termsMap = {}; let termNames = Object.keys(HostTargets.TERMS); for (let term of termNames) { termsMap[term] = 0; } -function main() { +async function main() { //let hostTargets = HostTargets.create({}); + /** @type {Object.} */ let tripletsMap = {}; + let modulePath = import.meta.url.slice('file://'.length); + let moduleDir = Path.dirname(modulePath); + let uasPath = Path.join(moduleDir, './uas.json'); + + let uaJson = await Fs.readFile(uasPath, 'utf8'); + let uaMap = JSON.parse(uaJson); + let uas = Object.keys(uaMap); + for (let ua of uas) { + /** @type {Array} */ let terms = []; let parts = ua.split(/\s+/g); @@ -33,6 +45,7 @@ function main() { terms = terms.concat(_terms); } + /** @type {Partial} */ let target = {}; let bogoTerms; try { @@ -104,4 +117,7 @@ function main() { } } -main(); +main().catch(function (e) { + console.error(e.stack); + process.exit(1); +}); diff --git a/lint-versions.js b/lint-versions.js index 7b004c0..74c8766 100644 --- a/lint-versions.js +++ b/lint-versions.js @@ -1,9 +1,9 @@ 'use strict'; -let Path = require('node:path'); -let Fs = require('node:fs/promises'); +import Path from 'node:path'; +import Fs from 'node:fs/promises'; -let Lexver = require('./lexver.js'); +import Lexver from './lexver.js'; async function main() { let matchVer = process.argv[2]; @@ -14,7 +14,9 @@ async function main() { let versions; { - let filepath = Path.join(__dirname, 'versions.txt'); + let modulePath = import.meta.url.slice('file://'.length); + let moduleDir = Path.dirname(modulePath); + let filepath = Path.join(moduleDir, 'versions.txt'); let versionsTxt = await Fs.readFile(filepath, 'utf-8'); versionsTxt = versionsTxt.trim(); versions = versionsTxt.split('\n'); diff --git a/package-lock.json b/package-lock.json index 9f5667b..0f06654 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,7 +7,27 @@ "": { "name": "build-classifier", "version": "1.0.2", - "license": "SEE LICENSE IN LICENSE" + "license": "SEE LICENSE IN LICENSE", + "devDependencies": { + "@types/node": "^22.13.1" + } + }, + "node_modules/@types/node": { + "version": "22.13.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.1.tgz", + "integrity": "sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" } } } diff --git a/package.json b/package.json index 8744cb2..b5980c5 100644 --- a/package.json +++ b/package.json @@ -2,17 +2,38 @@ "name": "build-classifier", "version": "1.0.2", "description": "Determine Host Target Triplet (Arch, Libc, OS, Vendor) from a filenames / download URL and uname / User-Agent strings.", - "main": "index.js", + "main": "./build-classifier.js", + "files": [ + "./*.js", + "./build-classifier.js", + "./bin/*.js", + "./lib/*.js" + ], + "type": "module", + "imports": { + "build-classifier": "./build-classifier.js", + "build-classifier/": "./" + }, + "exports": { + ".": "./build-classifier.js", + "./*": "./*" + }, "scripts": { "bump": "npm version -m \"chore(release): bump to v%s\"", "fmt": "npm run prettier", - "lint": "npm run jshint", + "lint": "npm run jshint && npm run tsc", + "prepublish": "npm run reexport-types", "test": "node lexver-test.js", "----": "------------------------------------", "jshint": "npx -p jshint@2.x -- jshint -c ./.jshintrc --exclude 'node_modules/**/*' *.js", - "prettier": "npx -p prettier@3.x -- prettier -w '**/*.{js,md,html}'" + "prettier": "npx -p prettier@3.x -- prettier -w '**/*.{js,md,html}'", + "reexport-types": "npx -p jswt@2.x -- reexport", + "tsc": "! npx -p typescript@5.x -- tsc -p ./jsconfig.json | grep '\\.js([0-9]\\+,[0-9]\\+): error' | grep -v '\\ (https://therootcompany.com/)", - "license": "SEE LICENSE IN LICENSE" + "license": "SEE LICENSE IN LICENSE", + "devDependencies": { + "@types/node": "^22.13.1" + } } diff --git a/triplet.js b/triplet.js index 66eae3f..58f369d 100644 --- a/triplet.js +++ b/triplet.js @@ -1,1010 +1,1084 @@ -/** - * @typedef Triplet - * @prop {Array} TERMS_CHANNEL - * @prop {Array} TERMS_CHECKSUM - * @prop {Array} TERMS_NON_BUILD - * @prop {Array} TERMS_EXTS_NON_BUILD - * @prop {Array} TERMS_EXTS_BUILD - * @prop {Array} TERMS_EXTS_NON_INFORMATIVE - * @prop {Array} TERMS_EXTS_SOURCE - */ - /** @typedef {import('./types.js').OsString} OsString */ /** @typedef {import('./types.js').LibcString} LibcString */ /** @typedef {import('./types.js').ArchString} ArchString */ /** @typedef {import('./types.js').TargetTriplet} TargetTriplet */ +/** @typedef {import('./types.js').TargetMatcher} TargetMatcher */ /** @type {Triplet} */ -//@ts-ignore -var Triplet = ('object' === typeof module && exports) || {}; -(function (window, Triplet) { - /*jshint maxstatements: 2000 */ - 'use strict'; - - Triplet.TERMS_CHANNEL = ['master', 'nightly']; - Triplet.TERMS_CHECKSUM = [ - 'B3SUMS', - 'checksum', - 'MD5SUMS', - 'SHA1SUMS', - 'SHA256SUMS', - 'SHA512SUMS', - ]; - Triplet.TERMS_NON_BUILD = [ - 'bootstrap', - 'debug', // TODO dashd - 'setup', - 'source', - 'src', - 'symbols', - // a build, but not one you'd use given the alternative - 'unsigned', // TODO dashd - 'vendor', // TODO rclone go vendor - ]; - Triplet._RE_TERMS_NON_BUILD = []; - for (let term of Triplet.TERMS_NON_BUILD) { - let re = new RegExp(`(\\b|_)(${term})(\\b|_)`); - Triplet._RE_TERMS_NON_BUILD.push(re); - } - Triplet.TERMS_EXTS_NON_BUILD = [ - '.1', - '.asc', - '.b3', - '.json', - '.md5', - '.pem', - '.sbom', - '.sha256', - '.sha256sum', - '.sha512', - '.sig', - '.txt', - // no android - '.apk', - // we can't use these yet - '.deb', - '.rpm', - // windows something... not sure - '.msixbundle', - ]; - - // many assets are bare - we'd have to check if any contain a '.' - // in the name before using these as a whitelist - // (ordered by OS for clarity, by length for matching) - Triplet.TERMS_EXTS_PKG = [ - // Windows - // '.exe.zip', - // '.exe.xz', - '.exe', - '.msi', - '.msixbundle', - // macOS - // '.app.zip', - //'.dmg.zip', // because some systems only allow .zip - '.app', // for classification (it'll be in a zip or dmg) - '.dmg', - '.pkg', - // Enterprise Linux - '.rpm', - // Debian - '.deb', - // Android - '.apk', - // Nondescript - '.tar', - // '.tar.bz2', // ('.tbz2' is never used) - // '.tar.gz', // ('.tgz' is never used) - // '.tar.xz', // ('.txz' is never used) - // '.tar.zst', // ('.tzst' is never used) - // POSIX - // (also, could be the runnable, installable thing, or an install script) - '.sh', - // Any (font, vim script) - '.git', - ]; - - Triplet.TERMS_EXTS_ZIP = [ - '.bz2', // -j, --bzip2 (for completeness, not used) - '.gz', // -z, --gzip - '.xz', // -J, --xz - '.zip', // (auto on BSD tar on Windows and macOS) - // neither macos nor linux have zstd by default - '.zst', // --zstd - '.7z', // --lz4, --lzma, --lzop - ]; - - // these don't provide any clues as to which OS is used - // (counter-examples: .exe (windows) .dmg (mac) .deb (linux) - Triplet.TERMS_EXTS_NON_INFORMATIVE = [ - '.gz', - '.xz', - '.zst', - '.zip', - // .tar should come *after* extensions that use it - '.tar', - '.7z', - ]; - Triplet.TERMS_EXTS_SOURCE = ['.zip', '.tar.gz', '.tar.xz', '.tar.zst']; - - Triplet.TERMS_PRIMARY_MAP = {}; - let T = { - NONE: {}, - - // Apple - APPLE: { os: 'darwin', vendor: 'apple', libc: 'none' }, - APPLE_X86_64: { - os: 'darwin', - vendor: 'apple', - arch: 'x86_64', - libc: 'none', - }, - APPLE_X86: { - os: 'darwin', - vendor: 'apple', - arch: 'x86', - libc: 'none', - }, - APPLE_AARCH64: { - os: 'darwin', - vendor: 'apple', - arch: 'aarch64', - }, - - // Linux - LINUX: { os: 'linux', vendor: 'unknown' }, - LINUX_X86_64: { os: 'linux', arch: 'x86_64', vendor: 'unknown' }, - LINUX_X86: { os: 'linux', arch: 'x86', vendor: 'unknown' }, - LINUX_ARMHF_GNU: { - arch: 'armhf', - os: 'linux', - libc: 'gnu', - arches: ['armv7', 'armhf'], - }, - LINUX_ARMHF_MUSL: { - arch: 'armhf', - os: 'linux', - libc: 'none', - libcs: ['none', 'musl'], - }, - LINUX_ARMEL_MUSL: { - arch: 'armel', - os: 'linux', - libc: 'none', - libcs: ['none', 'musl'], - }, - - // Windows 10+ - WIN_PC: { os: 'windows', vendor: 'pc' }, - WIN_PC_X86: { os: 'windows', vendor: 'pc', arch: 'x86' }, - WIN_PC_X86_64: { os: 'windows', vendor: 'pc', arch: 'x86_64' }, - WIN_PC_MSVC: { - os: 'windows', - vendor: 'pc', - libc: 'msvc', - }, - - // BSDs - AIX: { os: 'aix' }, - DRAGONFLY: { os: 'dragonfly' }, - FREEBSD: { os: 'freebsd' }, - OPENBSD: { os: 'openbsd' }, - NETBSD: { os: 'netbsd' }, - PLAN9: { os: 'plan9' }, - ILLUMOS: { os: 'illumos' }, - SUNOS: { os: 'sunos' }, - SOLARIS: { os: 'solaris' }, - - // Any - POSIX_2017: { os: 'posix_2017', arch: 'ANYARCH', vendor: 'unknown' }, - POSIX_2024: { os: 'posix_2024', arch: 'ANYARCH', vendor: 'unknown' }, - WASI: { os: 'wasi', vendor: 'unknown' }, - - // Arches - X86_64: { arch: 'x86_64' }, - X86_64_V2: { arch: 'x86_64_v2' }, - X86_64_V3: { arch: 'x86_64_v3' }, - X86_64_V4: { arch: 'x86_64_v4' }, - AMD64_ROCM: { arch: 'x86_64_rocm' }, - AARCH64: { arch: 'aarch64' }, - X86: { arch: 'x86' }, - ARMV7A: { arch: 'armv7a' }, - ARMV7: { arch: 'armv7' }, - ARMHF: { arch: 'armhf' }, - ARMV6: { arch: 'armv6' }, - ARMEL: { arch: 'armel' }, - - // LIBC - ALPINE: { - os: 'linux', - vendor: 'unknown', - libc: 'none', - libcs: ['none', 'musl'], - }, - MUSL: { libc: 'none', libcs: ['none', 'musl'] }, - LIBC_NONE: { libc: 'none' }, - }; - - let tpm = Triplet.TERMS_PRIMARY_MAP; - // meta replacements - tpm['{NAME}'] = T.NONE; - tpm['{OS}'] = T.NONE; - tpm['{ARCH}'] = T.NONE; - tpm['{LIBC}'] = T.NONE; - tpm['{VENDOR}'] = T.NONE; - tpm['{EXT}'] = T.NONE; - tpm['ANYARCH'] = T.NONE; - tpm['ANYOS'] = T.NONE; - - // just channels - tpm['stable'] = { channel: 'stable' }; - tpm['preview'] = { channel: 'preview' }; - tpm['lts'] = { channel: 'lts' }; - tpm['beta'] = { channel: 'beta' }; - tpm['dev'] = { channel: 'dev' }; - tpm['debug'] = { channel: 'debug' }; - - // - // OS - // - tpm['apple'] = T.APPLE; - tpm['darwin'] = T.APPLE; - tpm['darwin_10_12'] = T.APPLE; - tpm['macos'] = T.APPLE; - tpm['osx'] = T.APPLE; - tpm['osx_10_6'] = T.APPLE; - tpm['osx_10_8'] = T.APPLE; - tpm['mac'] = T.APPLE; - tpm['macos_10_10'] = T.APPLE; +let Triplet = {}; + +Triplet.TERMS_CHANNEL = ['master', 'nightly']; +Triplet.TERMS_CHECKSUM = [ + 'B3SUMS', + 'checksum', + 'MD5SUMS', + 'SHA1SUMS', + 'SHA256SUMS', + 'SHA512SUMS', +]; +Triplet.TERMS_NON_BUILD = [ + 'bootstrap', + 'debug', // TODO dashd + 'setup', + 'source', + 'src', + 'symbols', + // a build, but not one you'd use given the alternative + 'unsigned', // TODO dashd + 'vendor', // TODO rclone go vendor +]; +Triplet._RE_TERMS_NON_BUILD = []; +for (let term of Triplet.TERMS_NON_BUILD) { + let re = new RegExp(`(\\b|_)(${term})(\\b|_)`); + Triplet._RE_TERMS_NON_BUILD.push(re); +} +Triplet.TERMS_EXTS_NON_BUILD = [ + '.1', + '.asc', + '.b3', + '.json', + '.md5', + '.pem', + '.sbom', + '.sha256', + '.sha256sum', + '.sha512', + '.sig', + '.txt', + // no android + '.apk', + // we can't use these yet + '.deb', + '.rpm', + // windows something... not sure + '.msixbundle', +]; + +// many assets are bare - we'd have to check if any contain a '.' +// in the name before using these as a whitelist +// (ordered by OS for clarity, by length for matching) +Triplet.TERMS_EXTS_PKG = [ // Windows - tpm['windows'] = T.WIN_PC; + // '.exe.zip', + // '.exe.xz', + '.exe', + '.msi', + '.msixbundle', + // macOS + // '.app.zip', + //'.dmg.zip', // because some systems only allow .zip + '.app', // for classification (it'll be in a zip or dmg) + '.dmg', + '.pkg', + // Enterprise Linux + '.rpm', + // Debian + '.deb', + // Android + '.apk', + // Nondescript + '.tar', + // '.tar.bz2', // ('.tbz2' is never used) + // '.tar.gz', // ('.tgz' is never used) + // '.tar.xz', // ('.txz' is never used) + // '.tar.zst', // ('.tzst' is never used) + // POSIX + // (also, could be the runnable, installable thing, or an install script) + '.sh', + // Any (font, vim script) + '.git', +]; + +Triplet.TERMS_EXTS_ZIP = [ + '.bz2', // -j, --bzip2 (for completeness, not used) + '.gz', // -z, --gzip + '.xz', // -J, --xz + '.zip', // (auto on BSD tar on Windows and macOS) + // neither macos nor linux have zstd by default + '.zst', // --zstd + '.7z', // --lz4, --lzma, --lzop +]; + +// these don't provide any clues as to which OS is used +// (counter-examples: .exe (windows) .dmg (mac) .deb (linux) +Triplet.TERMS_EXTS_NON_INFORMATIVE = [ + '.gz', + '.xz', + '.zst', + '.zip', + // .tar should come *after* extensions that use it + '.tar', + '.7z', +]; +Triplet.TERMS_EXTS_SOURCE = ['.zip', '.tar.gz', '.tar.xz', '.tar.zst']; + +/** @type {Object.} */ +Triplet.TERMS_PRIMARY_MAP = {}; +let T = { + NONE: {}, + + // Apple + APPLE: { os: 'darwin', vendor: 'apple', libc: 'none' }, + APPLE_X86_64: { + os: 'darwin', + vendor: 'apple', + arch: 'x86_64', + libc: 'none', + }, + APPLE_X86: { + os: 'darwin', + vendor: 'apple', + arch: 'x86', + libc: 'none', + }, + APPLE_AARCH64: { + os: 'darwin', + vendor: 'apple', + arch: 'aarch64', + }, + // Linux - tpm['linux'] = T.LINUX; - // BSD + other Unixes - tpm['aix'] = T.AIX; - tpm['dragonfly'] = T.DRAGONFLY; - tpm['freebsd'] = T.FREEBSD; - tpm['freebsd_12'] = T.FREEBSD; - tpm['openbsd'] = T.OPENBSD; - tpm['netbsd'] = T.NETBSD; - tpm['plan9'] = T.PLAN9; - tpm['illumos'] = T.ILLUMOS; - tpm['sunos'] = T.SUNOS; - tpm['solaris'] = T.SOLARIS; - tpm['solaris_11'] = T.SOLARIS; - // System Interfaces (POSIX, WASI) - tpm['posix'] = T.POSIX_2017; - tpm['posix_2017'] = T.POSIX_2017; - tpm['posix_2024'] = T.POSIX_2024; - tpm['wasi'] = T.WASI; - - // OS + Arch - tpm['windowsx86'] = T.WIN_PC_X86; - tpm['linux64'] = T.LINUX_X86_64; - tpm['linux32'] = T.LINUX_X86; - tpm['win64'] = T.WIN_PC_X86_64; - tpm['osx64'] = T.APPLE_X86_64; - tpm['mac32'] = T.APPLE_X86; - // ambiguous - tpm['win32'] = T.WIN_PC; - tpm['mac64'] = T.APPLE; - - // - // Arch - // - tpm['x86_64'] = T.X86_64; - tpm['x86_64_v1'] = T.X86_64; - tpm['x86_64_v2'] = T.X86_64_V2; - tpm['x86_64_v3'] = T.X86_64_V3; - tpm['x86_64_v4'] = T.X86_64_V4; - tpm['amd64'] = T.X86_64; - tpm['amd64_v1'] = T.X86_64; - tpm['amd64_v2'] = T.X86_64_V2; - tpm['amd64_v3'] = T.X86_64_V3; - tpm['amd64_v4'] = T.X86_64_V4; - tpm['amd64_rocm'] = T.AMD64_ROCM; - tpm['x64'] = T.X86_64; - tpm['aarch64'] = T.AARCH64; - tpm['arm64'] = T.AARCH64; - tpm['m1'] = T.AARCH64; - // How to navigate the minefield of armv[567](e|l|a|hf|kz) - // See - // - "hf" seems to standard now, but carried over from the v5/v6 days - // - "kz" is a special architecture for security - // - "e" / "el" seems to be old v5 stuff - // - "a" seems to be the best of the v7 era - // - "l" ??? seems to be standard - // We could have some crazy fallback logic but... aarch64 is the future! - tpm['armv7a'] = T.ARMV7A; - tpm['armv7l'] = T.ARMV7; // Most Linuxes - tpm['armv7h'] = T.ARMV7; // Arch - tpm['armv7hl'] = T.ARMV7; // RedHat - tpm['armv7'] = T.ARMV7; - tpm['arm32'] = T.ARMV7; - tpm['armhf'] = T.ARMHF; - // armv6hf will always work on armv7, or armv6hf, but not armv6 or armv5 - // See also: - tpm['armv6hf'] = T.ARMHF; - tpm['armv6l'] = T.ARMV6; - tpm['armv6'] = T.ARMV6; - tpm['armv5'] = T.ARMEL; - tpm['armel'] = T.ARMEL; - - // Enter the minefield of x86 - tpm['i386'] = T.X86; - tpm['386'] = T.X86; - tpm['i686'] = T.X86; - tpm['686'] = T.X86; - tpm['x86'] = T.X86; - tpm['ia32'] = T.X86; - // the weird ones - tpm['loong64'] = { arch: 'loong64' }; - tpm['mips'] = { arch: 'mips' }; - tpm['mipsel'] = { arch: 'mipsel' }; - tpm['mipsle'] = { arch: 'mipsel' }; - tpm['mips64'] = { arch: 'mips64' }; - tpm['mips64el'] = { arch: 'mips64el' }; - tpm['mips64le'] = { arch: 'mips64el' }; - tpm['mipsr6'] = { arch: 'mipsr6' }; - tpm['mipsr6el'] = { arch: 'mipsr6el' }; - tpm['mips64r6'] = { arch: 'mips64r6' }; - tpm['mips64r6el'] = { arch: 'mips64r6el' }; - tpm['mips64r6le'] = { arch: 'mips64r6el' }; - tpm['powerpc'] = { arch: 'ppc' }; - tpm['powerpc64'] = { arch: 'ppc64' }; - tpm['powerpc64el'] = { arch: 'ppc64le' }; - tpm['powerpc64le'] = { arch: 'ppc64le' }; - tpm['ppc'] = { arch: 'ppc' }; - tpm['ppc64'] = { arch: 'ppc64' }; - tpm['ppc64el'] = { arch: 'ppc64le' }; - tpm['ppc64le'] = { arch: 'ppc64le' }; - tpm['riscv64'] = { arch: 'riscv64' }; - tpm['s390x'] = { arch: 's390x' }; - // WASM - tpm['wasm'] = { arch: 'wasm32' }; - tpm['wasm32'] = { arch: 'wasm32' }; - - // - // mostly libc - // - tpm['static'] = T.LIBC_NONE; - tpm['bionic'] = T.NONE; - // TODO how to determine when "musl" is NOT static (i.e. musl++) - tpm['alpine'] = T.ALPINE; - tpm['musl'] = T.MUSL; - tpm['none'] = T.NONE; - - // saved for last due to ambiguity - tpm['universal'] = T.APPLE_X86_64; - tpm['gnueabihf'] = T.LINUX_ARMHF_GNU; - tpm['musleabihf'] = T.MUSL; - tpm['musleabi'] = T.MUSL; - tpm['eabihf'] = { arch: 'armhf', os: 'linux' }; - tpm['gnu'] = {}; - tpm['win'] = T.WIN_PC; - tpm['msvc'] = T.WIN_PC_MSVC; - tpm['32'] = T.X86; - tpm['64'] = T.X86_64; - tpm['all'] = T.APPLE_X86_64; - tpm['m1'] = T.APPLE_AARCH64; - tpm['unknown'] = { vendor: 'unknown' }; - tpm['pc'] = { vendor: 'pc' }; - tpm['android'] = { + LINUX: { os: 'linux', vendor: 'unknown' }, + LINUX_X86_64: { os: 'linux', arch: 'x86_64', vendor: 'unknown' }, + LINUX_X86: { os: 'linux', arch: 'x86', vendor: 'unknown' }, + LINUX_ARMHF_GNU: { + arch: 'armhf', os: 'linux', - android: true, - libc: 'bionic', - }; - tpm['androideabi'] = { + libc: 'gnu', + arches: ['armv7', 'armhf'], + }, + LINUX_ARMHF_MUSL: { + arch: 'armhf', os: 'linux', - android: true, - arch: 'armv7', - libc: 'bionic', - }; - - // Extensions - last resort - tpm['exe'] = T.WIN_PC; - tpm['msi'] = T.WIN_PC; - tpm['app'] = T.APPLE; - tpm['dmg'] = T.NONE; - tpm['pkg'] = T.APPLE; - tpm['git'] = { os: 'ANYOS', arch: 'ANYARCH' }; - //{ term: 'sh', os: '*', arch: 'POSIX' }, - tpm['sh'] = T.NONE; - // for completeness - tpm['arm'] = T.NONE; - tpm['js'] = T.NONE; - - Triplet.TERMS_TIERED_MAPS = [ - { - // these are sometimes ambiguous (i.e. "win32-x86", "mac64-arm64"), - // so their arch identifiers should only be used as a last resort - - // win - windows: T.WIN_PC_X86_64, - win32: T.WIN_PC_X86, - exe: T.WIN_PC_X86_64, - msi: T.WIN_PC_X86_64, - - // mac - macos: T.APPLE_X86_64, - mac64: T.APPLE_X86_64, - darwin: T.APPLE_X86_64, - app: T.APPLE_X86_64, - dmg: T.APPLE_X86_64, - pkg: T.APPLE_X86_64, - - // linux - linux: T.LINUX_X86_64, - android: T.AARCH64, - - // arm - arm: T.ARMHF, - - // libc - gnu: { os: 'windows', libc: 'none', vendor: 'pc' }, - musleabihf: T.LINUX_ARMHF_MUSL, - musleabi: T.LINUX_ARMEL_MUSL, - - // system interfaces - js: { os: 'wasi', arch: 'wasm32', vendor: 'unknown' }, - }, - ]; - - Triplet.maybeInstallable = function (proj, build) { - for (let sumname of Triplet.TERMS_CHECKSUM) { - if (build.download.includes(sumname)) { - return false; - } + libc: 'none', + libcs: ['none', 'musl'], + }, + LINUX_ARMEL_MUSL: { + arch: 'armel', + os: 'linux', + libc: 'none', + libcs: ['none', 'musl'], + }, + + // Windows 10+ + WIN_PC: { os: 'windows', vendor: 'pc' }, + WIN_PC_X86: { os: 'windows', vendor: 'pc', arch: 'x86' }, + WIN_PC_X86_64: { os: 'windows', vendor: 'pc', arch: 'x86_64' }, + WIN_PC_MSVC: { + os: 'windows', + vendor: 'pc', + libc: 'msvc', + }, + + // BSDs + AIX: { os: 'aix' }, + DRAGONFLY: { os: 'dragonfly' }, + FREEBSD: { os: 'freebsd' }, + OPENBSD: { os: 'openbsd' }, + NETBSD: { os: 'netbsd' }, + PLAN9: { os: 'plan9' }, + ILLUMOS: { os: 'illumos' }, + SUNOS: { os: 'sunos' }, + SOLARIS: { os: 'solaris' }, + + // Any + POSIX_2017: { os: 'posix_2017', arch: 'ANYARCH', vendor: 'unknown' }, + POSIX_2024: { os: 'posix_2024', arch: 'ANYARCH', vendor: 'unknown' }, + WASI: { os: 'wasi', vendor: 'unknown' }, + + // Arches + X86_64: { arch: 'x86_64' }, + X86_64_V2: { arch: 'x86_64_v2' }, + X86_64_V3: { arch: 'x86_64_v3' }, + X86_64_V4: { arch: 'x86_64_v4' }, + AMD64_ROCM: { arch: 'x86_64_rocm' }, + AARCH64: { arch: 'aarch64' }, + X86: { arch: 'x86' }, + ARMV7A: { arch: 'armv7a' }, + ARMV7: { arch: 'armv7' }, + ARMHF: { arch: 'armhf' }, + ARMV6: { arch: 'armv6' }, + ARMEL: { arch: 'armel' }, + + // LIBC + ALPINE: { + os: 'linux', + vendor: 'unknown', + libc: 'none', + libcs: ['none', 'musl'], + }, + MUSL: { libc: 'none', libcs: ['none', 'musl'] }, + LIBC_NONE: { libc: 'none' }, +}; + +let tpm = Triplet.TERMS_PRIMARY_MAP; +// meta replacements +tpm['{NAME}'] = T.NONE; +tpm['{OS}'] = T.NONE; +tpm['{ARCH}'] = T.NONE; +tpm['{LIBC}'] = T.NONE; +tpm['{VENDOR}'] = T.NONE; +tpm['{EXT}'] = T.NONE; +tpm['ANYARCH'] = T.NONE; +tpm['ANYOS'] = T.NONE; + +// just channels +tpm['stable'] = { channel: 'stable' }; +tpm['preview'] = { channel: 'preview' }; +tpm['lts'] = { channel: 'lts' }; +tpm['beta'] = { channel: 'beta' }; +tpm['dev'] = { channel: 'dev' }; +tpm['debug'] = { channel: 'debug' }; + +// +// OS +// +tpm['apple'] = T.APPLE; +tpm['darwin'] = T.APPLE; +tpm['darwin_10_12'] = T.APPLE; +tpm['macos'] = T.APPLE; +tpm['osx'] = T.APPLE; +tpm['osx_10_6'] = T.APPLE; +tpm['osx_10_8'] = T.APPLE; +tpm['mac'] = T.APPLE; +tpm['macos_10_10'] = T.APPLE; +// Windows +tpm['windows'] = T.WIN_PC; +// Linux +tpm['linux'] = T.LINUX; +// BSD + other Unixes +tpm['aix'] = T.AIX; +tpm['dragonfly'] = T.DRAGONFLY; +tpm['freebsd'] = T.FREEBSD; +tpm['freebsd_12'] = T.FREEBSD; +tpm['openbsd'] = T.OPENBSD; +tpm['netbsd'] = T.NETBSD; +tpm['plan9'] = T.PLAN9; +tpm['illumos'] = T.ILLUMOS; +tpm['sunos'] = T.SUNOS; +tpm['solaris'] = T.SOLARIS; +tpm['solaris_11'] = T.SOLARIS; +// System Interfaces (POSIX, WASI) +tpm['posix'] = T.POSIX_2017; +tpm['posix_2017'] = T.POSIX_2017; +tpm['posix_2024'] = T.POSIX_2024; +tpm['wasi'] = T.WASI; + +// OS + Arch +tpm['windowsx86'] = T.WIN_PC_X86; +tpm['linux64'] = T.LINUX_X86_64; +tpm['linux32'] = T.LINUX_X86; +tpm['win64'] = T.WIN_PC_X86_64; +tpm['osx64'] = T.APPLE_X86_64; +tpm['mac32'] = T.APPLE_X86; +// ambiguous +tpm['win32'] = T.WIN_PC; +tpm['mac64'] = T.APPLE; + +// +// Arch +// +tpm['x86_64'] = T.X86_64; +tpm['x86_64_v1'] = T.X86_64; +tpm['x86_64_v2'] = T.X86_64_V2; +tpm['x86_64_v3'] = T.X86_64_V3; +tpm['x86_64_v4'] = T.X86_64_V4; +tpm['amd64'] = T.X86_64; +tpm['amd64_v1'] = T.X86_64; +tpm['amd64_v2'] = T.X86_64_V2; +tpm['amd64_v3'] = T.X86_64_V3; +tpm['amd64_v4'] = T.X86_64_V4; +tpm['amd64_rocm'] = T.AMD64_ROCM; +tpm['x64'] = T.X86_64; +tpm['aarch64'] = T.AARCH64; +tpm['arm64'] = T.AARCH64; +tpm['m1'] = T.AARCH64; +// How to navigate the minefield of armv[567](e|l|a|hf|kz) +// See +// - "hf" seems to standard now, but carried over from the v5/v6 days +// - "kz" is a special architecture for security +// - "e" / "el" seems to be old v5 stuff +// - "a" seems to be the best of the v7 era +// - "l" ??? seems to be standard +// We could have some crazy fallback logic but... aarch64 is the future! +tpm['armv7a'] = T.ARMV7A; +tpm['armv7l'] = T.ARMV7; // Most Linuxes +tpm['armv7h'] = T.ARMV7; // Arch +tpm['armv7hl'] = T.ARMV7; // RedHat +tpm['armv7'] = T.ARMV7; +tpm['arm32'] = T.ARMV7; +tpm['armhf'] = T.ARMHF; +// armv6hf will always work on armv7, or armv6hf, but not armv6 or armv5 +// See also: +tpm['armv6hf'] = T.ARMHF; +tpm['armv6l'] = T.ARMV6; +tpm['armv6'] = T.ARMV6; +tpm['armv5'] = T.ARMEL; +tpm['armel'] = T.ARMEL; + +// Enter the minefield of x86 +tpm['i386'] = T.X86; +tpm['386'] = T.X86; +tpm['i686'] = T.X86; +tpm['686'] = T.X86; +tpm['x86'] = T.X86; +tpm['ia32'] = T.X86; +// the weird ones +tpm['loong64'] = { arch: 'loong64' }; +tpm['mips'] = { arch: 'mips' }; +tpm['mipsel'] = { arch: 'mipsel' }; +tpm['mipsle'] = { arch: 'mipsel' }; +tpm['mips64'] = { arch: 'mips64' }; +tpm['mips64el'] = { arch: 'mips64el' }; +tpm['mips64le'] = { arch: 'mips64el' }; +tpm['mipsr6'] = { arch: 'mipsr6' }; +tpm['mipsr6el'] = { arch: 'mipsr6el' }; +tpm['mips64r6'] = { arch: 'mips64r6' }; +tpm['mips64r6el'] = { arch: 'mips64r6el' }; +tpm['mips64r6le'] = { arch: 'mips64r6el' }; +tpm['powerpc'] = { arch: 'ppc' }; +tpm['powerpc64'] = { arch: 'ppc64' }; +tpm['powerpc64el'] = { arch: 'ppc64le' }; +tpm['powerpc64le'] = { arch: 'ppc64le' }; +tpm['ppc'] = { arch: 'ppc' }; +tpm['ppc64'] = { arch: 'ppc64' }; +tpm['ppc64el'] = { arch: 'ppc64le' }; +tpm['ppc64le'] = { arch: 'ppc64le' }; +tpm['riscv64'] = { arch: 'riscv64' }; +tpm['s390x'] = { arch: 's390x' }; +// WASM +tpm['wasm'] = { arch: 'wasm32' }; +tpm['wasm32'] = { arch: 'wasm32' }; + +// +// mostly libc +// +tpm['static'] = T.LIBC_NONE; +tpm['bionic'] = T.NONE; +// TODO how to determine when "musl" is NOT static (i.e. musl++) +tpm['alpine'] = T.ALPINE; +tpm['musl'] = T.MUSL; +tpm['none'] = T.NONE; + +// saved for last due to ambiguity +tpm['universal'] = T.APPLE_X86_64; +tpm['gnueabihf'] = T.LINUX_ARMHF_GNU; +tpm['musleabihf'] = T.MUSL; +tpm['musleabi'] = T.MUSL; +tpm['eabihf'] = { arch: 'armhf', os: 'linux' }; +tpm['gnu'] = {}; +tpm['win'] = T.WIN_PC; +tpm['msvc'] = T.WIN_PC_MSVC; +tpm['32'] = T.X86; +tpm['64'] = T.X86_64; +tpm['all'] = T.APPLE_X86_64; +tpm['m1'] = T.APPLE_AARCH64; +tpm['unknown'] = { vendor: 'unknown' }; +tpm['pc'] = { vendor: 'pc' }; +tpm['android'] = { + os: 'linux', + android: true, + libc: 'bionic', +}; +tpm['androideabi'] = { + os: 'linux', + android: true, + arch: 'armv7', + libc: 'bionic', +}; + +// Extensions - last resort +tpm['exe'] = T.WIN_PC; +tpm['msi'] = T.WIN_PC; +tpm['app'] = T.APPLE; +tpm['dmg'] = T.NONE; +tpm['pkg'] = T.APPLE; +tpm['git'] = { os: 'ANYOS', arch: 'ANYARCH' }; +//{ term: 'sh', os: '*', arch: 'POSIX' }, +tpm['sh'] = T.NONE; +// for completeness +tpm['arm'] = T.NONE; +tpm['js'] = T.NONE; + +Triplet.TERMS_TIERED_MAPS = [ + { + // these are sometimes ambiguous (i.e. "win32-x86", "mac64-arm64"), + // so their arch identifiers should only be used as a last resort + + // win + windows: T.WIN_PC_X86_64, + win32: T.WIN_PC_X86, + exe: T.WIN_PC_X86_64, + msi: T.WIN_PC_X86_64, + + // mac + macos: T.APPLE_X86_64, + mac64: T.APPLE_X86_64, + darwin: T.APPLE_X86_64, + app: T.APPLE_X86_64, + dmg: T.APPLE_X86_64, + pkg: T.APPLE_X86_64, + + // linux + linux: T.LINUX_X86_64, + android: T.AARCH64, + + // arm + arm: T.ARMHF, + + // libc + gnu: { os: 'windows', libc: 'none', vendor: 'pc' }, + musleabihf: T.LINUX_ARMHF_MUSL, + musleabi: T.LINUX_ARMEL_MUSL, + + // system interfaces + js: { os: 'wasi', arch: 'wasm32', vendor: 'unknown' }, + }, +]; + +/** + * @typedef ProjectPartial + * @prop {String} name + * @prop {Array} [_names] + */ + +/** + * @typedef KnownMatch + * @prop {Boolean} _deleteme + */ + +/** + * @typedef BuildAssetPartial + * @prop {String} name + * @prop {String} version + * @prop {String} [_version] + * @prop {String} [_filename] + * @prop {String} os + * @prop {String} arch + * @prop {String} vendor + * @prop {String} libc + * @prop {String} download + */ + +/** + * @param {ProjectPartial} proj + * @param {BuildAssetPartial} build + */ +Triplet.maybeInstallable = function (proj, build) { + for (let sumname of Triplet.TERMS_CHECKSUM) { + if (build.download.includes(sumname)) { + return false; } + } - for (let ext of Triplet.TERMS_EXTS_NON_BUILD) { - if (build.download.endsWith(ext)) { - return false; - } + for (let ext of Triplet.TERMS_EXTS_NON_BUILD) { + if (build.download.endsWith(ext)) { + return false; } + } - for (let re of Triplet._RE_TERMS_NON_BUILD) { - if (re.test(build.download)) { - return false; - } + for (let re of Triplet._RE_TERMS_NON_BUILD) { + if (re.test(build.download)) { + return false; } + } - // don't count tip commits or debug channels as versions - // (though a ${latest}-${date} could possibly be used as a substitute) - let channels = Triplet.TERMS_CHANNEL; - for (let ch of channels) { - if (build.version === ch) { - return false; - } + // don't count tip commits or debug channels as versions + // (though a ${latest}-${date} could possibly be used as a substitute) + let channels = Triplet.TERMS_CHANNEL; + for (let ch of channels) { + if (build.version === ch) { + return false; } + } - // exclude source-only files - let projNames = proj._names; - if (!projNames) { - projNames = [proj.name]; + // exclude source-only files + let projNames = proj._names; + if (!projNames) { + projNames = [proj.name]; + } + for (let projName of projNames) { + if (!build.name) { + break; } - for (let projName of projNames) { - if (!build.name) { - break; - } - let filename = build.name; - let version = build._version || build.version; - - let prefix = `${projName}-${version}.`; - let match = filename.startsWith(prefix); - if (match) { - prefix = prefix.slice(0, -1); - for (let ext of Triplet.TERMS_EXTS_SOURCE) { - let srcname = `${prefix}${ext}`; - if (filename === srcname) { - //console.log('skip source', build.download); - // TODO enum code - return 0; - } + let filename = build.name; + let version = build._version || build.version; + + let prefix = `${projName}-${version}.`; + let match = filename.startsWith(prefix); + if (match) { + prefix = prefix.slice(0, -1); + for (let ext of Triplet.TERMS_EXTS_SOURCE) { + let srcname = `${prefix}${ext}`; + if (filename === srcname) { + //console.log('skip source', build.download); + // TODO enum code + return 0; } } } + } - // not that we can say for sure this is a good file, - // but we can't say it's a bad one - return true; - }; + // not that we can say for sure this is a good file, + // but we can't say it's a bad one + return true; +}; - Triplet.toPattern = function (proj, build) { - let { download, _filename, version, _version } = build; - let projNames = proj._names; +/** + * @param {ProjectPartial} proj + * @param {BuildAssetPartial} build + */ +Triplet.toPattern = function (proj, build) { + let { download, _filename, version, _version } = build; + let projNames = proj._names; - // for when the download URL is a uuid, for example - if (_filename) { - download = _filename; - } + // for when the download URL is a uuid, for example + if (_filename) { + download = _filename; + } - // use the raw, non-semver version for file names - if (_version) { - version = _version; - } - if (!projNames) { - projNames = [proj.name]; + // use the raw, non-semver version for file names + if (_version) { + version = _version; + } + if (!projNames) { + projNames = [proj.name]; + } + + // for watchexec tags like cli-v1.20.3 + version = version.replace(/^cli-/, ''); + version = version.replace(/^v?/i, 'v?'); + let verEsc = version.replace(/\./g, '\\.').replace(/\+/g, '\\+'); + // maybe just once before and once after + + // generic sources that benefit most from dynamic matching + { + let ghReleaseRe = + /https:..github.com.[^\/]+.[^\/]+.releases.download.[^\/]+.(.*)/; + let ghrMatches = download.match(ghReleaseRe); + if (ghrMatches) { + download = ghrMatches[1]; } - // for watchexec tags like cli-v1.20.3 - version = version.replace(/^cli-/, ''); - version = version.replace(/^v?/i, 'v?'); - let verEsc = version.replace(/\./g, '\\.').replace(/\+/g, '\\+'); - // maybe just once before and once after - - // generic sources that benefit most from dynamic matching - { - let ghReleaseRe = - /https:..github.com.[^\/]+.[^\/]+.releases.download.[^\/]+.(.*)/; - let ghrMatches = download.match(ghReleaseRe); - if (ghrMatches) { - download = ghrMatches[1]; - } + let sfRe = + /https:..sourceforge.net.projects.([^\/]+).files.([^\/]+).download/; + let sfMatches = download.match(sfRe); + if (sfMatches) { + download = `${sfMatches[1]}_${sfMatches[2]}`; + } + } - let sfRe = - /https:..sourceforge.net.projects.([^\/]+).files.([^\/]+).download/; - let sfMatches = download.match(sfRe); - if (sfMatches) { - download = `${sfMatches[1]}_${sfMatches[2]}`; - } + // sources that _don't_ benefit from matching + { + // ex: https://codeload.github.com/BeyondCodeBootcamp/DuckDNS.sh/legacy.zip/refs/tags/v1.0.2 + let ghSourceRe = + /https:..codeload.github.com\/([^\/]+)\/([^\/]+)\/([^\/]+)\/refs\/tags\/([^\/]+)/; + let ghsMatches = download.match(ghSourceRe); + if (ghsMatches) { + download = `${ghsMatches[2]}/${ghsMatches[4]}/${ghsMatches[3]}`; } - // sources that _don't_ benefit from matching - { - // ex: https://codeload.github.com/BeyondCodeBootcamp/DuckDNS.sh/legacy.zip/refs/tags/v1.0.2 - let ghSourceRe = - /https:..codeload.github.com\/([^\/]+)\/([^\/]+)\/([^\/]+)\/refs\/tags\/([^\/]+)/; - let ghsMatches = download.match(ghSourceRe); - if (ghsMatches) { - download = `${ghsMatches[2]}/${ghsMatches[4]}/${ghsMatches[3]}`; - } + let gitSourceRe = /(https|http|git):.*\/(.*).git$/; + download = download.replace(gitSourceRe, '$2.git'); + } - let gitSourceRe = /(https|http|git):.*\/(.*).git$/; - download = download.replace(gitSourceRe, '$2.git'); - } + // trim the start of any url https://example.com/(whatever/thing)?query= + download = download.replace(/https:\/\/[^\/]+\/([^\?]+)\??.*/, '$1'); + + download = download.replace( + new RegExp(`(\\b|\\D)v?${verEsc}`, 'ig'), + '$1{VER}', + ); + for (let projName of projNames) { + let nameEsc = projName.replace(/[\._\-\+]/g, '.'); + let nameRe = new RegExp(`(\\b|_)${nameEsc}(\\b|_|\\d|[A-Z])`, 'gi'); + download = download.replace(nameRe, '{NAME}$2'); + } - // trim the start of any url https://example.com/(whatever/thing)?query= - download = download.replace(/https:\/\/[^\/]+\/([^\?]+)\??.*/, '$1'); + // trim URLs up to the first {FOO} + // note (the closing \} is a benign fix for code editor matching / highlighting) + download = download.replace(/^[^\{\}]+\//, ''); - download = download.replace( - new RegExp(`(\\b|\\D)v?${verEsc}`, 'ig'), - '$1{VER}', - ); - for (let projName of projNames) { - let nameEsc = projName.replace(/[\._\-\+]/g, '.'); - let nameRe = new RegExp(`(\\b|_)${nameEsc}(\\b|_|\\d|[A-Z])`, 'gi'); - download = download.replace(nameRe, '{NAME}$2'); + for (let ext of Triplet.TERMS_EXTS_NON_INFORMATIVE) { + let hasNonInformativeExt = download.endsWith(ext); + if (hasNonInformativeExt) { + download = download.slice(0, -ext.length); } + } - // trim URLs up to the first {FOO} - // note (the closing \} is a benign fix for code editor matching / highlighting) - download = download.replace(/^[^\{\}]+\//, ''); + return download; +}; - for (let ext of Triplet.TERMS_EXTS_NON_INFORMATIVE) { - let hasNonInformativeExt = download.endsWith(ext); - if (hasNonInformativeExt) { - download = download.slice(0, -ext.length); - } - } +// This normalizes the triplet permutation: +// - lowercase all-the-things +// - hyphens (.) to separate terms +// - underscores (_) to join within a term +// - eliminate placeholders {VER} and {NAME} +let _DOUBLE_NAME_RE = /{NAME}.*{NAME}/; - return download; - }; - - // This normalizes the triplet permutation: - // - lowercase all-the-things - // - hyphens (.) to separate terms - // - underscores (_) to join within a term - // - eliminate placeholders {VER} and {NAME} - let _DOUBLE_NAME_RE = /{NAME}.*{NAME}/; - - Triplet.patternToTerms = function (filename) { - // NIX {VER} and multiple occurences of {NAME} - // {NAME}/{NAME}-{VER}-Windows-x86_64_v2-musl.exe => - // {NAME}-Windows-x86-64-v2-musl-exe - filename = filename.replace(/{VER}/g, ''); - for (;;) { - let hasMany = _DOUBLE_NAME_RE.test(filename); - if (!hasMany) { - break; - } - filename = filename.replace('{NAME}', '.'); +/** + * @param {String} filename + */ +Triplet.patternToTerms = function (filename) { + // NIX {VER} and multiple occurences of {NAME} + // {NAME}/{NAME}-{VER}-Windows-x86_64_v2-musl.exe => + // {NAME}-Windows-x86-64-v2-musl-exe + filename = filename.replace(/{VER}/g, ''); + for (;;) { + let hasMany = _DOUBLE_NAME_RE.test(filename); + if (!hasMany) { + break; } - filename = filename.replace(/{VER}/g, '.'); - filename = filename.replace('{NAME}', '.{NAME}.'); - - // Lossy approach (better for long term future) - // Before we replace - and _ with . - // freebsd12 => freebsd - // solaris11-64 => solaris-64 - // darwin-10.15 => darwin - // macos10 => macos - //filename = filename.replace(/(\w|-)?1\d(\.\d+)?/g, '$1'); - - // Lossless two-step approach (better for exact matches) - // freebsd12 => freebsd^12 - // solaris11-64 => solaris^11-64 - // darwin-10.15 => darwin^10^15 - // macos10 => macos^10 - // darwin-10.15 => darwin^10^15 - filename = filename.replace(/(macos|darwin|osx)-?(1\d)\.(\d+)/, '$1^$2^$3'); - filename = filename.replace( - /(macos|darwin|osx|freebsd|solaris)-?(1\d)/, - '$1^$2', - ); + filename = filename.replace('{NAME}', '.'); + } + filename = filename.replace(/{VER}/g, '.'); + filename = filename.replace('{NAME}', '.{NAME}.'); + + // Lossy approach (better for long term future) + // Before we replace - and _ with . + // freebsd12 => freebsd + // solaris11-64 => solaris-64 + // darwin-10.15 => darwin + // macos10 => macos + //filename = filename.replace(/(\w|-)?1\d(\.\d+)?/g, '$1'); + + // Lossless two-step approach (better for exact matches) + // freebsd12 => freebsd^12 + // solaris11-64 => solaris^11-64 + // darwin-10.15 => darwin^10^15 + // macos10 => macos^10 + // darwin-10.15 => darwin^10^15 + filename = filename.replace(/(macos|darwin|osx)-?(1\d)\.(\d+)/, '$1^$2^$3'); + filename = filename.replace( + /(macos|darwin|osx|freebsd|solaris)-?(1\d)/, + '$1^$2', + ); + + // /{NAME}--windows-x86_64_v2-musl.exe => windows.x86.64.v2.musl.exe + filename = filename.replace(/[_\/\.\-]+/g, '.'); + + // Part 2: put it back + // freebsd^12 => freebsd_12 + // solaris^11.64 => solaris_11.64 + // darwin^10^15 => darwin_10_15 + // macos^10 => macos_10 + // darwin^10^15 => darwin_10_15 + filename = filename.replace(/(\w)\^(\d+)\^(\d+)/, '$1_$2_$3'); + filename = filename.replace(/(\w)\^(\d+)/, '$1_$2'); + + // x86.64 => x86_64 + filename = filename.replace('x86.64', 'x86_64'); + filename = filename.replace('32.bit', 'x86'); + filename = filename.replace('64.bit', 'x86_64'); + + // armv7 => arm_v7 + // amd64.v1 => amd64_v1 + // arm.6 => arm_v6 + // arm5 => arm_v5 + filename = filename.replace(/(arm|x86_64|amd64)\.?v?(\d)/g, '$1_v$2'); + filename = filename.replace(/arm_/g, 'arm'); + filename = filename.replace(/armv32/g, 'arm32'); + filename = filename.replace(/armv64/g, 'arm64'); + + // .{name}.Windows-X64. => {NAME}.windows-x64 + filename = filename.toLowerCase(); + filename = filename.replace('{name}', '{NAME}'); + + // trim excess .s + filename = filename.replace(/\.+$/g, ''); + filename = filename.replace(/^\.+/g, ''); + + let terms = filename.split(/\.+/); + // because '' => [''] + if (terms[0] === '') { + terms.shift(); + } - // /{NAME}--windows-x86_64_v2-musl.exe => windows.x86.64.v2.musl.exe - filename = filename.replace(/[_\/\.\-]+/g, '.'); - - // Part 2: put it back - // freebsd^12 => freebsd_12 - // solaris^11.64 => solaris_11.64 - // darwin^10^15 => darwin_10_15 - // macos^10 => macos_10 - // darwin^10^15 => darwin_10_15 - filename = filename.replace(/(\w)\^(\d+)\^(\d+)/, '$1_$2_$3'); - filename = filename.replace(/(\w)\^(\d+)/, '$1_$2'); - - // x86.64 => x86_64 - filename = filename.replace('x86.64', 'x86_64'); - filename = filename.replace('32.bit', 'x86'); - filename = filename.replace('64.bit', 'x86_64'); - - // armv7 => arm_v7 - // amd64.v1 => amd64_v1 - // arm.6 => arm_v6 - // arm5 => arm_v5 - filename = filename.replace(/(arm|x86_64|amd64)\.?v?(\d)/g, '$1_v$2'); - filename = filename.replace(/arm_/g, 'arm'); - filename = filename.replace(/armv32/g, 'arm32'); - filename = filename.replace(/armv64/g, 'arm64'); - - // .{name}.Windows-X64. => {NAME}.windows-x64 - filename = filename.toLowerCase(); - filename = filename.replace('{name}', '{NAME}'); - - // trim excess .s - filename = filename.replace(/\.+$/g, ''); - filename = filename.replace(/^\.+/g, ''); - - let terms = filename.split(/\.+/); - // because '' => [''] - if (terms[0] === '') { - terms.shift(); - } + return terms; +}; - return terms; - }; +/** + * @param {Partial} partialTarget - expected to be an empty object + * @param {ProjectPartial} proj + * @param {BuildAssetPartial} build + * @param {Array} terms + */ +Triplet.termsToTarget = function (partialTarget, proj, build, terms) { + /* jshint maxcomplexity:25 */ + + let target = Object.assign(partialTarget, { + os: build.os || '', + android: false, + arch: build.arch || '', + vendor: build.vendor || '', + libc: build.libc || '', + //ext: build.ext || '', + /** @type {Array} */ + unknownTerms: [], + }); + + for (let term of terms) { + let knownMatch = Triplet.TERMS_PRIMARY_MAP[term]; + if (!knownMatch) { + target.unknownTerms.push(term); + continue; + } - Triplet.termsToTarget = function (target, proj, build, terms) { - /* jshint maxcomplexity:25 */ + //console.log('dbg-known:', term, knownMatch); + let checkForMismatch = true; + upsertTarget(proj.name, build, terms, target, knownMatch, checkForMismatch); + } - Object.assign(target, { - os: build.os || '', - android: false, - arch: build.arch || '', - vendor: build.vendor || '', - libc: build.libc || '', - //ext: build.ext || '', - unknownTerms: [], - }); + if (target.android) { + if (target.os !== 'linux') { + throw new Error( + `[SANITY FAIL] '${terms}' should detect as 'linux', not '${target.os}'`, + ); + } + target.os = 'android'; + } + for (let termsMap of Triplet.TERMS_TIERED_MAPS) { for (let term of terms) { - let knownMatch = Triplet.TERMS_PRIMARY_MAP[term]; + let knownMatch = termsMap[term]; if (!knownMatch) { - target.unknownTerms.push(term); continue; } //console.log('dbg-known:', term, knownMatch); - let checkForMismatch = true; - upsertTarget( - proj.name, - build, - terms, - target, - knownMatch, - checkForMismatch, - ); + upsertTarget(proj.name, build, terms, target, knownMatch); + let complete = target.os && target.arch && target.vendor && target.libc; + if (complete) { + break; + } } + } - if (target.android) { - if (target.os !== 'linux') { - throw new Error( - `[SANITY FAIL] '${terms}' should detect as 'linux', not '${target.os}'`, - ); + // See + if (target.os === 'linux') { + if (target.libc === 'none') { + let isGnuOnly = terms.includes('gnu'); + if (isGnuOnly) { + target.libc = 'gnu'; } - target.os = 'android'; } + } - for (let termsMap of Triplet.TERMS_TIERED_MAPS) { - for (let term of terms) { - let knownMatch = termsMap[term]; - if (!knownMatch) { - continue; - } + // for (let ext of Triplet.TERMS_EXTS_BUILD) { + // if (filename.endsWith(ext)) { + // if (!target.ext) { + // target.ext = ext; + // } + // break; + // } + // } + + let os = target.os || 'E_MISSING_OS'; + let arch = target.arch || 'E_MISSING_ARCH'; + if (!target.vendor) { + target.vendor = 'unknown'; + } + if (!target.libc) { + target.libc = 'none'; + } - //console.log('dbg-known:', term, knownMatch); - upsertTarget(proj.name, build, terms, target, knownMatch); - let complete = target.os && target.arch && target.vendor && target.libc; - if (complete) { - break; - } - } - } + if (!target.os) { + let err = new Error( + `${os}: could not match ${proj.name} ${build.download} to os target`, + ); + Object.assign(err, { + _projName: proj.name, + _terms: terms, + _build: build, + }); + throw err; + } + if (!target.arch) { + let err = new Error( + `${arch}: could not match ${proj.name} ${build.download} to arch target`, + ); + Object.assign(err, { + _projName: proj.name, + _terms: terms, + _build: build, + }); + throw err; + } - // See - if (target.os === 'linux') { - if (target.libc === 'none') { - let isGnuOnly = terms.includes('gnu'); - if (isGnuOnly) { - target.libc = 'gnu'; - } - } - } + return target; +}; - // for (let ext of Triplet.TERMS_EXTS_BUILD) { - // if (filename.endsWith(ext)) { - // if (!target.ext) { - // target.ext = ext; - // } - // break; - // } - // } - - let os = target.os || 'E_MISSING_OS'; - let arch = target.arch || 'E_MISSING_ARCH'; - if (!target.vendor) { - target.vendor = 'unknown'; - } - if (!target.libc) { - target.libc = 'none'; - } +/** + * @param {String} name + * @param {BuildAssetPartial} build + * @param {Array} terms + * @param {TargetTriplet} target + * @param {TargetMatcher} knownMatches + * @param {Boolean} [checkForMismatch] + */ +function upsertTarget( + name, + build, + terms, + target, + knownMatches, + checkForMismatch = false, +) { + upsertOs(name, build, terms, target, knownMatches, checkForMismatch); + upsertArch(name, build, terms, target, knownMatches, checkForMismatch); + upsertLibc(name, build, terms, target, knownMatches, checkForMismatch); + upsertVendor(name, build, terms, target, knownMatches, checkForMismatch); +} - if (!target.os) { - let err = new Error( - `${os}: could not match ${proj.name} ${build.download} to os target`, - ); - err._projName = proj.name; - err._terms = terms; - err._build = build; - throw new Error(err); - } - if (!target.arch) { - let err = new Error( - `${arch}: could not match ${proj.name} ${build.download} to arch target`, - ); - err._projName = proj.name; - err._terms = terms; - err._build = build; - throw new Error(err); - } +/** + * @param {String} name + * @param {BuildAssetPartial} build + * @param {Array} terms + * @param {TargetTriplet} target + * @param {TargetMatcher} knownMatches + * @param {Boolean} checkForMismatch + */ +function upsertOs(name, build, terms, target, knownMatches, checkForMismatch) { + if (knownMatches.android) { + target.android = knownMatches.android; + } - return target; - }; - - function upsertTarget( - name, - build, - terms, - target, - knownMatches, - checkForMismatch, - ) { - upsertOs(name, build, terms, target, knownMatches, checkForMismatch); - upsertArch(name, build, terms, target, knownMatches, checkForMismatch); - upsertLibc(name, build, terms, target, knownMatches, checkForMismatch); - upsertVendor(name, build, terms, target, knownMatches, checkForMismatch); - } - - function upsertOs( - name, - build, - terms, - target, - knownMatches, - checkForMismatch, - ) { - if (knownMatches.android) { - target.android = knownMatches.android; - } + if (!knownMatches.os) { + return; + } - if (!knownMatches.os) { - return; - } + if (!target.os) { + target.os = knownMatches.os; + return; + } - if (!target.os) { - target.os = knownMatches.os; - return; - } + let shouldMatch = target.os && checkForMismatch; + if (!shouldMatch) { + return; + } - let shouldMatch = target.os && checkForMismatch; - if (!shouldMatch) { - return; - } + if (knownMatches.os !== target.os) { + let msg = `${name}: target detected wrong os for ${terms}`; + let detail = `'${knownMatches.os}' != @'${target.os}'`; + throw new Error(`${msg}: ${detail}`); + } +} - if (knownMatches.os !== target.os) { - let msg = `${name}: target detected wrong os for ${terms}`; - let detail = `'${knownMatches.os}' != @'${target.os}'`; - throw new Error(`${msg}: ${detail}`); - } +/** + * @param {String} name + * @param {BuildAssetPartial} build + * @param {Array} terms + * @param {TargetTriplet} target + * @param {TargetMatcher} knownMatches + * @param {Boolean} checkForMismatch + */ +function upsertArch( + name, + build, + terms, + target, + knownMatches, + checkForMismatch, +) { + if (!knownMatches.arch) { + return; } - function upsertArch( - name, - build, - terms, - target, - knownMatches, - checkForMismatch, - ) { - if (!knownMatches.arch) { - return; - } + if (!target.arch) { + target.arch = knownMatches.arch; + return; + } - if (!target.arch) { - target.arch = knownMatches.arch; - return; - } + let shouldMatch = target.arch?.length && checkForMismatch; + if (!shouldMatch) { + return; + } - let shouldMatch = target.arch?.length && checkForMismatch; - if (!shouldMatch) { - return; - } + let arches = knownMatches.arches; + if (!arches) { + arches = [knownMatches.arch]; + } + let isPossibleArch = arches.includes(target.arch); + if (!isPossibleArch) { + let msg = `${name}: target detected wrong arch for ${terms}: ${build.download}`; + let detail = `${knownMatches.arch} != @'${target.arch}'`; + throw new Error(`${msg}: ${detail}`); + } +} - let arches = knownMatches.arches; - if (!arches) { - arches = [knownMatches.arch]; - } - let isPossibleArch = arches.includes(target.arch); - if (!isPossibleArch) { - let msg = `${name}: target detected wrong arch for ${terms}: ${build.download}`; - let detail = `${knownMatches.arch} != @'${target.arch}'`; - throw new Error(`${msg}: ${detail}`); - } +/** + * @param {String} name + * @param {BuildAssetPartial} build + * @param {Array} terms + * @param {TargetTriplet} target + * @param {TargetMatcher} knownMatches + * @param {Boolean} checkForMismatch + */ +function upsertLibc( + name, + build, + terms, + target, + knownMatches, + checkForMismatch, +) { + if (!knownMatches.libc) { + return; } - function upsertLibc( - name, - build, - terms, - target, - knownMatches, - checkForMismatch, - ) { - if (!knownMatches.libc) { - return; - } + if (!target.libc) { + target.libc = knownMatches.libc; + return; + } - if (!target.libc) { - target.libc = knownMatches.libc; - return; - } + let shouldMatch = target.libc?.length && checkForMismatch; + if (!shouldMatch) { + return; + } - let shouldMatch = target.libc?.length && checkForMismatch; - if (!shouldMatch) { - return; - } + let libcs = knownMatches.libcs; + if (!libcs) { + libcs = [knownMatches.libc]; + } - let libcs = knownMatches.libcs; - if (!libcs) { - libcs = [knownMatches.libc]; - } + let isPossibleLibc = libcs.includes(target.libc); + if (!isPossibleLibc) { + let msg = `${name}: target detected wrong libc for ${terms}`; + let detail = `${knownMatches.libc} != @'${target.libc}'`; + throw new Error(`${msg}: ${detail}`); + } +} - let isPossibleLibc = libcs.includes(target.libc); - if (!isPossibleLibc) { - let msg = `${name}: target detected wrong libc for ${terms}`; - let detail = `${knownMatches.libc} != @'${target.libc}'`; - throw new Error(`${msg}: ${detail}`); - } +/** + * @param {String} name + * @param {BuildAssetPartial} build + * @param {Array} terms + * @param {TargetTriplet} target + * @param {TargetMatcher} knownMatches + * @param {Boolean} checkForMismatch + */ +function upsertVendor( + name, + build, + terms, + target, + knownMatches, + checkForMismatch, +) { + if (!knownMatches.vendor) { + return; } - function upsertVendor( - name, - build, - terms, - target, - knownMatches, - checkForMismatch, - ) { - if (!knownMatches.vendor) { - return; - } + if (!target.vendor) { + target.vendor = knownMatches.vendor; + return; + } - if (!target.vendor) { - target.vendor = knownMatches.vendor; - return; - } + let shouldMatch = build.vendor && checkForMismatch; + if (!shouldMatch) { + return; + } - let shouldMatch = build.vendor && checkForMismatch; - if (!shouldMatch) { - return; - } + if (knownMatches.vendor !== target.vendor) { + let msg = `${name}: target detected wrong vendor for ${terms}`; + let detail = `'${knownMatches.vendor}' != @'${target.vendor}'`; + throw new Error(`${msg}: ${detail}`); + } +} - if (knownMatches.vendor !== target.vendor) { - let msg = `${name}: target detected wrong vendor for ${terms}`; - let detail = `'${knownMatches.vendor}' != @'${target.vendor}'`; - throw new Error(`${msg}: ${detail}`); - } +/** + * @param {BuildAssetPartial} build + */ +Triplet.buildToPackageType = function (build) { + let filenames = []; + if (build.name) { + filenames.push(build.name); + } + if (build.download) { + filenames.push(build.download); } - Triplet.buildToPackageType = function (build) { - let filenames = []; - if (build.name) { - filenames.push(build.name); - } - if (build.download) { - filenames.push(build.download); + let pkg = ''; + for (let filename of filenames) { + pkg = Triplet.filenameToPackageType(filename); + if (pkg) { + break; } + } - let pkg = ''; - for (let filename of filenames) { - pkg = Triplet.filenameToPackageType(filename); - if (pkg) { - break; - } - } + return pkg; +}; - return pkg; - }; - - /** - * Determines the package type from the file name, accounting for ambiguous extensions. - * @param {String} filename - * @returns {String} - ex: '.zip', '.app.zip', '.tar.gz', '...' - */ - Triplet.filenameToPackageType = function (filename) { - let _filename = filename; - let pkg = ''; - - // find and remove the zip extension - for (let ext of Triplet.TERMS_EXTS_ZIP) { - if (_filename.endsWith(ext)) { - _filename = _filename.slice(0, -ext.length); - pkg = ext; - break; - } +/** + * Determines the package type from the file name, accounting for ambiguous extensions. + * @param {String} filename + * @returns {String} - ex: '.zip', '.app.zip', '.tar.gz', '...' + */ +Triplet.filenameToPackageType = function (filename) { + let _filename = filename; + let pkg = ''; + + // find and remove the zip extension + for (let ext of Triplet.TERMS_EXTS_ZIP) { + if (_filename.endsWith(ext)) { + _filename = _filename.slice(0, -ext.length); + pkg = ext; + break; } + } - // find the container extension - for (let ext of Triplet.TERMS_EXTS_PKG) { - if (_filename.endsWith(ext)) { - _filename = _filename.slice(0, -ext.length); - pkg = `${ext}${pkg}`; - break; - } + // find the container extension + for (let ext of Triplet.TERMS_EXTS_PKG) { + if (_filename.endsWith(ext)) { + _filename = _filename.slice(0, -ext.length); + pkg = `${ext}${pkg}`; + break; } - // twice because sometimes things are nested in silly ways - // ex: goreleaser's .pkg.tar.gz for Arch - for (let ext of Triplet.TERMS_EXTS_PKG) { - if (_filename.endsWith(ext)) { - _filename = _filename.slice(0, -ext.length); - pkg = `${ext}${pkg}`; - break; - } + } + // twice because sometimes things are nested in silly ways + // ex: goreleaser's .pkg.tar.gz for Arch + for (let ext of Triplet.TERMS_EXTS_PKG) { + if (_filename.endsWith(ext)) { + _filename = _filename.slice(0, -ext.length); + pkg = `${ext}${pkg}`; + break; } - // thrice for sanity check! - for (let ext of Triplet.TERMS_EXTS_PKG) { - if (_filename.endsWith(ext)) { - console.warn(`[Sanity Fail] max silliness of pkg type nesting:`); - console.warn(` ${filename}`); - //_filename = _filename.slice(0, -ext.length); - pkg = `${ext}${pkg}`; - break; - } + } + // thrice for sanity check! + for (let ext of Triplet.TERMS_EXTS_PKG) { + if (_filename.endsWith(ext)) { + console.warn(`[Sanity Fail] max silliness of pkg type nesting:`); + console.warn(` ${filename}`); + //_filename = _filename.slice(0, -ext.length); + pkg = `${ext}${pkg}`; + break; } + } - // ex: - // - '.zip' - // - '.app.zip' - // - '.tar.gz' - // - '.exe.xz' - // - '' (linux/bsd binary) - return pkg; - }; - - // @ts-ignore - window.Triplet = Triplet; -})(('object' === typeof window && window) || {}, Triplet); -if ('object' === typeof module) { - module.exports = Triplet; -} + // ex: + // - '.zip' + // - '.app.zip' + // - '.tar.gz' + // - '.exe.xz' + // - '' (linux/bsd binary) + return pkg; +}; + +export let TERMS_CHANNEL = Triplet.TERMS_CHANNEL; +export let TERMS_CHECKSUM = Triplet.TERMS_CHECKSUM; +export let TERMS_NON_BUILD = Triplet.TERMS_NON_BUILD; +export let _RE_TERMS_NON_BUILD = Triplet._RE_TERMS_NON_BUILD; +export let TERMS_EXTS_NON_BUILD = Triplet.TERMS_EXTS_NON_BUILD; +export let TERMS_EXTS_PKG = Triplet.TERMS_EXTS_PKG; +export let TERMS_EXTS_ZIP = Triplet.TERMS_EXTS_ZIP; +export let TERMS_EXTS_NON_INFORMATIVE = Triplet.TERMS_EXTS_NON_INFORMATIVE; +export let TERMS_EXTS_SOURCE = Triplet.TERMS_EXTS_SOURCE; +export let TERMS_PRIMARY_MAP = Triplet.TERMS_PRIMARY_MAP; +export let TERMS_TIERED_MAPS = Triplet.TERMS_TIERED_MAPS; +export let maybeInstallable = Triplet.maybeInstallable; +export let toPattern = Triplet.toPattern; +export let patternToTerms = Triplet.patternToTerms; +export let termsToTarget = Triplet.termsToTarget; +export let buildToPackageType = Triplet.buildToPackageType; +export let filenameToPackageType = Triplet.filenameToPackageType; +export default Triplet; diff --git a/types.js b/types.js index 78830ea..fac0945 100644 --- a/types.js +++ b/types.js @@ -1,6 +1,4 @@ -'use strict'; - -module.exports._types = true; +let types = {}; /** * @typedef {""|"ANYOS"|"posix_2024"|"posix_2017"|"aix"|"android"|"darwin"|"dragonfly"|"freebsd"|"illumos"|"linux"|"netbsd"|"openbsd"|"plan9"|"solaris"|"sunos"|"wasi"|"windows"} OsString @@ -18,6 +16,27 @@ module.exports._types = true; * @typedef {""|"ANYVENDOR"|"apple"|"pc"|"unknown"} VendorString */ +/** + * @typedef TargetMatcher + * @prop {OsString} [os] + * @prop {VendorString} [vendor] + * @prop {ArchString} [arch] + * @prop {Array} [arches] + * @prop {LibcString} [libc] + * @prop {Array} [libcs] + * @prop {Boolean} [android] + */ + +/** + * @typedef ErrorDetails + * @prop {String} message + * @prop {String} os + * @prop {String} arch + * @prop {String} libc + * @prop {String} vendor + * @prop {Array} terms + */ + /** * @typedef TargetTriplet * @prop {OsString} os @@ -25,5 +44,10 @@ module.exports._types = true; * @prop {LibcString} libc * @prop {VendorString} [vendor] * @prop {Array} [unknownTerms] + * @prop {Array} [libcs] + * @prop {Array} [errors] * @prop {Boolean} [android] - for intermediary representation */ + +export let _types = types; +export default types;