├── bin └── .gitkeep ├── install.js ├── .gitignore ├── bun.lockb ├── tsconfig.json ├── package.json ├── .github └── workflows │ └── release.yml ├── README.md └── install.ts /bin/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /install.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upstash/qstash-cli/master/bun.lockb -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["install.ts"], 3 | "compilerOptions": { 4 | "target": "es2016", 5 | "module": "commonjs", 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "strict": true, 9 | "skipLibCheck": true, 10 | "resolveJsonModule": true 11 | } 12 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@upstash/qstash-cli", 3 | "version": "2.20.9", 4 | "description": "Offical CLI tool for QStash", 5 | "main": "index.js", 6 | "scripts": { 7 | "prepublishOnly": "touch bin/qstash", 8 | "postinstall": "node install.js", 9 | "build": "tsc" 10 | }, 11 | "files": [ 12 | "bin", 13 | "install.js" 14 | ], 15 | "bin": { 16 | "@upstash/qstash-cli": "./bin/qstash", 17 | "qstash-cli": "./bin/qstash", 18 | "qstash": "./bin/qstash" 19 | }, 20 | "author": "", 21 | "license": "ISC", 22 | "devDependencies": { 23 | "@types/node": "^22.10.2", 24 | "@types/node-fetch": "^2.6.12", 25 | "@types/tar": "^6.1.1", 26 | "@types/unzipper": "^0.10.10" 27 | }, 28 | "dependencies": { 29 | "node-fetch": "2.6.2", 30 | "tar": "^6.1.1", 31 | "unzipper": "^0.12.3" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | release: 5 | types: 6 | - published 7 | 8 | jobs: 9 | release: 10 | name: Release 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout Repo 14 | uses: actions/checkout@v3 15 | 16 | - name: Set env 17 | run: echo "VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV 18 | 19 | - name: Setup Bun 20 | uses: oven-sh/setup-bun@v1 21 | with: 22 | bun-version: latest 23 | 24 | - name: Set package version 25 | run: echo $(jq --arg v "${{ env.VERSION }}" '(.version) = $v' package.json) > package.json 26 | 27 | - name: Install Dependencies 28 | run: bun install 29 | 30 | - name: Build 31 | run: bun run build 32 | 33 | - name: Set NPM_TOKEN 34 | run: npm set "//registry.npmjs.org/:_authToken" ${{ secrets.NPM_TOKEN }} 35 | 36 | - name: Publish 37 | if: "!github.event.release.prerelease" 38 | run: | 39 | npm publish --access public 40 | 41 | - name: Publish release candidate 42 | if: "github.event.release.prerelease" 43 | run: | 44 | npm publish --access public --tag=canary -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Upstash QStash CLI 2 | 3 | ![npm (scoped)](https://img.shields.io/npm/v/@upstash/qstash-cli) 4 | 5 | > [!NOTE] 6 | > **This project is in GA Stage.** 7 | > The Upstash Professional Support fully covers this project. It receives regular updates, and bug fixes. 8 | > The Upstash team is committed to maintaining and improving its functionality. 9 | 10 | QStash CLI is a command-line tool that helps developers work with QStash locally. The only command currently available is `dev`, which runs a local QStash server for development and testing purposes. 11 | 12 | **QStash** is an HTTP based messaging and scheduling solution for serverless and 13 | edge runtimes. 14 | 15 | It is 100% built on stateless HTTP requests and designed for: 16 | 17 | - Serverless functions (AWS Lambda ...) 18 | - Cloudflare Workers (see 19 | [the example](https://github.com/upstash/sdk-qstash-ts/tree/main/examples/cloudflare-workers)) 20 | - Fastly Compute@Edge 21 | - Next.js, including [edge](https://nextjs.org/docs/api-reference/edge-runtime) 22 | - Deno 23 | - Client side web/mobile applications 24 | - WebAssembly 25 | - and other environments where HTTP is preferred over TCP. 26 | 27 | ## Quick Start 28 | 29 | ### Install 30 | 31 | ```bash 32 | npm install @upstash/qstash-cli 33 | ``` 34 | 35 | ## Basic Usage: 36 | 37 | ```bash 38 | npx @upstash/qstash-cli dev 39 | ``` 40 | 41 | ### Available Commands 42 | 43 | ```bash 44 | Usage: 45 | qstash-cli [command] [options] 46 | Commands: 47 | dev Start a local dev server 48 | Options: 49 | -port The port number to start server on (default: 8080) 50 | ``` 51 | 52 | 53 | ## Docs 54 | 55 | See [the local development guide](https://docs.upstash.com/qstash/howto/local-development) for details. 56 | -------------------------------------------------------------------------------- /install.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import * as path from 'path'; 3 | import * as os from 'os'; 4 | import tar from "tar"; 5 | import fetch from "node-fetch"; 6 | import * as unzipper from 'unzipper'; 7 | import PJ from "./package.json"; 8 | 9 | interface BinaryConfig { 10 | arch: 'arm64' | 'amd64'; 11 | platform: 'darwin' | 'linux' | 'windows'; 12 | extension: '.tar.gz' | '.zip'; 13 | baseUrl: string; 14 | } 15 | 16 | const platformMap: Partial> = { 17 | linux: "linux", 18 | darwin: "darwin", 19 | win32: "windows" 20 | }; 21 | 22 | const archMap: Partial> = { 23 | arm64: "arm64", 24 | x64: "amd64", 25 | }; 26 | 27 | const extensionMap: Partial> = { 28 | linux: ".tar.gz", 29 | darwin: ".tar.gz", 30 | win32: ".zip", 31 | }; 32 | 33 | class BinaryDownloader { 34 | private config: BinaryConfig; 35 | 36 | constructor(config: BinaryConfig) { 37 | this.config = config 38 | } 39 | 40 | private URL(): string { 41 | const { arch, platform, baseUrl, extension } = this.config; 42 | let version = PJ.version.trim() 43 | return `${baseUrl}/${version}/qstash-server_${version}_${platform}_${arch}${extension}`; 44 | } 45 | 46 | public async download(): Promise { 47 | return new Promise((resolve, reject) => { 48 | const url = this.URL(); 49 | fetch(url).then((res) => { 50 | if (res.status !== 200) { 51 | throw new Error(`Error downloading binary; invalid response status code: ${res.status}`); 52 | } 53 | if (!res.body) { 54 | return reject(new Error("No body to pipe")); 55 | } 56 | resolve(res.body); 57 | }).catch(reject); 58 | }); 59 | } 60 | 61 | public async extract(stream: NodeJS.ReadableStream): Promise { 62 | return new Promise((resolve, reject) => { 63 | const bin = path.resolve("./bin"); 64 | switch (this.config.extension) { 65 | case ".tar.gz": 66 | const untar = tar.extract({ cwd: bin }); 67 | stream 68 | .pipe(untar) 69 | .on('close', () => resolve()) 70 | .on('error', reject) 71 | break; 72 | case ".zip": 73 | stream 74 | .pipe(unzipper.Extract({ path: bin })) 75 | .on('close', () => resolve()) 76 | .on('error', reject); 77 | } 78 | }) 79 | } 80 | } 81 | 82 | function getSysInfo(): { arch: BinaryConfig['arch'], platform: BinaryConfig['platform'], extension: BinaryConfig['extension'] } { 83 | const arch = archMap[process.arch] 84 | const platform = platformMap[process.platform] 85 | const extension = extensionMap[process.platform] 86 | 87 | if (!platform) { 88 | throw new Error(`Unsupported platform: ${process.platform}`); 89 | } 90 | 91 | if (!arch) { 92 | throw new Error(`Unsupported architecture: ${process.arch}`); 93 | } 94 | 95 | if (!extension) { 96 | throw new Error(`Unsupported extension: ${process.platform}`); 97 | } 98 | 99 | return { arch, platform, extension }; 100 | } 101 | 102 | (async () => { 103 | try { 104 | const { arch, platform, extension } = getSysInfo(); 105 | 106 | const downloader = new BinaryDownloader({ 107 | arch, 108 | platform, 109 | extension, 110 | baseUrl: 'https://artifacts.upstash.com/qstash/versions' 111 | }); 112 | const stream = await downloader.download(); 113 | await downloader.extract(stream); 114 | } catch (error) { 115 | console.error(error); 116 | process.exit(1); 117 | } 118 | })(); 119 | 120 | 121 | --------------------------------------------------------------------------------