Browse Source

:sparkles: Draft workflow for building & publishing packages (#16)

* :sparkles: Draft workflow for building & publishing packages

* :construction: Workflow WIP

* :construction_worker: Update workflow

* :construction_worker: Update workflow

* :construction_worker: Update CI

* :construction_worker: Publish to github npm registry

* :construction_worker: Update workflow

* :construction_worker: Update workflow\

* :memo: Fix template

* :construction_worker: Remove dry-run

* :construction_worker: Publish packages for 0.0.1

* :construction_worker: Enable cross

* :construction_worker: Use cross for windows

* :package: Add parent package

* :construction_worker: Add Ring for windows build

* :construction_worker: Add ring to cli crates on windows

* :construction_worker: Add ring to cli crates on windows

* :construction_worker: Use no features for windows build

* :construction_worker: Remove dependencies step

* :construction_worker: Publish binaries to release

* :construction_worker: Set release tag

* :construction_worker: Update binary name

* :construction_worker: Improve artifact name

* :construction_worker: Remove dry run

* :memo: Update package.json template

* :memo: Update package template

* :memo: Update archs

* :memo: Use npm

* :construction_worker: Add repo checkout step

* :construction_worker: Add npm install step

* :construction_worker: Update workdlow file
Gabriele Picco 1 year ago
parent
commit
fc7301c66b

+ 172 - 0
.github/workflows/publish-packages.yml

@@ -0,0 +1,172 @@
+name: Publish bolt-cli packages
+on:
+  release:
+    types: [ published ]
+  workflow_dispatch:
+    inputs:
+      release_version:
+        description: 'The release version'
+        required: true
+        default: '0.0.1'
+
+jobs:
+  publish-npm-binaries:
+    name: Publish NPM packages
+    runs-on: ${{ matrix.build.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        build:
+          - {
+            NAME: linux-x64-glibc,
+            OS: ubuntu-20.04,
+            TOOLCHAIN: stable,
+            TARGET: x86_64-unknown-linux-gnu,
+          }
+          - {
+            NAME: linux-x86-glibc,
+            OS: ubuntu-22.04,
+            TOOLCHAIN: stable,
+            TARGET: i686-unknown-linux-gnu,
+          }
+          - {
+            NAME: linux-arm64-glibc,
+            OS: ubuntu-20.04,
+            TOOLCHAIN: stable,
+            TARGET: aarch64-unknown-linux-gnu,
+          }
+          - {
+            NAME: win32-x64-msvc,
+            OS: windows-2022,
+            TOOLCHAIN: stable,
+            TARGET: x86_64-pc-windows-msvc,
+          }
+          - {
+            NAME: win32-x86-msvc,
+            OS: windows-2022,
+            TOOLCHAIN: stable,
+            TARGET: i686-pc-windows-msvc,
+          }
+          - {
+            NAME: darwin-x64,
+            OS: macos-11,
+            TOOLCHAIN: stable,
+            TARGET: x86_64-apple-darwin,
+          }
+          - {
+            NAME: darwin-arm64,
+            OS: macos-11,
+            TOOLCHAIN: stable,
+            TARGET: aarch64-apple-darwin,
+          }
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+
+      - name: Set the release version
+        shell: bash
+        run: echo "RELEASE_VERSION=${github.event.inputs.release_version || GITHUB_REF:11}" >> $GITHUB_ENV
+
+      - name: Install Rust toolchain
+        uses: actions-rs/toolchain@v1
+        with:
+          toolchain: ${{ matrix.build.TOOLCHAIN }}
+          target: ${{ matrix.build.TARGET }}
+          override: true
+
+      - name: Build (linux/macos)
+        if: matrix.build.OS != 'windows-2022'
+        uses: actions-rs/cargo@v1
+        with:
+          use-cross: true
+          command: build
+          args: --manifest-path=cli/Cargo.toml --release --locked --target ${{ matrix.build.TARGET }}
+
+      - name: Build (windows)
+        if: matrix.build.OS == 'windows-2022'
+        uses: actions-rs/cargo@v1
+        with:
+          command: build
+          args: --manifest-path=cli/Cargo.toml --no-default-features --release --locked --target ${{ matrix.build.TARGET }}
+
+      - name: Build the NPM package
+        shell: bash
+        run: |
+          # set the binary name
+          bin="bolt"
+          # derive the OS and architecture from the build matrix name
+          # note: when split by a hyphen, first part is the OS and the second is the architecture
+          node_os=$(echo "${{ matrix.build.NAME }}" | cut -d '-' -f1)
+          export node_os
+          node_arch=$(echo "${{ matrix.build.NAME }}" | cut -d '-' -f2)
+          export node_arch
+          # set the version
+          export node_version="${{ env.RELEASE_VERSION }}"
+          # set the package name
+          # note: use 'windows' as OS name instead of 'win32'
+          if [ "${{ matrix.build.OS }}" = "windows-2022" ]; then
+            export node_pkg="${bin}-cli-windows-${node_arch}"
+          else
+            export node_pkg="${bin}-cli-${node_os}-${node_arch}"
+          fi
+          echo "node_pkg=${node_pkg}" >> $GITHUB_ENV
+          # create the package directory
+          mkdir -p "${node_pkg}/bin"
+          # generate package.json from the template
+          envsubst < cli/npm-package/package.json.tmpl > "${node_pkg}/package.json"
+          cat "${node_pkg}/package.json"
+          # copy the binary into the package
+          # note: windows binaries has '.exe' extension
+          if [ "${{ matrix.build.OS }}" = "windows-2022" ]; then
+            bin="${bin}.exe"
+          fi
+          echo "bin_name=${bin}" >> $GITHUB_ENV
+          cp "target/${{ matrix.build.TARGET }}/release/${bin}" "${node_pkg}/bin"
+          cp "target/${{ matrix.build.TARGET }}/release/${bin}" "${node_pkg}/bin"
+          
+          # Create the release bin file
+          release_name="bolt-${{ matrix.build.NAME }}"
+          if [ "${{ matrix.build.OS }}" = "windows-2022" ]; then
+            release_name="${release_name}.exe"
+          fi
+          echo "release_name=${release_name}" >> $GITHUB_ENV
+          mv "target/${{ matrix.build.TARGET }}/release/${bin}" "target/${{ matrix.build.TARGET }}/release/${release_name}"
+
+      - name: Publish binary to GitHub release
+        uses: svenstaro/upload-release-action@v2
+        with:
+          repo_token: ${{ secrets.GITHUB_TOKEN }}
+          file: target/${{ matrix.build.TARGET }}/release/${{ env.release_name }}
+          overwrite: true
+          tag: "v${{ env.RELEASE_VERSION }}"
+          release_name: "v${{ env.RELEASE_VERSION }}"
+          asset_name: "${{ env.release_name }}"
+
+      - name: Publish the NPM package
+        shell: bash
+        run: |
+          cd ${{ env.node_pkg }}
+          echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc
+          npm set //registry.npmjs.org/:_authToken ${{ secrets.NPM_TOKEN }}
+          npm publish --access public
+        env:
+          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
+  publish-wrapper-npm-package:
+    name: Publish wrapper NPM packages
+    runs-on: ubuntu-20.04
+    needs: publish-npm-binaries
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+      - name: Publish the NPM package
+        shell: bash
+        run: |
+          cd cli/npm-package
+          npm install
+          npm run build
+          cd lib
+          echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc
+          npm set //registry.npmjs.org/:_authToken ${{ secrets.NPM_TOKEN }}
+          npm publish --access public
+        env:
+          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

+ 6 - 0
.gitignore

@@ -16,4 +16,10 @@ tests/examples/target
 tests/examples/node_modules
 tests/examples/.yarn
 
+cli/npm-package/lib
+cli/npm-package/.npmrc
+cli/npm-package/.yarnrc
+cli/npm-package/package-lock.json
+.npmrc
+.yarnrc
 

+ 2 - 0
cli/npm-package/.eslintignore

@@ -0,0 +1,2 @@
+lib/**
+scripts/**

+ 15 - 0
cli/npm-package/.eslintrc

@@ -0,0 +1,15 @@
+{
+  "extends": [
+    "eslint:recommended",
+    "plugin:@typescript-eslint/recommended"
+  ],
+  "parser": "@typescript-eslint/parser",
+  "plugins": [
+    "@typescript-eslint"
+  ],
+  "ignorePatterns": [
+    "lib/**",
+    "scripts/**"
+  ],
+  "root": true
+}

+ 44 - 37
cli/npm-package/bolt → cli/npm-package/bolt.ts

@@ -1,85 +1,93 @@
 #!/usr/bin/env node
-const fs = require("fs");
-const { spawn, spawnSync } = require("child_process");
-const path = require("path");
-const { arch, platform } = require("os");
-const { version } = require("./package.json");
+import fs from "fs";
+import { spawn, spawnSync } from "child_process";
+import path from "path";
+import { arch, platform } from "os";
+import { version } from "./package.json";
 
 const PACKAGE_VERSION = `bolt-cli ${version}`;
-const PACKAGE_BOLT_PATH = path.join(__dirname, "bolt");
 
-function getBinaryVersion(location) {
+function getBinaryVersion(location: string): [string | null, string | null] {
   const result = spawnSync(location, ["--version"]);
-  const error =
+  const error: string | null =
     (result.error && result.error.toString()) ||
     (result.stderr.length > 0 && result.stderr.toString().trim()) ||
     null;
   return [error, result.stdout && result.stdout.toString().trim()];
 }
 
-function runBolt(location) {
+function getExePath(): string {
+  let os: string = platform();
+  let extension = "";
+  if (["win32", "cygwin"].includes(os)) {
+    os = "windows";
+    extension = ".exe";
+  }
+  const binaryName = `@magicblock-labs/bolt-cli-${os}-${arch()}/bin/bolt${extension}`;
+  try {
+    return require.resolve(binaryName);
+  } catch (e) {
+    throw new Error(
+      `Couldn't find application binary inside node_modules for ${os}-${arch()}`
+    );
+  }
+}
+
+function runBolt(location: string): void {
   const args = process.argv.slice(2);
   const bolt = spawn(location, args, { stdio: "inherit" });
-  bolt.on("exit", (code, signal) => {
+  bolt.on("exit", (code: number | null, signal: NodeJS.Signals | null) => {
     process.on("exit", () => {
       if (signal) {
         process.kill(process.pid, signal);
-      } else {
+      } else if (code !== null) {
         process.exit(code);
       }
     });
   });
 
-  process.on("SIGINT", function () {
+  process.on("SIGINT", () => {
     bolt.kill("SIGINT");
     bolt.kill("SIGTERM");
   });
 }
 
-function tryPackageBolt() {
-  if (arch() !== "x64" || platform() !== "linux") {
-    console.error(`Only x86_64 / Linux distributed in NPM package right now.`);
-    return false;
-  }
-
-  const [error, binaryVersion] = getBinaryVersion(PACKAGE_BOLT_PATH);
-  if (error !== null) {
-    console.error(`Failed to get version of local binary: ${error}`);
-    return false;
-  }
-  if (binaryVersion !== PACKAGE_VERSION) {
+function tryPackageBolt(): boolean {
+  try {
+    const path = getExePath();
+    runBolt(path);
+    return true;
+  } catch (e) {
     console.error(
-      `Package binary version is not correct. Expected "${PACKAGE_VERSION}", found "${binaryVersion}".`
+      "Failed to run bolt from package:",
+      e instanceof Error ? e.message : e
     );
     return false;
   }
-
-  runBolt(PACKAGE_BOLT_PATH);
-  return true;
 }
 
-function trySystemBolt() {
-  console.error("Trying globally installed bolt.");
-
-  const absolutePath = process.env.PATH.split(":")
+function trySystemBolt(): void {
+  const absolutePath = process.env.PATH?.split(path.delimiter)
     .filter((dir) => dir !== path.dirname(process.argv[1]))
     .find((dir) => {
       try {
         fs.accessSync(`${dir}/bolt`, fs.constants.X_OK);
+        return true;
       } catch {
         return false;
       }
-      return true;
     });
 
   if (!absolutePath) {
-    console.error(`Could not find globally installed bolt, install with cargo.`);
-    process.exit();
+    console.error(
+      `Could not find globally installed bolt, please install with cargo.`
+    );
+    process.exit(1);
   }
 
   const absoluteBinaryPath = `${absolutePath}/bolt`;
-
   const [error, binaryVersion] = getBinaryVersion(absoluteBinaryPath);
+
   if (error !== null) {
     console.error(`Failed to get version of global binary: ${error}`);
     return;
@@ -93,5 +101,4 @@ function trySystemBolt() {
 
   runBolt(absoluteBinaryPath);
 }
-
 tryPackageBolt() || trySystemBolt();

+ 22 - 2
cli/npm-package/package.json

@@ -12,10 +12,30 @@
   },
   "license": "MIT",
   "bin": {
-    "bolt": "./bolt.js"
+    "bolt": "bolt.js"
   },
   "scripts": {
-    "prepack": "[ \"$(uname -op)\" != \"x86_64 GNU/Linux\" ] && (echo Can be packed only on x86_64 GNU/Linux && exit 1) || ([ \"$(./bolt --version)\" != \"bolt-cli $(jq -r .version package.json)\" ] && (echo Check bolt binary version && exit 2) || exit 0)"
+    "typecheck": "tsc --noEmit",
+    "lint": "eslint .",
+    "lint:fix": "eslint . --fix",
+    "build": "tsc",
+    "dev": "yarn build && node lib/index.js"
+  },
+  "devDependencies": {
+    "@types/node": "^20.8.8",
+    "@typescript-eslint/eslint-plugin": "^5.48.0",
+    "@typescript-eslint/parser": "^5.48.0",
+    "eslint": "^8.31.0",
+    "typescript": "^4.9.4"
+  },
+  "optionalDependencies": {
+    "@magicblock-labs/bolt-cli-darwin-x64": "0.0.1",
+    "@magicblock-labs/bolt-cli-darwin-arm64": "0.0.1",
+    "@magicblock-labs/bolt-cli-linux-x86": "0.0.1",
+    "@magicblock-labs/bolt-cli-linux-x64": "0.0.1",
+    "@magicblock-labs/bolt-cli-linux-arm64": "0.0.1",
+    "@magicblock-labs/bolt-cli-windows-x86": "0.0.1",
+    "@magicblock-labs/bolt-cli-windows-x64": "0.0.1"
   },
   "publishConfig": {
     "access": "public"

+ 26 - 0
cli/npm-package/package.json.tmpl

@@ -0,0 +1,26 @@
+{
+  "name": "@magicblock-labs/${node_pkg}",
+  "description": "Bolt CLI tool (${node_pkg})",
+  "version": "${node_version}",
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/magicblock-labs/bolt.git"
+  },
+  "bugs": {
+    "url": "https://github.com/magicblock-labs/bolt/issues"
+  },
+  "license": "MIT",
+  "private": false,
+  "author": "Magicblock Labs <dev@magicblock.gg>",
+  "homepage": "https://www.magicblock.gg/",
+  "os": [
+    "${node_os}"
+  ],
+  "cpu": [
+    "${node_arch}"
+  ],
+  "publishConfig": {
+    "access": "public",
+    "registry": "https://registry.npmjs.org/"
+  }
+}

+ 13 - 0
cli/npm-package/tsconfig.json

@@ -0,0 +1,13 @@
+{
+  "compilerOptions": {
+    "target": "es2016",
+    "module": "commonjs",
+    "esModuleInterop": true,
+    "resolveJsonModule": true,
+    "baseUrl": "./",
+    "outDir": "lib",
+    "forceConsistentCasingInFileNames": true,
+    "strict": true,
+    "skipLibCheck": true,
+  }
+}