diff --git a/.gitignore b/.gitignore index a4e7538..0d2c97c 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ node_modules/ .vscode/ /test_artifacts/ /coverage/ +yarn-error.log diff --git a/hak/matrix-seshat/fetchDeps.ts b/hak/matrix-seshat/fetchDeps.ts index 6edccaa..54b462d 100644 --- a/hak/matrix-seshat/fetchDeps.ts +++ b/hak/matrix-seshat/fetchDeps.ts @@ -18,12 +18,20 @@ import path from 'path'; import childProcess from 'child_process'; import fs from 'fs'; import fsProm from 'fs/promises'; -import needle from 'needle'; import tar from 'tar'; +import fetch from 'node-fetch'; +import { promises as stream } from "stream"; import HakEnv from '../../scripts/hak/hakEnv'; import { DependencyInfo } from '../../scripts/hak/dep'; +async function download(url: string, filename: string): Promise { + const resp = await fetch(url); + if (!resp.ok) throw new Error(`unexpected response ${resp.statusText}`); + if (!resp.body) throw new Error(`unexpected response has no body ${resp.statusText}`); + await stream.pipeline(resp.body, fs.createWriteStream(filename)); +} + export default async function(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise { if (hakEnv.wantsStaticSqlCipher()) { await getSqlCipher(hakEnv, moduleInfo); @@ -57,11 +65,7 @@ async function getSqlCipher(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise haveSqlcipherTar = false; } if (!haveSqlcipherTar) { - const bob = needle('get', `https://github.com/sqlcipher/sqlcipher/archive/v${version}.tar.gz`, { - follow: 10, - output: sqlCipherTarball, - }); - await bob; + await download(`https://github.com/sqlcipher/sqlcipher/archive/v${version}.tar.gz`, sqlCipherTarball); } // Extract the tarball to per-target directories, then we avoid cross-contaiminating archs @@ -118,10 +122,7 @@ async function getOpenSsl(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise fn.endsWith(". // Ensure lib, lib/i18n and lib/i18n/strings all exist fs.mkdirSync('lib/i18n/strings', { recursive: true }); -function genLangFile(file, dest) { - let translations = {}; +type Translations = Record | string>; + +function genLangFile(file: string, dest: string): void { + const inTrs: Record = {}; [file].forEach(function(f) { if (fs.existsSync(f)) { try { - Object.assign( - translations, - JSON.parse(fs.readFileSync(f).toString()), - ); + Object.assign(inTrs, JSON.parse(fs.readFileSync(f).toString())); } catch (e) { console.error("Failed: " + f, e); throw e; @@ -41,8 +40,7 @@ function genLangFile(file, dest) { } }); - translations = weblateToCounterpart(translations); - + const translations = weblateToCounterpart(inTrs); const json = JSON.stringify(translations, null, 4); const filename = path.basename(file); @@ -66,8 +64,8 @@ function genLangFile(file, dest) { * "other": "%(count)s badgers" * } */ -function weblateToCounterpart(inTrs) { - const outTrs = {}; +function weblateToCounterpart(inTrs: Record): Translations { + const outTrs: Translations = {}; for (const key of Object.keys(inTrs)) { const keyParts = key.split('|', 2); @@ -96,12 +94,12 @@ function weblateToCounterpart(inTrs) { watch the input files for a given language, regenerate the file, and regenerating languages.json with the new filename */ -function watchLanguage(file, dest) { +function watchLanguage(file: string, dest: string): void { // XXX: Use a debounce because for some reason if we read the language // file immediately after the FS event is received, the file contents // appears empty. Possibly https://github.com/nodejs/node/issues/6112 - let makeLangDebouncer; - const makeLang = () => { + let makeLangDebouncer: NodeJS.Timeout | undefined; + const makeLang = (): void => { if (makeLangDebouncer) { clearTimeout(makeLangDebouncer); } @@ -118,7 +116,7 @@ function watchLanguage(file, dest) { // language resources const I18N_DEST = "lib/i18n/strings/"; -INCLUDE_LANGS.forEach((file) => { +INCLUDE_LANGS.forEach((file): void => { genLangFile(I18N_BASE_PATH + file, I18N_DEST); }, {}); diff --git a/scripts/fetch-package.js b/scripts/fetch-package.ts similarity index 77% rename from scripts/fetch-package.js rename to scripts/fetch-package.ts index 19119f2..96a01c6 100644 --- a/scripts/fetch-package.js +++ b/scripts/fetch-package.ts @@ -1,42 +1,40 @@ -#!/usr/bin/env node +#!/usr/bin/env -S npx ts-node --resolveJsonModule -const process = require('process'); -const path = require('path'); -const fs = require('fs'); -const fsPromises = require('fs').promises; -const childProcess = require('child_process'); -const tar = require('tar'); -const asar = require('asar'); -const needle = require('needle'); +import * as path from "path"; +import { createWriteStream, promises as fs } from "fs"; +import * as childProcess from "child_process"; +import tar from "tar"; +import * as asar from "asar"; +import fetch from "node-fetch"; +import { promises as stream } from "stream"; -const riotDesktopPackageJson = require('../package.json'); -const { setPackageVersion } = require('./set-version.js'); +import riotDesktopPackageJson from "../package.json"; +import { setPackageVersion } from "./set-version"; const PUB_KEY_URL = "https://packages.riot.im/element-release-key.asc"; const PACKAGE_URL_PREFIX = "https://github.com/vector-im/element-web/releases/download/"; const DEVELOP_TGZ_URL = "https://develop.element.io/develop.tar.gz"; const ASAR_PATH = 'webapp.asar'; -async function downloadToFile(url, filename) { +async function downloadToFile(url: string, filename: string): Promise { console.log("Downloading " + url + "..."); try { - await needle('get', url, null, - { - follow_max: 5, - output: filename, - }, - ); + const resp = await fetch(url); + if (!resp.ok) throw new Error(`unexpected response ${resp.statusText}`); + if (!resp.body) throw new Error(`unexpected response has no body ${resp.statusText}`); + await stream.pipeline(resp.body, createWriteStream(filename)); } catch (e) { + console.error(e); try { - await fsPromises.unlink(filename); + await fs.unlink(filename); } catch (_) {} throw e; } } -async function verifyFile(filename) { - return new Promise((resolve, reject) => { +async function verifyFile(filename: string): Promise { + return new Promise((resolve, reject) => { childProcess.execFile('gpg', ['--verify', filename + '.asc', filename], (error) => { if (error) { reject(error); @@ -47,15 +45,15 @@ async function verifyFile(filename) { }); } -async function main() { +async function main(): Promise { let verify = true; let importkey = false; let pkgDir = 'packages'; let deployDir = 'deploys'; - let cfgDir; - let targetVersion; - let filename; - let url; + let cfgDir: string | undefined; + let targetVersion: string | undefined; + let filename: string | undefined; + let url: string | undefined; let setVersion = false; while (process.argv.length > 2) { @@ -104,7 +102,7 @@ async function main() { url = PACKAGE_URL_PREFIX + targetVersion + '/' + filename; } - const haveGpg = await new Promise((resolve) => { + const haveGpg = await new Promise((resolve) => { childProcess.execFile('gpg', ['--version'], (error) => { resolve(!error); }); @@ -116,7 +114,7 @@ async function main() { return 1; } - await new Promise((resolve) => { + await new Promise((resolve) => { const gpgProc = childProcess.execFile('gpg', ['--import'], (error) => { if (error) { console.log("Failed to import key", error); @@ -125,7 +123,9 @@ async function main() { } resolve(!error); }); - needle.get(PUB_KEY_URL).pipe(gpgProc.stdin); + fetch(PUB_KEY_URL).then(resp => { + stream.pipeline(resp.body, gpgProc.stdin!); + }); }); return 0; } @@ -154,7 +154,7 @@ async function main() { if (!haveDeploy) { const outPath = path.join(pkgDir, filename); try { - await fsPromises.stat(outPath); + await fs.stat(outPath); console.log("Already have " + filename + ": not redownloading"); } catch (e) { try { @@ -167,7 +167,7 @@ async function main() { if (verify) { try { - await fsPromises.stat(outPath+'.asc'); + await fs.stat(outPath+'.asc'); console.log("Already have " + filename + ".asc: not redownloading"); } catch (e) { try { @@ -202,9 +202,9 @@ async function main() { } try { - await fsPromises.stat(ASAR_PATH); + await fs.stat(ASAR_PATH); console.log(ASAR_PATH + " already present: removing"); - await fsPromises.unlink(ASAR_PATH); + await fs.unlink(ASAR_PATH); } catch (e) { } @@ -212,7 +212,7 @@ async function main() { const configJsonSource = path.join(cfgDir, 'config.json'); const configJsonDest = path.join(expectedDeployDir, 'config.json'); console.log(configJsonSource + ' -> ' + configJsonDest); - await fsPromises.copyFile(configJsonSource, configJsonDest); + await fs.copyFile(configJsonSource, configJsonDest); } else { console.log("Skipping config file"); } @@ -221,7 +221,7 @@ async function main() { await asar.createPackage(expectedDeployDir, ASAR_PATH); if (setVersion) { - const semVer = fs.readFileSync(path.join(expectedDeployDir, "version"), "utf-8").trim(); + const semVer = (await fs.readFile(path.join(expectedDeployDir, "version"), "utf-8")).trim(); console.log("Updating version to " + semVer); await setPackageVersion(semVer); } diff --git a/scripts/hak/clean.ts b/scripts/hak/clean.ts index dbd55fa..965ac1b 100644 --- a/scripts/hak/clean.ts +++ b/scripts/hak/clean.ts @@ -22,7 +22,7 @@ import HakEnv from './hakEnv'; export default async function clean(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise { await new Promise((resolve, reject) => { - rimraf(moduleInfo.moduleDotHakDir, (err: Error) => { + rimraf(moduleInfo.moduleDotHakDir, (err?: Error | null) => { if (err) { reject(err); } else { @@ -32,7 +32,7 @@ export default async function clean(hakEnv: HakEnv, moduleInfo: DependencyInfo): }); await new Promise((resolve, reject) => { - rimraf(path.join(hakEnv.dotHakDir, 'links', moduleInfo.name), (err: Error) => { + rimraf(path.join(hakEnv.dotHakDir, 'links', moduleInfo.name), (err?: Error | null) => { if (err) { reject(err); } else { @@ -42,7 +42,7 @@ export default async function clean(hakEnv: HakEnv, moduleInfo: DependencyInfo): }); await new Promise((resolve, reject) => { - rimraf(path.join(hakEnv.projectRoot, 'node_modules', moduleInfo.name), (err: Error) => { + rimraf(path.join(hakEnv.projectRoot, 'node_modules', moduleInfo.name), (err?: Error | null) => { if (err) { reject(err); } else { diff --git a/scripts/hak/find-npm-prefix.d.ts b/scripts/hak/find-npm-prefix.d.ts new file mode 100644 index 0000000..42d731a --- /dev/null +++ b/scripts/hak/find-npm-prefix.d.ts @@ -0,0 +1,19 @@ +/* +Copyright 2022 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +declare module "find-npm-prefix" { + export default function findPrefix(dir: string): Promise; +} diff --git a/scripts/hak/hakEnv.ts b/scripts/hak/hakEnv.ts index bdf7d03..b72439a 100644 --- a/scripts/hak/hakEnv.ts +++ b/scripts/hak/hakEnv.ts @@ -37,20 +37,17 @@ async function getRuntimeVersion(projectRoot: string): Promise { export default class HakEnv { public readonly target: Target; - public runtime: string; - public runtimeVersion: string; + public runtime?: string; + public runtimeVersion?: string; public dotHakDir: string; public constructor(public readonly projectRoot: string, targetId: TargetId | null) { - if (targetId) { - this.target = TARGETS[targetId]; - } else { - this.target = getHost(); - } + const target = targetId ? TARGETS[targetId] : getHost(); - if (!this.target) { + if (!target) { throw new Error(`Unknown target ${targetId}!`); } + this.target = target; this.dotHakDir = path.join(this.projectRoot, '.hak'); } @@ -60,10 +57,7 @@ export default class HakEnv { } public getRuntimeAbi(): string { - return nodePreGypVersioning.get_runtime_abi( - this.runtime, - this.runtimeVersion, - ); + return nodePreGypVersioning.get_runtime_abi(this.runtime!, this.runtimeVersion!); } // {node_abi}-{platform}-{arch} @@ -95,7 +89,7 @@ export default class HakEnv { return isHostId(this.target.id); } - public makeGypEnv(): Record { + public makeGypEnv(): Record { return Object.assign({}, process.env, { npm_config_arch: this.target.arch, npm_config_target_arch: this.target.arch, @@ -107,10 +101,6 @@ export default class HakEnv { }); } - public getNodeModuleBin(name: string): string { - return path.join(this.projectRoot, 'node_modules', '.bin', name); - } - public wantsStaticSqlCipherUnix(): boolean { return this.isMac() || process.env.SQLCIPHER_STATIC == '1'; } diff --git a/scripts/hak/index.ts b/scripts/hak/index.ts index 9a879a2..00aec09 100644 --- a/scripts/hak/index.ts +++ b/scripts/hak/index.ts @@ -38,7 +38,7 @@ const MODULECOMMANDS = [ // Shortcuts for multiple commands at once (useful for building universal binaries // because you can run the fetch/fetchDeps/build for each arch and then copy/link once) -const METACOMMANDS = { +const METACOMMANDS: Record = { 'fetchandbuild': ['check', 'fetch', 'fetchDeps', 'build'], 'copyandlink': ['copy', 'link'], }; diff --git a/scripts/hak/node-pre-gyp.d.ts b/scripts/hak/node-pre-gyp.d.ts new file mode 100644 index 0000000..098df43 --- /dev/null +++ b/scripts/hak/node-pre-gyp.d.ts @@ -0,0 +1,20 @@ +/* +Copyright 2022 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +declare module "node-pre-gyp/lib/util/versioning" { + // eslint-disable-next-line @typescript-eslint/naming-convention + export function get_runtime_abi(runtime: string, version: string): string; +} diff --git a/scripts/set-version.js b/scripts/set-version.ts similarity index 61% rename from scripts/set-version.js rename to scripts/set-version.ts index d5d5cc1..4b896ab 100755 --- a/scripts/set-version.js +++ b/scripts/set-version.ts @@ -1,29 +1,28 @@ -#!/usr/bin/env node +#!/usr/bin/env -S npx ts-node /* * Checks for the presence of a webapp, inspects its version and sets the * version metadata of the package to match. */ -const fs = require('fs').promises; -const asar = require('asar'); -const childProcess = require('child_process'); +import { promises as fs } from "fs"; +import * as asar from "asar"; +import * as childProcess from "child_process"; -async function versionFromAsar() { +export async function versionFromAsar(): Promise { try { await fs.stat('webapp.asar'); } catch (e) { - console.log("No 'webapp.asar' found. Run 'yarn run fetch'"); - return 1; + throw new Error("No 'webapp.asar' found. Run 'yarn run fetch'"); } return asar.extractFile('webapp.asar', 'version').toString().trim(); } -async function setPackageVersion(ver) { +export async function setPackageVersion(ver: string): Promise { // set version in package.json: electron-builder will use this to populate // all the various version fields - await new Promise((resolve, reject) => { + await new Promise((resolve, reject) => { childProcess.execFile(process.platform === 'win32' ? 'yarn.cmd' : 'yarn', [ 'version', '-s', @@ -40,16 +39,20 @@ async function setPackageVersion(ver) { }); } -async function main(args) { +async function main(args: string[]): Promise { let version = args[0]; if (version === undefined) version = await versionFromAsar(); await setPackageVersion(version); + return 0; } if (require.main === module) { - main(process.argv.slice(2)).then((ret) => process.exit(ret)); + main(process.argv.slice(2)).then((ret) => { + process.exit(ret); + }).catch(e => { + console.error(e); + process.exit(1); + }); } - -module.exports = { versionFromAsar, setPackageVersion }; diff --git a/scripts/hak/tsconfig.json b/scripts/tsconfig.json similarity index 81% rename from scripts/hak/tsconfig.json rename to scripts/tsconfig.json index 82468ec..198ae11 100644 --- a/scripts/hak/tsconfig.json +++ b/scripts/tsconfig.json @@ -1,12 +1,15 @@ { "compilerOptions": { + "resolveJsonModule": true, "moduleResolution": "node", "esModuleInterop": true, "target": "es2017", "module": "commonjs", "sourceMap": false, + "strict": true, "lib": [ "es2019", + "dom" ] }, "include": [ diff --git a/yarn.lock b/yarn.lock index b06ee8a..cc308fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1947,6 +1947,18 @@ ajv "^6.12.0" ajv-keywords "^3.4.1" +"@electron/asar@^3.2.0": + version "3.2.2" + resolved "https://registry.yarnpkg.com/@electron/asar/-/asar-3.2.2.tgz#f6ae4eb4343ad00b994c40db3f09f71f968ff9c0" + integrity sha512-32fMU68x8a6zvxtC1IC/BhPDKTh8rQjdmwEplj3CDpnkcwBzZVN9v/8cK0LJqQ0FOQQVZW8BWZ1S6UU53TYR4w== + dependencies: + chromium-pickle-js "^0.2.0" + commander "^5.0.0" + glob "^7.1.6" + minimatch "^3.0.4" + optionalDependencies: + "@types/glob" "^7.1.1" + "@electron/get@^1.14.1": version "1.14.1" resolved "https://registry.yarnpkg.com/@electron/get/-/get-1.14.1.tgz#16ba75f02dffb74c23965e72d617adc721d27f40" @@ -2792,6 +2804,14 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== +"@types/tar@^6.1.3": + version "6.1.3" + resolved "https://registry.yarnpkg.com/@types/tar/-/tar-6.1.3.tgz#46a2ce7617950c4852dfd7e9cd41aa8161b9d750" + integrity sha512-YzDOr5kdAeqS8dcO6NTTHTMJ44MUCBDoLEIyPtwEn7PssKqUYL49R1iCVJPeiPzPlKi6DbH33eZkpeJ27e4vHg== + dependencies: + "@types/node" "*" + minipass "^3.3.5" + "@types/verror@^1.10.3": version "1.10.6" resolved "https://registry.yarnpkg.com/@types/verror/-/verror-1.10.6.tgz#3e600c62d210c5826460858f84bcbb65805460bb" @@ -3209,7 +3229,7 @@ array.prototype.flat@^1.2.5: es-abstract "^1.20.4" es-shim-unscopables "^1.0.0" -asar@^3.0.0, asar@^3.0.3, asar@^3.1.0: +asar@^3.0.3, asar@^3.1.0: version "3.2.0" resolved "https://registry.yarnpkg.com/asar/-/asar-3.2.0.tgz#e6edb5edd6f627ebef04db62f771c61bea9c1221" integrity sha512-COdw2ZQvKdFGFxXwX3oYh2/sOsJWJegrdJCGxnN4MZ7IULgRBp9P6665aqj9z1v9VwP4oP1hRBojRDQ//IGgAg== @@ -6467,7 +6487,7 @@ minipass@^2.6.0, minipass@^2.9.0: safe-buffer "^5.1.2" yallist "^3.0.0" -minipass@^3.0.0, minipass@^3.1.1, minipass@^3.1.6: +minipass@^3.0.0, minipass@^3.1.1, minipass@^3.1.6, minipass@^3.3.5: version "3.3.6" resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== @@ -6526,7 +6546,7 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== -needle@^2.5.0, needle@^2.5.2: +needle@^2.5.2: version "2.9.1" resolved "https://registry.yarnpkg.com/needle/-/needle-2.9.1.tgz#22d1dffbe3490c2b83e301f7709b6736cd8f2684" integrity sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ==