From 490c334f890f424afb818403d4316ecc65994d9e Mon Sep 17 00:00:00 2001 From: khooihongzhe Date: Thu, 14 May 2026 11:00:07 +0800 Subject: [PATCH] fix(npm): add npm to cg update methods, normalize repository.url - cg update now detects npm installs (path contains node_modules/@coingecko/cg-) and runs `npm install -g @coingecko/cg@latest`. npm check runs before homebrew so Homebrew-installed Node doesn't misclassify. - Add git+ prefix to repository.url in all 7 npm/*/package.json templates so npm stops auto-correcting on publish. - Document npm distribution + cg update method ordering in CLAUDE.md. --- CLAUDE.md | 2 ++ README.md | 4 ++-- cmd/update.go | 18 ++++++++++++++---- cmd/update_test.go | 20 +++++++++++++++++++- npm/cg-darwin-arm64/package.json | 2 +- npm/cg-darwin-x64/package.json | 2 +- npm/cg-linux-arm64/package.json | 2 +- npm/cg-linux-x64/package.json | 2 +- npm/cg-win32-arm64/package.json | 2 +- npm/cg-win32-x64/package.json | 2 +- npm/cg/package.json | 2 +- 11 files changed, 44 insertions(+), 14 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 17befb7..da5f549 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -113,6 +113,8 @@ coingecko-cli/ - **Tagging**: always tag from `main` after pulling latest — `git tag vX.Y.Z && git push origin vX.Y.Z` - **Install script**: `install.sh` downloads the latest release binary from GitHub Releases - **Go install**: `go install github.com/coingecko/coingecko-cli@latest` — produces a binary named `coingecko-cli` (module-path basename). Users alias or symlink it to `cg`; the `cg update --method go` flow prints this heads-up before running install. +- **npm**: `npm install -g @coingecko/cg` — umbrella package in `npm/cg/` plus 6 platform sub-packages (`npm/cg--/`) published via OIDC Trusted Publishing from `release.yml`. Each package needs its trusted publisher configured on npmjs.com (Repository: `coingecko/coingecko-cli`, Workflow: `release.yml`) before its first publish. Publish flow lives in `scripts/npm-publish.sh`. +- **`cg update` install methods**: auto-detects `homebrew`, `npm`, `go`, or `script` from the executable path. npm detection (`node_modules/@coingecko/cg-*`) runs before homebrew because Homebrew-installed Node puts npm globals under `/opt/homebrew/lib/node_modules/`. ## Key Design Decisions diff --git a/README.md b/README.md index 463fc9b..a022fde 100644 --- a/README.md +++ b/README.md @@ -355,13 +355,13 @@ cg watch --ids bitcoin --dry-run # Show WebSocket request info ### `cg update` — Upgrade the CLI -Check for a new version and upgrade in one step. Auto-detects whether you installed via Homebrew, `go install`, or the install script, and hands off to the right tool. +Check for a new version and upgrade in one step. Auto-detects whether you installed via Homebrew, npm, `go install`, or the install script, and hands off to the right tool. ```sh cg update # Override install method if auto-detection gets it wrong -cg update --method homebrew # or: go, script +cg update --method homebrew # or: npm, go, script ``` The CLI also checks for updates on launch (cached 24h) and shows a reminder if you're behind. Set `CG_NO_UPDATE_CHECK=1` to disable this in CI. diff --git a/cmd/update.go b/cmd/update.go index fbd1922..e7dda04 100644 --- a/cmd/update.go +++ b/cmd/update.go @@ -22,7 +22,7 @@ var updateCmd = &cobra.Command{ } func init() { - updateCmd.Flags().String("method", "", "Install method override (homebrew, go, script)") + updateCmd.Flags().String("method", "", "Install method override (homebrew, npm, go, script)") rootCmd.AddCommand(updateCmd) } @@ -36,9 +36,9 @@ func runUpdate(cmd *cobra.Command, args []string) error { method = detectInstallMethod() } else { switch method { - case "homebrew", "go", "script": + case "homebrew", "npm", "go", "script": default: - return fmt.Errorf("unknown install method %q — must be one of: homebrew, go, script", method) + return fmt.Errorf("unknown install method %q, must be one of: homebrew, npm, go, script", method) } } @@ -92,9 +92,16 @@ func detectInstallMethod() string { return classifyInstallPath(exe) } -// classifyInstallPath returns the install method ("homebrew", "go", or "script") +// classifyInstallPath returns the install method ("homebrew", "npm", "go", or "script") // for a resolved executable path. func classifyInstallPath(exe string) string { + // npm check runs before homebrew because Homebrew-installed Node puts npm globals + // under /opt/homebrew/lib/node_modules/, which would otherwise match the homebrew rule. + // filepath.ToSlash is a no-op off Windows, so normalize backslashes explicitly. + if strings.Contains(strings.ReplaceAll(exe, `\`, "/"), "node_modules/@coingecko/cg-") { + return "npm" + } + if strings.Contains(exe, "/Cellar/") || strings.Contains(exe, "/homebrew/") || strings.Contains(exe, "/opt/homebrew/") { @@ -124,6 +131,9 @@ func runInstallCommand(method string) error { case "homebrew": name = "brew" args = []string{"upgrade", "coingecko/coingecko-cli/cg"} + case "npm": + name = "npm" + args = []string{"install", "-g", "@coingecko/cg@latest"} case "go": warnf("Note: 'go install' produces a binary named 'coingecko-cli'. If you invoke this CLI as 'cg', make sure you have an alias or symlink (e.g. alias cg=coingecko-cli).\n\n") name = "go" diff --git a/cmd/update_test.go b/cmd/update_test.go index 84e3ad2..a6e8700 100644 --- a/cmd/update_test.go +++ b/cmd/update_test.go @@ -131,6 +131,24 @@ func TestClassifyInstallPath_Go_ExplicitGOPATH(t *testing.T) { assert.Equal(t, "go", classifyInstallPath(exe)) } +func TestClassifyInstallPath_Npm(t *testing.T) { + t.Setenv("GOBIN", "") + t.Setenv("GOPATH", "") + + cases := []struct { + path string + desc string + }{ + {"/usr/local/lib/node_modules/@coingecko/cg-darwin-arm64/cg", "macOS npm global"}, + {"/home/user/.npm-global/lib/node_modules/@coingecko/cg-linux-x64/cg", "Linux user npm prefix"}, + {"/opt/homebrew/lib/node_modules/@coingecko/cg-darwin-arm64/cg", "Homebrew-node npm prefix"}, + {`C:\Users\runner\AppData\Roaming\npm\node_modules\@coingecko\cg-win32-x64\cg.exe`, "Windows npm global"}, + } + for _, tc := range cases { + assert.Equal(t, "npm", classifyInstallPath(tc.path), tc.desc) + } +} + func TestClassifyInstallPath_Script(t *testing.T) { t.Setenv("GOBIN", "") t.Setenv("GOPATH", "") @@ -156,7 +174,7 @@ func TestClassifyInstallPath_GoBinNotParentDir(t *testing.T) { func TestDetectInstallMethod_ReturnsValidMethod(t *testing.T) { method := detectInstallMethod() - assert.Contains(t, []string{"homebrew", "go", "script"}, method) + assert.Contains(t, []string{"homebrew", "npm", "go", "script"}, method) } func TestRunUpdate_FetchError(t *testing.T) { diff --git a/npm/cg-darwin-arm64/package.json b/npm/cg-darwin-arm64/package.json index 4f24547..1d69803 100644 --- a/npm/cg-darwin-arm64/package.json +++ b/npm/cg-darwin-arm64/package.json @@ -4,7 +4,7 @@ "description": "CoinGecko CLI - Real Time & Historical Crypto Data (macOS ARM64)", "repository": { "type": "git", - "url": "https://github.com/coingecko/coingecko-cli.git" + "url": "git+https://github.com/coingecko/coingecko-cli.git" }, "homepage": "https://github.com/coingecko/coingecko-cli", "bugs": { diff --git a/npm/cg-darwin-x64/package.json b/npm/cg-darwin-x64/package.json index aec1151..f66453b 100644 --- a/npm/cg-darwin-x64/package.json +++ b/npm/cg-darwin-x64/package.json @@ -4,7 +4,7 @@ "description": "CoinGecko CLI - Real Time & Historical Crypto Data (macOS x64)", "repository": { "type": "git", - "url": "https://github.com/coingecko/coingecko-cli.git" + "url": "git+https://github.com/coingecko/coingecko-cli.git" }, "homepage": "https://github.com/coingecko/coingecko-cli", "bugs": { diff --git a/npm/cg-linux-arm64/package.json b/npm/cg-linux-arm64/package.json index a3d3a21..fd76bd1 100644 --- a/npm/cg-linux-arm64/package.json +++ b/npm/cg-linux-arm64/package.json @@ -4,7 +4,7 @@ "description": "CoinGecko CLI - Real Time & Historical Crypto Data (Linux ARM64)", "repository": { "type": "git", - "url": "https://github.com/coingecko/coingecko-cli.git" + "url": "git+https://github.com/coingecko/coingecko-cli.git" }, "homepage": "https://github.com/coingecko/coingecko-cli", "bugs": { diff --git a/npm/cg-linux-x64/package.json b/npm/cg-linux-x64/package.json index 27e7bee..8dda347 100644 --- a/npm/cg-linux-x64/package.json +++ b/npm/cg-linux-x64/package.json @@ -4,7 +4,7 @@ "description": "CoinGecko CLI - Real Time & Historical Crypto Data (Linux x64)", "repository": { "type": "git", - "url": "https://github.com/coingecko/coingecko-cli.git" + "url": "git+https://github.com/coingecko/coingecko-cli.git" }, "homepage": "https://github.com/coingecko/coingecko-cli", "bugs": { diff --git a/npm/cg-win32-arm64/package.json b/npm/cg-win32-arm64/package.json index 73c069f..1ffbb4b 100644 --- a/npm/cg-win32-arm64/package.json +++ b/npm/cg-win32-arm64/package.json @@ -4,7 +4,7 @@ "description": "CoinGecko CLI - Real Time & Historical Crypto Data (Windows ARM64)", "repository": { "type": "git", - "url": "https://github.com/coingecko/coingecko-cli.git" + "url": "git+https://github.com/coingecko/coingecko-cli.git" }, "homepage": "https://github.com/coingecko/coingecko-cli", "bugs": { diff --git a/npm/cg-win32-x64/package.json b/npm/cg-win32-x64/package.json index c3a5c31..46c464d 100644 --- a/npm/cg-win32-x64/package.json +++ b/npm/cg-win32-x64/package.json @@ -4,7 +4,7 @@ "description": "CoinGecko CLI - Real Time & Historical Crypto Data (Windows x64)", "repository": { "type": "git", - "url": "https://github.com/coingecko/coingecko-cli.git" + "url": "git+https://github.com/coingecko/coingecko-cli.git" }, "homepage": "https://github.com/coingecko/coingecko-cli", "bugs": { diff --git a/npm/cg/package.json b/npm/cg/package.json index 9159c37..2437753 100644 --- a/npm/cg/package.json +++ b/npm/cg/package.json @@ -4,7 +4,7 @@ "description": "CoinGecko CLI - Real Time & Historical Crypto Data", "repository": { "type": "git", - "url": "https://github.com/coingecko/coingecko-cli.git" + "url": "git+https://github.com/coingecko/coingecko-cli.git" }, "homepage": "https://github.com/coingecko/coingecko-cli", "bugs": {