-
Notifications
You must be signed in to change notification settings - Fork 1.4k
chore: remove cargo-dist, use native fetch for npm installer #646
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
2510bc7
chore: remove cargo-dist, use native fetch for npm installer
jpoehnelt-bot 335faca
fix: harden npm scripts — spawnSync, signal handling, binary-exists c…
jpoehnelt-bot 21f6e5d
fix: address PR review comments
jpoehnelt-bot 45e49b1
fix: use flat archives for consistent tar/zip extraction
jpoehnelt-bot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| --- | ||
| "@googleworkspace/cli": patch | ||
| --- | ||
|
|
||
| Remove cargo-dist; use native Node.js fetch for npm binary installer | ||
|
|
||
| Replaces the cargo-dist generated release pipeline and npm package with: | ||
| - A custom GitHub Actions release workflow with matrix cross-compilation | ||
| - A zero-dependency npm installer using native `fetch()` (Node 18+) | ||
| - Removes axios, rimraf, detect-libc, console.table, and axios-proxy-builder dependencies from the published npm package |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| # Downloaded binary (created during npm postinstall) | ||
| bin/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,152 @@ | ||
| #!/usr/bin/env node | ||
|
|
||
| "use strict"; | ||
|
|
||
| const fs = require("fs"); | ||
| const path = require("path"); | ||
| const os = require("os"); | ||
| const { pipeline } = require("stream/promises"); | ||
| const { createWriteStream, mkdirSync, rmSync } = require("fs"); | ||
| const { spawnSync } = require("child_process"); | ||
| const { getPlatform } = require("./platform"); | ||
|
|
||
| const INSTALL_DIR = path.join(__dirname, "bin"); | ||
|
|
||
| /** | ||
| * Get the GitHub release download URL base for the current package version. | ||
| */ | ||
| function getDownloadUrl(artifactName) { | ||
| const { version } = require("./package.json"); | ||
| return `https://github.com/googleworkspace/cli/releases/download/v${version}/${artifactName}`; | ||
| } | ||
|
|
||
| /** | ||
| * Strip ANSI escape sequences from a string. | ||
| */ | ||
| function sanitize(str) { | ||
| // eslint-disable-next-line no-control-regex | ||
| return String(str).replace(/\x1b\[[0-9;]*[a-zA-Z]/g, ""); | ||
| } | ||
|
|
||
| /** | ||
| * Download a file using native fetch (Node 18+). | ||
| * | ||
| * NOTE: Native fetch does not respect HTTP_PROXY / HTTPS_PROXY environment | ||
| * variables. If proxy support is needed, consider using the `undici` ProxyAgent | ||
| * or a Node.js build with proxy support. | ||
|
jpoehnelt marked this conversation as resolved.
|
||
| */ | ||
| async function download(url, dest) { | ||
| const res = await fetch(url, { redirect: "follow" }); | ||
|
jpoehnelt marked this conversation as resolved.
jpoehnelt marked this conversation as resolved.
|
||
|
|
||
| if (!res.ok) { | ||
| throw new Error(`Failed to download ${url}: ${res.status} ${res.statusText}`); | ||
| } | ||
|
|
||
| if (!res.body) { | ||
| throw new Error(`Failed to download ${url}: Response body is empty`); | ||
| } | ||
|
|
||
| const fileStream = createWriteStream(dest); | ||
| // Convert web ReadableStream to Node stream and pipe | ||
| const { Readable } = require("stream"); | ||
| const nodeStream = Readable.fromWeb(res.body); | ||
| await pipeline(nodeStream, fileStream); | ||
|
jpoehnelt marked this conversation as resolved.
|
||
| } | ||
|
|
||
| /** | ||
| * Run a command and throw on failure. | ||
| */ | ||
| function run(cmd, args) { | ||
| const result = spawnSync(cmd, args, { stdio: "pipe" }); | ||
| if (result.error) { | ||
| throw new Error(`Failed to run ${cmd}: ${result.error.message}`); | ||
| } | ||
| if ((result.status ?? 1) !== 0) { | ||
| const stderr = result.stderr ? result.stderr.toString() : ""; | ||
| throw new Error( | ||
| `Command failed: ${cmd} ${args.join(" ")}\n${stderr}`, | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Extract the archive to the install directory. | ||
| */ | ||
| function extract(archivePath, destDir) { | ||
| const isZip = archivePath.endsWith(".zip"); | ||
| const isTar = archivePath.includes(".tar."); | ||
|
|
||
| if (isTar) { | ||
| run("tar", ["xf", archivePath, "-C", destDir]); | ||
| } else if (isZip) { | ||
| if (process.platform === "win32") { | ||
| // Use single-quoted PowerShell strings with doubled single-quote escaping | ||
| // to safely handle paths containing spaces and special characters. | ||
| const psArchive = archivePath.replace(/'/g, "''"); | ||
| const psDest = destDir.replace(/'/g, "''"); | ||
| run("powershell.exe", [ | ||
| "-NoProfile", | ||
| "-NonInteractive", | ||
| "-Command", | ||
| `Expand-Archive -LiteralPath '${psArchive}' -DestinationPath '${psDest}' -Force`, | ||
| ]); | ||
|
jpoehnelt marked this conversation as resolved.
|
||
| } else { | ||
| run("unzip", ["-q", "-o", archivePath, "-d", destDir]); | ||
| } | ||
|
jpoehnelt marked this conversation as resolved.
|
||
| } else { | ||
| throw new Error(`Unsupported archive format: ${archivePath}`); | ||
| } | ||
| } | ||
|
|
||
| async function install() { | ||
| const platform = getPlatform(); | ||
| const { version } = require("./package.json"); | ||
| const url = getDownloadUrl(platform.artifact); | ||
|
|
||
| // Check if the correct version is already installed | ||
| const binPath = path.join(INSTALL_DIR, platform.binary); | ||
| const versionFile = path.join(INSTALL_DIR, ".version"); | ||
| if (fs.existsSync(binPath) && fs.existsSync(versionFile)) { | ||
| const installed = fs.readFileSync(versionFile, "utf8").trim(); | ||
| if (installed === version) { | ||
| console.error(`gws v${version} is already installed, skipping.`); | ||
| return; | ||
| } | ||
| console.error(`Upgrading gws from v${installed} to v${version}`); | ||
| } | ||
|
|
||
| // Clean and create install directory | ||
| if (fs.existsSync(INSTALL_DIR)) { | ||
| rmSync(INSTALL_DIR, { recursive: true, force: true }); | ||
| } | ||
| mkdirSync(INSTALL_DIR, { recursive: true }); | ||
|
|
||
| // Download to a temp file | ||
| const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "gws-")); | ||
| const archiveName = path.basename(platform.artifact); | ||
| const tmpFile = path.join(tmpDir, archiveName); | ||
|
|
||
| try { | ||
| console.error(`Downloading gws from ${url}`); | ||
| await download(url, tmpFile); | ||
|
|
||
| console.error(`Extracting to ${INSTALL_DIR}`); | ||
| extract(tmpFile, INSTALL_DIR); | ||
|
|
||
| // Make binary executable on Unix | ||
| if (process.platform !== "win32") { | ||
| fs.chmodSync(binPath, 0o755); | ||
| } | ||
|
|
||
| console.error(`gws v${version} has been installed!`); | ||
| fs.writeFileSync(versionFile, version); | ||
| } finally { | ||
| // Clean up temp files | ||
| rmSync(tmpDir, { recursive: true, force: true }); | ||
| } | ||
| } | ||
|
|
||
| install().catch((err) => { | ||
| console.error(`Error installing gws: ${sanitize(err.message)}`); | ||
| process.exit(1); | ||
| }); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| { | ||
| "name": "@googleworkspace/cli", | ||
| "description": "Google Workspace CLI — dynamic command surface from Discovery Service", | ||
| "version": "0.22.3", | ||
| "license": "Apache-2.0", | ||
| "author": "Justin Poehnelt", | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "https://github.com/googleworkspace/cli.git" | ||
| }, | ||
| "homepage": "https://github.com/googleworkspace/cli", | ||
| "bugs": { | ||
| "url": "https://github.com/googleworkspace/cli/issues" | ||
| }, | ||
| "bin": { | ||
| "gws": "run.js" | ||
| }, | ||
| "scripts": { | ||
| "postinstall": "node install.js" | ||
| }, | ||
| "engines": { | ||
| "node": ">=18" | ||
| }, | ||
| "preferUnplugged": true, | ||
| "keywords": [ | ||
| "cli", | ||
| "google-workspace", | ||
| "google", | ||
| "google-api", | ||
| "google-drive", | ||
| "google-gmail", | ||
| "google-sheets", | ||
| "google-calendar", | ||
| "google-docs", | ||
| "google-chat", | ||
| "google-admin", | ||
| "gsuite", | ||
| "discovery-api", | ||
| "ai-agent", | ||
| "agent-skills", | ||
| "automation", | ||
| "oauth2", | ||
| "rust" | ||
| ], | ||
| "publishConfig": { | ||
| "provenance": true, | ||
| "registry": "https://wombat-dressing-room.appspot.com" | ||
| }, | ||
| "supportedPlatforms": { | ||
| "aarch64-apple-darwin": { | ||
| "artifact": "google-workspace-cli-aarch64-apple-darwin.tar.gz", | ||
| "binary": "gws" | ||
| }, | ||
| "x86_64-apple-darwin": { | ||
| "artifact": "google-workspace-cli-x86_64-apple-darwin.tar.gz", | ||
| "binary": "gws" | ||
| }, | ||
| "aarch64-unknown-linux-gnu": { | ||
| "artifact": "google-workspace-cli-aarch64-unknown-linux-gnu.tar.gz", | ||
| "binary": "gws" | ||
| }, | ||
| "aarch64-unknown-linux-musl": { | ||
| "artifact": "google-workspace-cli-aarch64-unknown-linux-musl.tar.gz", | ||
| "binary": "gws" | ||
| }, | ||
| "x86_64-unknown-linux-gnu": { | ||
| "artifact": "google-workspace-cli-x86_64-unknown-linux-gnu.tar.gz", | ||
| "binary": "gws" | ||
| }, | ||
| "x86_64-unknown-linux-musl": { | ||
| "artifact": "google-workspace-cli-x86_64-unknown-linux-musl.tar.gz", | ||
| "binary": "gws" | ||
| }, | ||
| "x86_64-pc-windows-msvc": { | ||
| "artifact": "google-workspace-cli-x86_64-pc-windows-msvc.zip", | ||
| "binary": "gws.exe" | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| #!/usr/bin/env node | ||
|
|
||
| "use strict"; | ||
|
|
||
| const os = require("os"); | ||
| const path = require("path"); | ||
| const fs = require("fs"); | ||
| const { spawnSync } = require("child_process"); | ||
|
|
||
| const { supportedPlatforms } = require("./package.json"); | ||
|
|
||
| /** | ||
| * Map Node.js os.type() and os.arch() to Rust-style target triples. | ||
| */ | ||
| function getPlatformKey() { | ||
| const rawOs = os.type(); | ||
| const rawArch = os.arch(); | ||
|
|
||
| let osType; | ||
| switch (rawOs) { | ||
| case "Windows_NT": | ||
| osType = "pc-windows-msvc"; | ||
| break; | ||
| case "Darwin": | ||
| osType = "apple-darwin"; | ||
| break; | ||
| case "Linux": | ||
| osType = "unknown-linux-gnu"; | ||
| break; | ||
| default: | ||
| throw new Error(`Unsupported operating system: ${rawOs}`); | ||
| } | ||
|
|
||
| let arch; | ||
| switch (rawArch) { | ||
| case "x64": | ||
| arch = "x86_64"; | ||
| break; | ||
| case "arm64": | ||
| arch = "aarch64"; | ||
| break; | ||
| default: | ||
| throw new Error(`Unsupported architecture: ${rawArch}`); | ||
| } | ||
|
|
||
| // On Linux, try to detect musl libc | ||
| if (rawOs === "Linux") { | ||
| try { | ||
| const result = spawnSync("ldd", ["--version"], { | ||
| encoding: "utf8", | ||
| stdio: ["pipe", "pipe", "pipe"], | ||
| }); | ||
| // musl ldd prints version info to stderr | ||
| const output = (result.stdout || "") + (result.stderr || ""); | ||
| if (output.toLowerCase().includes("musl")) { | ||
| osType = "unknown-linux-musl"; | ||
| } | ||
| } catch { | ||
| // If ldd fails, assume glibc | ||
| } | ||
| } | ||
|
|
||
| const key = `${arch}-${osType}`; | ||
|
|
||
| if (!supportedPlatforms[key]) { | ||
| // Try musl fallback on Linux if glibc binary is not available | ||
| if (rawOs === "Linux") { | ||
| const muslKey = `${arch}-unknown-linux-musl`; | ||
| if (supportedPlatforms[muslKey]) { | ||
| return muslKey; | ||
| } | ||
| } | ||
| throw new Error( | ||
| `Unsupported platform: ${key}\nSupported platforms: ${Object.keys(supportedPlatforms).join(", ")}`, | ||
| ); | ||
| } | ||
|
|
||
| return key; | ||
| } | ||
|
|
||
| function getPlatform() { | ||
| const key = getPlatformKey(); | ||
| return supportedPlatforms[key]; | ||
| } | ||
|
|
||
| module.exports = { getPlatform, getPlatformKey }; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.