From 7342fb11911a87919c84ff31f4ec348a3c233148 Mon Sep 17 00:00:00 2001 From: Anush008 <46051506+Anush008@users.noreply.github.com> Date: Sun, 5 Nov 2023 23:10:36 +0530 Subject: [PATCH 1/4] feat: npm install --- .github/workflows/deploy.yml | 23 +++++++ npm/install.js | 129 +++++++++++++++++++++++++++++++++++ npm/package-lock.json | 122 +++++++++++++++++++++++++++++++++ npm/package.json | 19 ++++++ npm/runner.js | 23 +++++++ 5 files changed, 316 insertions(+) create mode 100644 npm/install.js create mode 100644 npm/package-lock.json create mode 100644 npm/package.json create mode 100644 npm/runner.js diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 7ad083c..8eca725 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -140,3 +140,26 @@ jobs: files: | ttyper-*/ttyper-* LICENSE.md + + npm-release: + name: NPM Publish + needs: github-release + runs-on: ubuntu-latest + steps: + - name: Setup | Checkout + uses: actions/checkout@v4 + - name: Setup | Node + uses: actions/setup-node@v3 + with: + node-version: '20.x' + registry-url: 'https://registry.npmjs.org' + - name: Setup | Vesion + run: npm version ${{ github.ref_name }} + working-directory: ./npm + - name: NPM Publish + run: | + cp ../README.md . + npm publish + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + working-directory: ./npm \ No newline at end of file diff --git a/npm/install.js b/npm/install.js new file mode 100644 index 0000000..fe5d343 --- /dev/null +++ b/npm/install.js @@ -0,0 +1,129 @@ +import fs from "fs"; +import https from "https"; +import path from "path"; +import { fileURLToPath } from "url"; + +// ESM doesn't support __dirname and __filename by default +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const packageJson = JSON.parse(fs.readFileSync("./package.json", "utf8")); +// The version number is expected to be in parity with the ttyper release version +const { version, repository, name, binDir: directory } = packageJson; + +// All the binary files will be stored in the /bin directory +const binDir = path.join(__dirname, directory); +console.log(`Installing ${name} v${version}`); + +try { + void install(); +} catch (error) { + console.error("Installation failed:", error.message); +} + +async function install() { + if (fs.existsSync(binDir)) { + fs.rmSync(binDir, { recursive: true }); + } + fs.mkdirSync(binDir, { + mode: 0o777, + }); + await getBinary(); + + // Remove the node_modules as we'll only need the binary + fs.rmSync(path.join(__dirname, "node_modules"), { recursive: true }); +} + +function getBinaryDownloadURL() { + let os, arch; + + // Possible values are : 'aix' | 'android' | 'darwin' | 'freebsd' | 'haiku' | 'linux' | 'openbsd' | 'sunos' | 'win32' | 'cygwin' | 'netbsd' + switch (process.platform) { + case "win32": + case "cygwin": + os = "pc-windows-msvc"; + break; + case "darwin": + os = "apple-darwin"; + break; + case "linux": + os = "unknown-linux-gnu"; + break; + default: + throw new Error(`Unsupported OS: ${process.platform}`); + } + + // Possible values are: 'arm' | 'arm64' | 'ia32' | 'mips' | 'mipsel' | 'ppc' | 'ppc64' | 's390' | 's390x' | 'x64' + switch (process.arch) { + case "x64": + arch = "x86_64"; + break; + case "arm64": + arch = "aarch64"; + break; + case "ia32": + arch = "i686"; + break; + default: + throw new Error(`Unsupported architecture: ${process.arch}`); + } + + const extension = os === "pc-windows-msvc" ? "zip" : "tar.gz"; + + return `${repository}/releases/download/v${version}/${name}-${arch}-${os}.${extension}`; +} + +function downloadPackage(url, outputPath) { + // We use https.get instead of fetch to get a readable stream from the response without additional dependencies + return new Promise((resolve, reject) => { + https + .get(url, (response) => { + // If the response is a redirect, we download the package from the new location + if (response.statusCode === 302) { + resolve(downloadPackage(response.headers.location, outputPath)); + } else if (response.statusCode === 200) { + const file = fs.createWriteStream(outputPath); + response.pipe(file); + file.on("finish", () => { + file.close(resolve); + }); + } else { + reject( + new Error( + `Failed to download ${name}. Status code: ${response.statusCode}` + ) + ); + } + }) + .on("error", reject); + }); +} + +async function extractPackage(inputPath, outputPath) { + if (path.extname(inputPath) === ".gz") { + const tar = await import("tar"); + await tar.x({ + file: inputPath, + cwd: outputPath, + }); + } else if (path.extname(inputPath) === ".zip") { + const AdmZip = (await import("adm-zip")).default; + const zip = new AdmZip(inputPath); + zip.extractAllTo(outputPath, true, true); + } +} + +async function getBinary() { + const downloadURL = getBinaryDownloadURL(); + console.log(`Downloading ${name} from ${downloadURL}`); + + const pkgName = ["win32", "cygwin"].includes(process.platform) + ? `package.zip` + : `package.tar.gz`; + const packagePath = path.join(binDir, pkgName); + + await downloadPackage(downloadURL, packagePath); + await extractPackage(packagePath, binDir); + + fs.rmSync(packagePath); +} diff --git a/npm/package-lock.json b/npm/package-lock.json new file mode 100644 index 0000000..2042f0d --- /dev/null +++ b/npm/package-lock.json @@ -0,0 +1,122 @@ +{ + "name": "ttyper", + "version": "0.0.0-alpha2", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "ttyper", + "version": "0.0.0-alpha2", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "adm-zip": "^0.5.10", + "tar": "^6.1.15" + }, + "bin": { + "ttyper": "bin/runner.js" + } + }, + "node_modules/adm-zip": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.10.tgz", + "integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", + "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } +} diff --git a/npm/package.json b/npm/package.json new file mode 100644 index 0000000..957c5df --- /dev/null +++ b/npm/package.json @@ -0,0 +1,19 @@ +{ + "name": "ttyper", + "version": "0.0.0-alpha2", + "description": "ttyper is a terminal-based typing test built with Rust and tui-rs.", + "repository": "https://github.com/max-niederman/ttyper", + "license": "MIT", + "bin": { + "ttyper": "runner.js" + }, + "type": "module", + "scripts": { + "install": "node install.js" + }, + "binDir": "bin", + "dependencies": { + "adm-zip": "^0.5.10", + "tar": "^6.1.15" + } +} diff --git a/npm/runner.js b/npm/runner.js new file mode 100644 index 0000000..a410a15 --- /dev/null +++ b/npm/runner.js @@ -0,0 +1,23 @@ +#!/usr/bin/env node + +import { spawn } from "child_process"; +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; + +// ESM doesn't support __dirname and __filename by default +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const packageJsonPath = path.join(__dirname, "package.json"); +const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")); +const { name, binDir } = packageJson; + +const commandArgs = process.argv.slice(2); +const exeName = ["win32", "cygwin"].includes(process.platform) + ? `${name}.exe` + : name; +const cwd = path.join(__dirname, binDir); +const binPath = path.join(cwd, exeName); + +const child = spawn(binPath, commandArgs, { stdio: "inherit", cwd }); \ No newline at end of file From 624dd686fb166db0d8e1587a4c20ebfcb54e4a9f Mon Sep 17 00:00:00 2001 From: Anush008 <46051506+Anush008@users.noreply.github.com> Date: Sun, 5 Nov 2023 23:29:07 +0530 Subject: [PATCH 2/4] chore: pin to 0.0.0 --- npm/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/npm/package.json b/npm/package.json index 957c5df..00796c1 100644 --- a/npm/package.json +++ b/npm/package.json @@ -1,6 +1,6 @@ { "name": "ttyper", - "version": "0.0.0-alpha2", + "version": "0.0.0", "description": "ttyper is a terminal-based typing test built with Rust and tui-rs.", "repository": "https://github.com/max-niederman/ttyper", "license": "MIT", From 12821491e8e4dd354da6ad67fbd743128af9e288 Mon Sep 17 00:00:00 2001 From: Anush008 <46051506+Anush008@users.noreply.github.com> Date: Sun, 5 Nov 2023 23:29:58 +0530 Subject: [PATCH 3/4] chore: typo fix --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 8eca725..2087b87 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -153,7 +153,7 @@ jobs: with: node-version: '20.x' registry-url: 'https://registry.npmjs.org' - - name: Setup | Vesion + - name: Setup | Version run: npm version ${{ github.ref_name }} working-directory: ./npm - name: NPM Publish From d33894f98636280a73575c114f0fff339cda9e3c Mon Sep 17 00:00:00 2001 From: Anush008 Date: Mon, 4 Mar 2024 10:10:14 +0530 Subject: [PATCH 4/4] chore: resolve download URL --- npm/install.js | 4 ++-- npm/package.json | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/npm/install.js b/npm/install.js index fe5d343..97beabc 100644 --- a/npm/install.js +++ b/npm/install.js @@ -9,7 +9,7 @@ const __dirname = path.dirname(__filename); const packageJson = JSON.parse(fs.readFileSync("./package.json", "utf8")); // The version number is expected to be in parity with the ttyper release version -const { version, repository, name, binDir: directory } = packageJson; +const { version, releasesUrl, name, binDir: directory } = packageJson; // All the binary files will be stored in the /bin directory const binDir = path.join(__dirname, directory); @@ -70,7 +70,7 @@ function getBinaryDownloadURL() { const extension = os === "pc-windows-msvc" ? "zip" : "tar.gz"; - return `${repository}/releases/download/v${version}/${name}-${arch}-${os}.${extension}`; + return `${releasesUrl}/download/v${version}/${name}-${arch}-${os}.${extension}`; } function downloadPackage(url, outputPath) { diff --git a/npm/package.json b/npm/package.json index 00796c1..516c847 100644 --- a/npm/package.json +++ b/npm/package.json @@ -2,7 +2,11 @@ "name": "ttyper", "version": "0.0.0", "description": "ttyper is a terminal-based typing test built with Rust and tui-rs.", - "repository": "https://github.com/max-niederman/ttyper", + "repository": { + "type": "git", + "url": "git+https://github.com/Anush008/ttyper.git" + }, + "releasesUrl": "https://github.com/Anush008/ttyper/releases", "license": "MIT", "bin": { "ttyper": "runner.js" @@ -16,4 +20,4 @@ "adm-zip": "^0.5.10", "tar": "^6.1.15" } -} +} \ No newline at end of file