@fredlackey/devutils 0.0.18 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +214 -141
- package/package.json +8 -83
- package/src/api/loader.js +229 -0
- package/src/api/registry.json +62 -0
- package/src/cli.js +293 -60
- package/src/commands/ai/index.js +16 -0
- package/src/commands/ai/launch.js +112 -0
- package/src/commands/ai/list.js +54 -0
- package/src/commands/ai/resume.js +70 -0
- package/src/commands/ai/sessions.js +121 -0
- package/src/commands/ai/set.js +131 -0
- package/src/commands/ai/show.js +74 -0
- package/src/commands/ai/tools.js +46 -0
- package/src/commands/alias/add.js +93 -0
- package/src/commands/alias/helpers.js +107 -0
- package/src/commands/alias/index.js +14 -0
- package/src/commands/alias/list.js +55 -0
- package/src/commands/alias/remove.js +62 -0
- package/src/commands/alias/sync.js +109 -0
- package/src/commands/api/disable.js +73 -0
- package/src/commands/api/enable.js +148 -0
- package/src/commands/api/index.js +15 -0
- package/src/commands/api/list.js +66 -0
- package/src/commands/api/update.js +87 -0
- package/src/commands/auth/index.js +15 -0
- package/src/commands/auth/list.js +49 -0
- package/src/commands/auth/login.js +384 -0
- package/src/commands/auth/logout.js +111 -0
- package/src/commands/auth/refresh.js +184 -0
- package/src/commands/auth/services.js +169 -0
- package/src/commands/auth/status.js +104 -0
- package/src/commands/config/export.js +224 -0
- package/src/commands/config/get.js +52 -0
- package/src/commands/config/import.js +308 -0
- package/src/commands/config/index.js +17 -0
- package/src/commands/config/init.js +143 -0
- package/src/commands/config/reset.js +57 -0
- package/src/commands/config/set.js +93 -0
- package/src/commands/config/show.js +35 -0
- package/src/commands/help.js +338 -0
- package/src/commands/identity/add.js +133 -0
- package/src/commands/identity/index.js +17 -0
- package/src/commands/identity/link.js +76 -0
- package/src/commands/identity/list.js +48 -0
- package/src/commands/identity/remove.js +72 -0
- package/src/commands/identity/show.js +65 -0
- package/src/commands/identity/sync.js +172 -0
- package/src/commands/identity/unlink.js +57 -0
- package/src/commands/ignore/add.js +165 -0
- package/src/commands/ignore/index.js +14 -0
- package/src/commands/ignore/list.js +89 -0
- package/src/commands/ignore/markers.js +43 -0
- package/src/commands/ignore/remove.js +164 -0
- package/src/commands/ignore/show.js +169 -0
- package/src/commands/machine/detect.js +122 -0
- package/src/commands/machine/index.js +14 -0
- package/src/commands/machine/list.js +74 -0
- package/src/commands/machine/set.js +106 -0
- package/src/commands/machine/show.js +35 -0
- package/src/commands/schema.js +152 -0
- package/src/commands/search/collections.js +134 -0
- package/src/commands/search/get.js +71 -0
- package/src/commands/search/index-cmd.js +54 -0
- package/src/commands/search/index.js +21 -0
- package/src/commands/search/keyword.js +60 -0
- package/src/commands/search/qmd.js +70 -0
- package/src/commands/search/query.js +64 -0
- package/src/commands/search/semantic.js +62 -0
- package/src/commands/search/status.js +46 -0
- package/src/commands/status.js +224 -171
- package/src/commands/tools/check.js +79 -0
- package/src/commands/tools/index.js +14 -0
- package/src/commands/tools/install.js +110 -0
- package/src/commands/tools/list.js +91 -0
- package/src/commands/tools/search.js +60 -0
- package/src/commands/update.js +83 -112
- package/src/commands/util/add.js +151 -0
- package/src/commands/util/index.js +15 -0
- package/src/commands/util/list.js +97 -0
- package/src/commands/util/remove.js +76 -0
- package/src/commands/util/run.js +79 -0
- package/src/commands/util/show.js +67 -0
- package/src/commands/version.js +21 -88
- package/src/installers/_template.js +104 -0
- package/src/installers/git.js +150 -0
- package/src/installers/homebrew.js +190 -0
- package/src/installers/node.js +223 -0
- package/src/installers/registry.json +29 -0
- package/src/lib/config.js +125 -0
- package/src/lib/detect.js +74 -0
- package/src/lib/errors.js +114 -0
- package/src/lib/github.js +315 -0
- package/src/lib/installer.js +225 -0
- package/src/lib/output.js +239 -0
- package/src/lib/platform.js +112 -0
- package/src/lib/platforms/amazon-linux.js +41 -0
- package/src/lib/platforms/gitbash.js +46 -0
- package/src/lib/platforms/macos.js +45 -0
- package/src/lib/platforms/raspbian.js +41 -0
- package/src/lib/platforms/ubuntu.js +39 -0
- package/src/lib/platforms/windows.js +45 -0
- package/src/lib/prompt.js +161 -0
- package/src/lib/schema.js +211 -0
- package/src/lib/shell.js +75 -0
- package/src/patterns/gitignore/claude-code.txt +25 -0
- package/src/patterns/gitignore/docker.txt +15 -0
- package/src/patterns/gitignore/go.txt +24 -0
- package/src/patterns/gitignore/java.txt +38 -0
- package/src/patterns/gitignore/jetbrains.txt +26 -0
- package/src/patterns/gitignore/linux.txt +18 -0
- package/src/patterns/gitignore/macos.txt +27 -0
- package/src/patterns/gitignore/node.txt +51 -0
- package/src/patterns/gitignore/python.txt +55 -0
- package/src/patterns/gitignore/rust.txt +14 -0
- package/src/patterns/gitignore/terraform.txt +30 -0
- package/src/patterns/gitignore/vscode.txt +15 -0
- package/src/patterns/gitignore/windows.txt +25 -0
- package/src/utils/clone/index.js +165 -0
- package/src/utils/git-push/index.js +230 -0
- package/src/utils/git-status/index.js +116 -0
- package/src/utils/git-status/unix.sh +75 -0
- package/src/utils/registry.json +41 -0
- package/bin/dev.js +0 -16
- package/files/README.md +0 -0
- package/files/claude/.claude/commands/setup-context.md +0 -3
- package/files/monorepos/_archive/README.md +0 -36
- package/files/monorepos/_legacy/README.md +0 -36
- package/files/monorepos/ai-docs/README.md +0 -33
- package/files/monorepos/apps/README.md +0 -24
- package/files/monorepos/docs/README.md +0 -40
- package/files/monorepos/packages/README.md +0 -25
- package/files/monorepos/research/README.md +0 -29
- package/files/monorepos/scripts/README.md +0 -24
- package/src/commands/README.md +0 -41
- package/src/commands/configure.js +0 -199
- package/src/commands/identity.js +0 -1630
- package/src/commands/ignore.js +0 -247
- package/src/commands/install.js +0 -526
- package/src/commands/setup.js +0 -246
- package/src/completion.js +0 -284
- package/src/constants.js +0 -45
- package/src/ignore/claude-code.txt +0 -10
- package/src/ignore/docker.txt +0 -18
- package/src/ignore/linux.txt +0 -23
- package/src/ignore/macos.txt +0 -36
- package/src/ignore/node.txt +0 -55
- package/src/ignore/terraform.txt +0 -37
- package/src/ignore/vscode.txt +0 -18
- package/src/ignore/windows.txt +0 -35
- package/src/index.js +0 -0
- package/src/installs/README.md +0 -399
- package/src/installs/adobe-creative-cloud.js +0 -546
- package/src/installs/adobe-creative-cloud.md +0 -605
- package/src/installs/appcleaner.js +0 -321
- package/src/installs/appcleaner.md +0 -699
- package/src/installs/apt-transport-https.js +0 -390
- package/src/installs/apt-transport-https.md +0 -678
- package/src/installs/atomicparsley.js +0 -642
- package/src/installs/atomicparsley.md +0 -795
- package/src/installs/aws-cli.js +0 -797
- package/src/installs/aws-cli.md +0 -727
- package/src/installs/balena-etcher.js +0 -710
- package/src/installs/balena-etcher.md +0 -761
- package/src/installs/bambu-studio.js +0 -1143
- package/src/installs/bambu-studio.md +0 -780
- package/src/installs/bash-completion.js +0 -575
- package/src/installs/bash-completion.md +0 -833
- package/src/installs/bash.js +0 -417
- package/src/installs/bash.md +0 -993
- package/src/installs/beyond-compare.js +0 -603
- package/src/installs/beyond-compare.md +0 -813
- package/src/installs/brave-browser.js +0 -968
- package/src/installs/brave-browser.md +0 -650
- package/src/installs/build-essential.js +0 -529
- package/src/installs/build-essential.md +0 -977
- package/src/installs/ca-certificates.js +0 -618
- package/src/installs/ca-certificates.md +0 -937
- package/src/installs/caffeine.js +0 -508
- package/src/installs/caffeine.md +0 -839
- package/src/installs/camtasia.js +0 -596
- package/src/installs/camtasia.md +0 -762
- package/src/installs/chatgpt.js +0 -476
- package/src/installs/chatgpt.md +0 -814
- package/src/installs/chocolatey.js +0 -456
- package/src/installs/chocolatey.md +0 -661
- package/src/installs/chrome-canary.js +0 -419
- package/src/installs/chrome-canary.md +0 -641
- package/src/installs/chromium.js +0 -667
- package/src/installs/chromium.md +0 -838
- package/src/installs/claude-code.js +0 -576
- package/src/installs/claude-code.md +0 -1173
- package/src/installs/cloudflare-warp.js +0 -900
- package/src/installs/cloudflare-warp.md +0 -1047
- package/src/installs/comet-browser.js +0 -588
- package/src/installs/comet-browser.md +0 -731
- package/src/installs/curl.js +0 -379
- package/src/installs/curl.md +0 -714
- package/src/installs/cursor.js +0 -579
- package/src/installs/cursor.md +0 -970
- package/src/installs/dbeaver.js +0 -924
- package/src/installs/dbeaver.md +0 -939
- package/src/installs/dbschema.js +0 -692
- package/src/installs/dbschema.md +0 -925
- package/src/installs/dependencies.md +0 -453
- package/src/installs/development-tools.js +0 -600
- package/src/installs/development-tools.md +0 -977
- package/src/installs/docker.js +0 -1029
- package/src/installs/docker.md +0 -1109
- package/src/installs/drawio.js +0 -1019
- package/src/installs/drawio.md +0 -795
- package/src/installs/elmedia-player.js +0 -347
- package/src/installs/elmedia-player.md +0 -556
- package/src/installs/ffmpeg.js +0 -889
- package/src/installs/ffmpeg.md +0 -852
- package/src/installs/file.js +0 -464
- package/src/installs/file.md +0 -987
- package/src/installs/gemini-cli.js +0 -811
- package/src/installs/gemini-cli.md +0 -1153
- package/src/installs/git.js +0 -400
- package/src/installs/git.md +0 -907
- package/src/installs/gitego.js +0 -949
- package/src/installs/gitego.md +0 -1172
- package/src/installs/go.js +0 -931
- package/src/installs/go.md +0 -958
- package/src/installs/google-antigravity.js +0 -913
- package/src/installs/google-antigravity.md +0 -1075
- package/src/installs/google-chrome.js +0 -833
- package/src/installs/google-chrome.md +0 -862
- package/src/installs/gpg.js +0 -480
- package/src/installs/gpg.md +0 -1056
- package/src/installs/homebrew.js +0 -1028
- package/src/installs/homebrew.md +0 -988
- package/src/installs/imageoptim.js +0 -968
- package/src/installs/imageoptim.md +0 -1119
- package/src/installs/installers.json +0 -4032
- package/src/installs/installers.json.tmp +0 -3953
- package/src/installs/jq.js +0 -400
- package/src/installs/jq.md +0 -809
- package/src/installs/keyboard-maestro.js +0 -719
- package/src/installs/keyboard-maestro.md +0 -825
- package/src/installs/kiro.js +0 -864
- package/src/installs/kiro.md +0 -1015
- package/src/installs/latex.js +0 -789
- package/src/installs/latex.md +0 -1095
- package/src/installs/lftp.js +0 -356
- package/src/installs/lftp.md +0 -907
- package/src/installs/lsb-release.js +0 -346
- package/src/installs/lsb-release.md +0 -814
- package/src/installs/messenger.js +0 -847
- package/src/installs/messenger.md +0 -900
- package/src/installs/microsoft-office.js +0 -568
- package/src/installs/microsoft-office.md +0 -760
- package/src/installs/microsoft-teams.js +0 -801
- package/src/installs/microsoft-teams.md +0 -886
- package/src/installs/moom.js +0 -326
- package/src/installs/moom.md +0 -570
- package/src/installs/node.js +0 -904
- package/src/installs/node.md +0 -1153
- package/src/installs/nordpass.js +0 -716
- package/src/installs/nordpass.md +0 -921
- package/src/installs/nordvpn.js +0 -892
- package/src/installs/nordvpn.md +0 -1052
- package/src/installs/nvm.js +0 -995
- package/src/installs/nvm.md +0 -1057
- package/src/installs/ohmyzsh.js +0 -529
- package/src/installs/ohmyzsh.md +0 -1094
- package/src/installs/openssh.js +0 -804
- package/src/installs/openssh.md +0 -1056
- package/src/installs/pandoc.js +0 -662
- package/src/installs/pandoc.md +0 -1036
- package/src/installs/parallels-desktop.js +0 -431
- package/src/installs/parallels-desktop.md +0 -446
- package/src/installs/pinentry.js +0 -510
- package/src/installs/pinentry.md +0 -1142
- package/src/installs/pngyu.js +0 -869
- package/src/installs/pngyu.md +0 -896
- package/src/installs/postman.js +0 -799
- package/src/installs/postman.md +0 -940
- package/src/installs/procps.js +0 -425
- package/src/installs/procps.md +0 -851
- package/src/installs/safari-tech-preview.js +0 -374
- package/src/installs/safari-tech-preview.md +0 -533
- package/src/installs/sfnt2woff.js +0 -658
- package/src/installs/sfnt2woff.md +0 -795
- package/src/installs/shellcheck.js +0 -481
- package/src/installs/shellcheck.md +0 -1005
- package/src/installs/slack.js +0 -741
- package/src/installs/slack.md +0 -865
- package/src/installs/snagit.js +0 -585
- package/src/installs/snagit.md +0 -844
- package/src/installs/software-properties-common.js +0 -372
- package/src/installs/software-properties-common.md +0 -805
- package/src/installs/spotify.js +0 -877
- package/src/installs/spotify.md +0 -901
- package/src/installs/studio-3t.js +0 -823
- package/src/installs/studio-3t.md +0 -918
- package/src/installs/sublime-text.js +0 -804
- package/src/installs/sublime-text.md +0 -914
- package/src/installs/superwhisper.js +0 -706
- package/src/installs/superwhisper.md +0 -630
- package/src/installs/tailscale.js +0 -745
- package/src/installs/tailscale.md +0 -1100
- package/src/installs/tar.js +0 -389
- package/src/installs/tar.md +0 -946
- package/src/installs/termius.js +0 -798
- package/src/installs/termius.md +0 -844
- package/src/installs/terraform.js +0 -779
- package/src/installs/terraform.md +0 -899
- package/src/installs/tfenv.js +0 -778
- package/src/installs/tfenv.md +0 -1091
- package/src/installs/tidal.js +0 -771
- package/src/installs/tidal.md +0 -864
- package/src/installs/tmux.js +0 -346
- package/src/installs/tmux.md +0 -1030
- package/src/installs/tree.js +0 -411
- package/src/installs/tree.md +0 -833
- package/src/installs/unzip.js +0 -460
- package/src/installs/unzip.md +0 -879
- package/src/installs/vim.js +0 -421
- package/src/installs/vim.md +0 -1040
- package/src/installs/vlc.js +0 -821
- package/src/installs/vlc.md +0 -927
- package/src/installs/vscode.js +0 -843
- package/src/installs/vscode.md +0 -1002
- package/src/installs/wget.js +0 -420
- package/src/installs/wget.md +0 -791
- package/src/installs/whatsapp.js +0 -729
- package/src/installs/whatsapp.md +0 -854
- package/src/installs/winpty.js +0 -352
- package/src/installs/winpty.md +0 -620
- package/src/installs/woff2.js +0 -553
- package/src/installs/woff2.md +0 -977
- package/src/installs/wsl.js +0 -572
- package/src/installs/wsl.md +0 -699
- package/src/installs/xcode-clt.js +0 -520
- package/src/installs/xcode-clt.md +0 -351
- package/src/installs/xcode.js +0 -560
- package/src/installs/xcode.md +0 -573
- package/src/installs/yarn.js +0 -824
- package/src/installs/yarn.md +0 -1074
- package/src/installs/yq.js +0 -654
- package/src/installs/yq.md +0 -944
- package/src/installs/yt-dlp.js +0 -701
- package/src/installs/yt-dlp.md +0 -946
- package/src/installs/yum-utils.js +0 -297
- package/src/installs/yum-utils.md +0 -648
- package/src/installs/zoom.js +0 -759
- package/src/installs/zoom.md +0 -884
- package/src/installs/zsh.js +0 -455
- package/src/installs/zsh.md +0 -1008
- package/src/scripts/README.md +0 -617
- package/src/scripts/STATUS.md +0 -208
- package/src/scripts/afk.js +0 -411
- package/src/scripts/backup-all.js +0 -746
- package/src/scripts/backup-source.js +0 -727
- package/src/scripts/brewd.js +0 -389
- package/src/scripts/brewi.js +0 -520
- package/src/scripts/brewr.js +0 -527
- package/src/scripts/brews.js +0 -477
- package/src/scripts/brewu.js +0 -504
- package/src/scripts/c.js +0 -201
- package/src/scripts/ccurl.js +0 -341
- package/src/scripts/certbot-crontab-init.js +0 -504
- package/src/scripts/certbot-init.js +0 -657
- package/src/scripts/ch.js +0 -355
- package/src/scripts/claude-danger.js +0 -268
- package/src/scripts/clean-dev.js +0 -435
- package/src/scripts/clear-dns-cache.js +0 -541
- package/src/scripts/clone.js +0 -435
- package/src/scripts/code-all.js +0 -437
- package/src/scripts/count-files.js +0 -211
- package/src/scripts/count-folders.js +0 -211
- package/src/scripts/count.js +0 -264
- package/src/scripts/d.js +0 -219
- package/src/scripts/datauri.js +0 -389
- package/src/scripts/delete-files.js +0 -380
- package/src/scripts/docker-clean.js +0 -426
- package/src/scripts/dp.js +0 -442
- package/src/scripts/e.js +0 -390
- package/src/scripts/empty-trash.js +0 -513
- package/src/scripts/evm.js +0 -444
- package/src/scripts/fetch-github-repos.js +0 -456
- package/src/scripts/get-channel.js +0 -345
- package/src/scripts/get-course.js +0 -399
- package/src/scripts/get-dependencies.js +0 -306
- package/src/scripts/get-folder.js +0 -799
- package/src/scripts/get-tunes.js +0 -426
- package/src/scripts/get-video.js +0 -367
- package/src/scripts/git-backup.js +0 -577
- package/src/scripts/git-clone.js +0 -493
- package/src/scripts/git-pup.js +0 -319
- package/src/scripts/git-push.js +0 -396
- package/src/scripts/h.js +0 -622
- package/src/scripts/hide-desktop-icons.js +0 -499
- package/src/scripts/hide-hidden-files.js +0 -538
- package/src/scripts/install-dependencies-from.js +0 -456
- package/src/scripts/ips.js +0 -663
- package/src/scripts/iso.js +0 -370
- package/src/scripts/killni.js +0 -577
- package/src/scripts/ll.js +0 -467
- package/src/scripts/local-ip.js +0 -325
- package/src/scripts/m.js +0 -524
- package/src/scripts/map.js +0 -309
- package/src/scripts/mkd.js +0 -351
- package/src/scripts/ncu-update-all.js +0 -457
- package/src/scripts/nginx-init.js +0 -718
- package/src/scripts/npmi.js +0 -382
- package/src/scripts/o.js +0 -511
- package/src/scripts/org-by-date.js +0 -338
- package/src/scripts/p.js +0 -224
- package/src/scripts/packages.js +0 -330
- package/src/scripts/path.js +0 -225
- package/src/scripts/ports.js +0 -597
- package/src/scripts/q.js +0 -305
- package/src/scripts/refresh-files.js +0 -394
- package/src/scripts/remove-smaller-files.js +0 -516
- package/src/scripts/rename-files-with-date.js +0 -533
- package/src/scripts/resize-image.js +0 -539
- package/src/scripts/rm-safe.js +0 -669
- package/src/scripts/s.js +0 -540
- package/src/scripts/set-git-public.js +0 -365
- package/src/scripts/show-desktop-icons.js +0 -475
- package/src/scripts/show-hidden-files.js +0 -472
- package/src/scripts/tpa.js +0 -280
- package/src/scripts/tpo.js +0 -280
- package/src/scripts/u.js +0 -505
- package/src/scripts/vpush.js +0 -437
- package/src/scripts/y.js +0 -283
- package/src/utils/README.md +0 -95
- package/src/utils/common/apps.js +0 -143
- package/src/utils/common/display.js +0 -157
- package/src/utils/common/network.js +0 -185
- package/src/utils/common/os.js +0 -294
- package/src/utils/common/package-manager.js +0 -301
- package/src/utils/common/privileges.js +0 -138
- package/src/utils/common/shell.js +0 -261
- package/src/utils/macos/apps.js +0 -228
- package/src/utils/macos/brew.js +0 -315
- package/src/utils/ubuntu/apt.js +0 -307
- package/src/utils/ubuntu/desktop.js +0 -292
- package/src/utils/ubuntu/snap.js +0 -344
- package/src/utils/ubuntu/systemd.js +0 -286
- package/src/utils/windows/choco.js +0 -465
- package/src/utils/windows/env.js +0 -246
- package/src/utils/windows/registry.js +0 -269
- package/src/utils/windows/shell.js +0 -240
- package/src/utils/windows/winget.js +0 -489
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates a structured error object in the DevUtils standard format.
|
|
5
|
+
* Does not write anything or exit the process.
|
|
6
|
+
*
|
|
7
|
+
* @param {number} code - Numeric error code (400=bad input, 401=unauthorized, 404=not found, 500=internal).
|
|
8
|
+
* @param {string} message - Human-readable description of what went wrong.
|
|
9
|
+
* @param {string} [service='devutils'] - The service that generated the error.
|
|
10
|
+
* @returns {{ error: { code: number, message: string, service: string } }}
|
|
11
|
+
*/
|
|
12
|
+
function createError(code, message, service) {
|
|
13
|
+
return {
|
|
14
|
+
error: {
|
|
15
|
+
code: code,
|
|
16
|
+
message: message,
|
|
17
|
+
service: service || 'devutils',
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Creates a structured error, writes it to stderr, and exits with code 1.
|
|
24
|
+
* Uses detect.js to determine the output format and output.js to render.
|
|
25
|
+
*
|
|
26
|
+
* Uses lazy require() to avoid circular dependency issues.
|
|
27
|
+
*
|
|
28
|
+
* @param {number} code - Numeric error code.
|
|
29
|
+
* @param {string} message - Human-readable error message.
|
|
30
|
+
* @param {string} [service] - The service that generated the error.
|
|
31
|
+
*/
|
|
32
|
+
function throwError(code, message, service) {
|
|
33
|
+
const err = createError(code, message, service);
|
|
34
|
+
const output = require('./output');
|
|
35
|
+
const detect = require('./detect');
|
|
36
|
+
|
|
37
|
+
const { format } = detect.detectOutputMode();
|
|
38
|
+
const formatted = output.renderError(err, format);
|
|
39
|
+
process.stderr.write(formatted + '\n');
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Alias for throwError. Some command stories use context.errors.exit().
|
|
45
|
+
*/
|
|
46
|
+
const exit = throwError;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Checks if an object matches the DevUtils error shape.
|
|
50
|
+
* Uses duck-typing: if it has error.code (number) and error.message (string), it's valid.
|
|
51
|
+
*
|
|
52
|
+
* @param {*} obj - The object to check.
|
|
53
|
+
* @returns {boolean}
|
|
54
|
+
*/
|
|
55
|
+
function isDevUtilsError(obj) {
|
|
56
|
+
if (obj === null || obj === undefined) return false;
|
|
57
|
+
if (typeof obj !== 'object') return false;
|
|
58
|
+
if (!obj.error) return false;
|
|
59
|
+
if (typeof obj.error !== 'object') return false;
|
|
60
|
+
if (typeof obj.error.code !== 'number') return false;
|
|
61
|
+
if (typeof obj.error.message !== 'string') return false;
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Creates a 404 Not Found error.
|
|
67
|
+
* @param {string} message - What was not found.
|
|
68
|
+
* @param {string} [service] - The service that generated the error.
|
|
69
|
+
* @returns {{ error: { code: number, message: string, service: string } }}
|
|
70
|
+
*/
|
|
71
|
+
function notFound(message, service) {
|
|
72
|
+
return createError(404, message, service);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Creates a 400 Bad Input error.
|
|
77
|
+
* @param {string} message - What was wrong with the input.
|
|
78
|
+
* @param {string} [service] - The service that generated the error.
|
|
79
|
+
* @returns {{ error: { code: number, message: string, service: string } }}
|
|
80
|
+
*/
|
|
81
|
+
function badInput(message, service) {
|
|
82
|
+
return createError(400, message, service);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Creates a 500 Internal Error.
|
|
87
|
+
* @param {string} message - What went wrong internally.
|
|
88
|
+
* @param {string} [service] - The service that generated the error.
|
|
89
|
+
* @returns {{ error: { code: number, message: string, service: string } }}
|
|
90
|
+
*/
|
|
91
|
+
function internal(message, service) {
|
|
92
|
+
return createError(500, message, service);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Creates a 401 Unauthorized error.
|
|
97
|
+
* @param {string} message - Why access was denied.
|
|
98
|
+
* @param {string} [service] - The service that generated the error.
|
|
99
|
+
* @returns {{ error: { code: number, message: string, service: string } }}
|
|
100
|
+
*/
|
|
101
|
+
function unauthorized(message, service) {
|
|
102
|
+
return createError(401, message, service);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
module.exports = {
|
|
106
|
+
createError,
|
|
107
|
+
throwError,
|
|
108
|
+
exit,
|
|
109
|
+
isDevUtilsError,
|
|
110
|
+
notFound,
|
|
111
|
+
badInput,
|
|
112
|
+
internal,
|
|
113
|
+
unauthorized,
|
|
114
|
+
};
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const shell = require('./shell');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Check if the GitHub CLI (gh) is installed on the system.
|
|
7
|
+
* @returns {boolean} True if gh is available on PATH
|
|
8
|
+
*/
|
|
9
|
+
function isInstalled() {
|
|
10
|
+
return shell.commandExists('gh');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Throw a clear error if gh is not installed.
|
|
15
|
+
* Call this at the top of any function that needs gh.
|
|
16
|
+
*/
|
|
17
|
+
function requireGh() {
|
|
18
|
+
if (!isInstalled()) {
|
|
19
|
+
throw new Error(
|
|
20
|
+
'GitHub CLI (gh) is not installed.\n' +
|
|
21
|
+
'Install it from: https://cli.github.com/\n' +
|
|
22
|
+
'Or with Homebrew: brew install gh'
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Check if the user is authenticated with GitHub via gh.
|
|
29
|
+
* @returns {Promise<boolean>} True if authenticated
|
|
30
|
+
*/
|
|
31
|
+
async function isAuthenticated() {
|
|
32
|
+
requireGh();
|
|
33
|
+
const result = await shell.exec('gh auth status');
|
|
34
|
+
return result.exitCode === 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Get the authenticated username.
|
|
39
|
+
* @returns {Promise<string|null>} The GitHub username, or null if not authenticated
|
|
40
|
+
*/
|
|
41
|
+
async function getUsername() {
|
|
42
|
+
requireGh();
|
|
43
|
+
const result = await shell.exec('gh api user --jq .login');
|
|
44
|
+
if (result.exitCode !== 0) return null;
|
|
45
|
+
return result.stdout.trim() || null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Create a new GitHub repository.
|
|
50
|
+
* @param {string} name - Repository name
|
|
51
|
+
* @param {boolean} isPrivate - Whether the repo should be private
|
|
52
|
+
* @returns {Promise<{ success: boolean, url: string|null, error: string|null }>}
|
|
53
|
+
*/
|
|
54
|
+
async function createRepo(name, isPrivate = true) {
|
|
55
|
+
requireGh();
|
|
56
|
+
const visibility = isPrivate ? '--private' : '--public';
|
|
57
|
+
const result = await shell.exec(`gh repo create "${name}" ${visibility} --yes`);
|
|
58
|
+
|
|
59
|
+
if (result.exitCode !== 0) {
|
|
60
|
+
return { success: false, url: null, error: result.stderr };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// gh repo create outputs the repo URL
|
|
64
|
+
return { success: true, url: result.stdout.trim(), error: null };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Clone a GitHub repository to a local directory.
|
|
69
|
+
* @param {string} url - Repository URL or owner/name
|
|
70
|
+
* @param {string} dest - Local destination directory
|
|
71
|
+
* @returns {Promise<{ success: boolean, error: string|null }>}
|
|
72
|
+
*/
|
|
73
|
+
async function cloneRepo(url, dest) {
|
|
74
|
+
requireGh();
|
|
75
|
+
const result = await shell.exec(`gh repo clone "${url}" "${dest}"`);
|
|
76
|
+
|
|
77
|
+
if (result.exitCode !== 0) {
|
|
78
|
+
return { success: false, error: result.stderr };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return { success: true, error: null };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Push local changes to the remote repository.
|
|
86
|
+
* Runs git add, commit, and push inside the given directory.
|
|
87
|
+
* If there are no changes, returns success without committing.
|
|
88
|
+
* @param {string} dest - Local repository directory
|
|
89
|
+
* @param {string} [message] - Commit message (defaults to 'DevUtils config update')
|
|
90
|
+
* @returns {Promise<{ success: boolean, error: string|null }>}
|
|
91
|
+
*/
|
|
92
|
+
async function pushRepo(dest, message = 'DevUtils config update') {
|
|
93
|
+
const escapedMessage = message.replace(/"/g, '\\"');
|
|
94
|
+
|
|
95
|
+
// Stage all changes
|
|
96
|
+
let result = await shell.exec('git add -A', { cwd: dest });
|
|
97
|
+
if (result.exitCode !== 0) {
|
|
98
|
+
return { success: false, error: 'git add failed: ' + result.stderr };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Check if there is anything to commit
|
|
102
|
+
const status = await shell.exec('git status --porcelain', { cwd: dest });
|
|
103
|
+
if (!status.stdout.trim()) {
|
|
104
|
+
// Nothing to commit -- still a success, just nothing changed
|
|
105
|
+
return { success: true, error: null };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Commit
|
|
109
|
+
result = await shell.exec(`git commit -m "${escapedMessage}"`, { cwd: dest });
|
|
110
|
+
if (result.exitCode !== 0) {
|
|
111
|
+
return { success: false, error: 'git commit failed: ' + result.stderr };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Push
|
|
115
|
+
result = await shell.exec('git push', { cwd: dest });
|
|
116
|
+
if (result.exitCode !== 0) {
|
|
117
|
+
return { success: false, error: 'git push failed: ' + result.stderr };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return { success: true, error: null };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Pull the latest changes from the remote repository.
|
|
125
|
+
* @param {string} dest - Local repository directory
|
|
126
|
+
* @returns {Promise<{ success: boolean, error: string|null }>}
|
|
127
|
+
*/
|
|
128
|
+
async function pullRepo(dest) {
|
|
129
|
+
const result = await shell.exec('git pull', { cwd: dest });
|
|
130
|
+
|
|
131
|
+
if (result.exitCode !== 0) {
|
|
132
|
+
return { success: false, error: result.stderr };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return { success: true, error: null };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* List the authenticated user's repositories.
|
|
140
|
+
* @param {number} [limit] - Maximum number of repos to return (default: 100)
|
|
141
|
+
* @returns {Promise<Array<{ name: string, url: string, private: boolean }>>}
|
|
142
|
+
*/
|
|
143
|
+
async function listRepos(limit = 100) {
|
|
144
|
+
requireGh();
|
|
145
|
+
const result = await shell.exec(
|
|
146
|
+
`gh repo list --json name,url,isPrivate --limit ${limit}`
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
if (result.exitCode !== 0) return [];
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
const repos = JSON.parse(result.stdout);
|
|
153
|
+
return repos.map(r => ({
|
|
154
|
+
name: r.name,
|
|
155
|
+
url: r.url,
|
|
156
|
+
private: r.isPrivate
|
|
157
|
+
}));
|
|
158
|
+
} catch {
|
|
159
|
+
return [];
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Create a new GitHub gist.
|
|
165
|
+
* Writes temp files to disk because gh gist create expects file paths.
|
|
166
|
+
* Temp files are cleaned up even if the command fails.
|
|
167
|
+
* @param {Object<string, string>} files - Object mapping filename to content
|
|
168
|
+
* @param {string} [description] - Gist description
|
|
169
|
+
* @param {boolean} [secret] - Whether the gist should be secret (default: true)
|
|
170
|
+
* @returns {Promise<{ success: boolean, id: string|null, url: string|null, error: string|null }>}
|
|
171
|
+
*/
|
|
172
|
+
async function createGist(files, description = '', secret = true) {
|
|
173
|
+
requireGh();
|
|
174
|
+
|
|
175
|
+
const fs = require('fs');
|
|
176
|
+
const path = require('path');
|
|
177
|
+
const nodeOs = require('os');
|
|
178
|
+
|
|
179
|
+
const tempDir = fs.mkdtempSync(path.join(nodeOs.tmpdir(), 'devutils-gist-'));
|
|
180
|
+
const tempFiles = [];
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
for (const [filename, content] of Object.entries(files)) {
|
|
184
|
+
const filePath = path.join(tempDir, filename);
|
|
185
|
+
fs.writeFileSync(filePath, content);
|
|
186
|
+
tempFiles.push(filePath);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const secretFlag = secret ? '' : '--public';
|
|
190
|
+
const descFlag = description ? `--desc "${description.replace(/"/g, '\\"')}"` : '';
|
|
191
|
+
const fileArgs = tempFiles.map(f => `"${f}"`).join(' ');
|
|
192
|
+
|
|
193
|
+
const result = await shell.exec(
|
|
194
|
+
`gh gist create ${fileArgs} ${secretFlag} ${descFlag}`
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
if (result.exitCode !== 0) {
|
|
198
|
+
return { success: false, id: null, url: null, error: result.stderr };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// gh gist create outputs the gist URL
|
|
202
|
+
const url = result.stdout.trim();
|
|
203
|
+
// Extract the gist ID from the URL (last path segment)
|
|
204
|
+
const id = url.split('/').pop();
|
|
205
|
+
|
|
206
|
+
return { success: true, id, url, error: null };
|
|
207
|
+
} finally {
|
|
208
|
+
// Clean up temp files
|
|
209
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Get the contents of a gist by ID.
|
|
215
|
+
* Uses gh api with a jq filter to extract file contents as structured JSON.
|
|
216
|
+
* @param {string} id - The gist ID
|
|
217
|
+
* @returns {Promise<{ success: boolean, files: Object<string, string>|null, error: string|null }>}
|
|
218
|
+
*/
|
|
219
|
+
async function getGist(id) {
|
|
220
|
+
requireGh();
|
|
221
|
+
const result = await shell.exec(
|
|
222
|
+
`gh api gists/${id} --jq '.files | to_entries | map({key: .key, value: .value.content}) | from_entries'`
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
if (result.exitCode !== 0) {
|
|
226
|
+
return { success: false, files: null, error: result.stderr };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
const files = JSON.parse(result.stdout);
|
|
231
|
+
return { success: true, files, error: null };
|
|
232
|
+
} catch {
|
|
233
|
+
return { success: false, files: null, error: 'Failed to parse gist content' };
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Update an existing gist with new file contents.
|
|
239
|
+
* Updates each file sequentially using gh gist edit --add.
|
|
240
|
+
* @param {string} id - The gist ID
|
|
241
|
+
* @param {Object<string, string>} files - Object mapping filename to new content
|
|
242
|
+
* @returns {Promise<{ success: boolean, error: string|null }>}
|
|
243
|
+
*/
|
|
244
|
+
async function updateGist(id, files) {
|
|
245
|
+
requireGh();
|
|
246
|
+
|
|
247
|
+
const fs = require('fs');
|
|
248
|
+
const path = require('path');
|
|
249
|
+
const nodeOs = require('os');
|
|
250
|
+
|
|
251
|
+
const tempDir = fs.mkdtempSync(path.join(nodeOs.tmpdir(), 'devutils-gist-'));
|
|
252
|
+
|
|
253
|
+
try {
|
|
254
|
+
// Write each file to disk and update via gh gist edit
|
|
255
|
+
for (const [filename, content] of Object.entries(files)) {
|
|
256
|
+
const filePath = path.join(tempDir, filename);
|
|
257
|
+
fs.writeFileSync(filePath, content);
|
|
258
|
+
|
|
259
|
+
const result = await shell.exec(
|
|
260
|
+
`gh gist edit ${id} --add "${filePath}"`
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
if (result.exitCode !== 0) {
|
|
264
|
+
return { success: false, error: `Failed to update ${filename}: ${result.stderr}` };
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return { success: true, error: null };
|
|
269
|
+
} finally {
|
|
270
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* List the authenticated user's gists.
|
|
276
|
+
* Parses tab-separated output from gh gist list.
|
|
277
|
+
* @param {number} [limit] - Maximum number of gists to return (default: 30)
|
|
278
|
+
* @returns {Promise<Array<{ id: string, description: string, public: boolean, files: string[] }>>}
|
|
279
|
+
*/
|
|
280
|
+
async function listGists(limit = 30) {
|
|
281
|
+
requireGh();
|
|
282
|
+
const result = await shell.exec(
|
|
283
|
+
`gh gist list --limit ${limit}`
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
if (result.exitCode !== 0) return [];
|
|
287
|
+
|
|
288
|
+
// gh gist list outputs a tab-separated table
|
|
289
|
+
// Columns: ID, Description, Files, Visibility, Updated
|
|
290
|
+
const lines = result.stdout.split('\n').filter(Boolean);
|
|
291
|
+
return lines.map(line => {
|
|
292
|
+
const parts = line.split('\t');
|
|
293
|
+
return {
|
|
294
|
+
id: parts[0] || '',
|
|
295
|
+
description: parts[1] || '',
|
|
296
|
+
public: (parts[3] || '').trim() === 'public',
|
|
297
|
+
files: (parts[2] || '').split(',').map(f => f.trim()).filter(Boolean)
|
|
298
|
+
};
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
module.exports = {
|
|
303
|
+
isInstalled,
|
|
304
|
+
isAuthenticated,
|
|
305
|
+
getUsername,
|
|
306
|
+
createRepo,
|
|
307
|
+
cloneRepo,
|
|
308
|
+
pushRepo,
|
|
309
|
+
pullRepo,
|
|
310
|
+
listRepos,
|
|
311
|
+
createGist,
|
|
312
|
+
getGist,
|
|
313
|
+
updateGist,
|
|
314
|
+
listGists
|
|
315
|
+
};
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Cached registry data. Set on first call to loadRegistry().
|
|
8
|
+
* The registry doesn't change during a CLI run, so we only read it once.
|
|
9
|
+
* @type {Array<object>|null}
|
|
10
|
+
*/
|
|
11
|
+
let registryCache = null;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Reads and parses the installer registry (src/installers/registry.json).
|
|
15
|
+
* Returns the tools array. Results are cached after the first call.
|
|
16
|
+
*
|
|
17
|
+
* @returns {Array<object>} The array of tool entries from the registry.
|
|
18
|
+
*/
|
|
19
|
+
function loadRegistry() {
|
|
20
|
+
if (registryCache) {
|
|
21
|
+
return registryCache;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const registryPath = path.join(__dirname, '..', 'installers', 'registry.json');
|
|
25
|
+
const raw = fs.readFileSync(registryPath, 'utf8');
|
|
26
|
+
const data = JSON.parse(raw);
|
|
27
|
+
registryCache = data.tools || [];
|
|
28
|
+
return registryCache;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Looks up a tool by name in the registry.
|
|
33
|
+
* Comparison is case-insensitive.
|
|
34
|
+
*
|
|
35
|
+
* @param {string} name - The tool name to look up (e.g. 'git', 'Node').
|
|
36
|
+
* @returns {object|null} The tool entry, or null if not found.
|
|
37
|
+
*/
|
|
38
|
+
function findTool(name) {
|
|
39
|
+
if (!name) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const tools = loadRegistry();
|
|
44
|
+
const lower = name.toLowerCase();
|
|
45
|
+
return tools.find(t => t.name.toLowerCase() === lower) || null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Loads the installer module for a given tool entry.
|
|
50
|
+
* Validates that the file exists and exports the required functions (isInstalled, install).
|
|
51
|
+
*
|
|
52
|
+
* @param {object} toolEntry - A tool entry from the registry (must have an `installer` field).
|
|
53
|
+
* @returns {object} The installer module (with isInstalled, install, etc.).
|
|
54
|
+
* @throws {Error} If the installer file is missing or doesn't export required functions.
|
|
55
|
+
*/
|
|
56
|
+
function loadInstaller(toolEntry) {
|
|
57
|
+
if (!toolEntry || !toolEntry.installer) {
|
|
58
|
+
throw new Error('Invalid tool entry: missing installer field.');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const installerPath = path.join(__dirname, '..', 'installers', toolEntry.installer);
|
|
62
|
+
|
|
63
|
+
if (!fs.existsSync(installerPath)) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
`Installer file not found: ${toolEntry.installer}. ` +
|
|
66
|
+
`Expected at ${installerPath}`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const mod = require(installerPath);
|
|
71
|
+
|
|
72
|
+
if (typeof mod.isInstalled !== 'function') {
|
|
73
|
+
throw new Error(
|
|
74
|
+
`Installer "${toolEntry.installer}" does not export an isInstalled() function.`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (typeof mod.install !== 'function') {
|
|
79
|
+
throw new Error(
|
|
80
|
+
`Installer "${toolEntry.installer}" does not export an install() function.`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return mod;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Resolves the full dependency chain for a tool, in installation order.
|
|
89
|
+
* If tool A depends on B, and B depends on C, returns ['C', 'B', 'A'].
|
|
90
|
+
* Detects circular dependencies and throws if found.
|
|
91
|
+
*
|
|
92
|
+
* @param {string} toolName - The name of the tool to resolve.
|
|
93
|
+
* @param {Set<string>} [visited] - Already-visited tool names (used internally for recursion).
|
|
94
|
+
* @param {string[]} [chain] - Current dependency chain (used internally for circular detection).
|
|
95
|
+
* @returns {string[]} Flat, ordered array of tool names to install.
|
|
96
|
+
* @throws {Error} If a circular dependency is detected or a tool is unknown.
|
|
97
|
+
*/
|
|
98
|
+
function resolveDependencies(toolName, visited = new Set(), chain = []) {
|
|
99
|
+
if (chain.includes(toolName)) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`Circular dependency detected: ${chain.join(' -> ')} -> ${toolName}`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (visited.has(toolName)) {
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const tool = findTool(toolName);
|
|
110
|
+
if (!tool) {
|
|
111
|
+
throw new Error(`Unknown tool: ${toolName}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
chain.push(toolName);
|
|
115
|
+
visited.add(toolName);
|
|
116
|
+
|
|
117
|
+
const result = [];
|
|
118
|
+
for (const dep of tool.dependencies) {
|
|
119
|
+
result.push(...resolveDependencies(dep, visited, [...chain]));
|
|
120
|
+
}
|
|
121
|
+
result.push(toolName);
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Checks if a tool is installed on the current system.
|
|
127
|
+
* Loads the tool's installer and calls its isInstalled() function.
|
|
128
|
+
*
|
|
129
|
+
* @param {string} toolName - The name of the tool to check.
|
|
130
|
+
* @param {object} context - The CLI context object.
|
|
131
|
+
* @returns {Promise<boolean>} true if the tool is installed.
|
|
132
|
+
* @throws {Error} If the tool is unknown or the installer can't be loaded.
|
|
133
|
+
*/
|
|
134
|
+
async function checkInstalled(toolName, context) {
|
|
135
|
+
const tool = findTool(toolName);
|
|
136
|
+
if (!tool) {
|
|
137
|
+
throw new Error(`Unknown tool: ${toolName}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const mod = loadInstaller(tool);
|
|
141
|
+
return mod.isInstalled(context);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Installs a tool, including any dependencies, using the platform-appropriate method.
|
|
146
|
+
*
|
|
147
|
+
* Steps:
|
|
148
|
+
* 1. Looks up the tool in the registry. Throws if not found.
|
|
149
|
+
* 2. Checks if the current platform is supported. Throws if not.
|
|
150
|
+
* 3. Resolves the dependency chain.
|
|
151
|
+
* 4. For each tool in the chain, checks if it's already installed.
|
|
152
|
+
* If not, loads the installer and calls install(context).
|
|
153
|
+
* 5. Returns a summary object.
|
|
154
|
+
*
|
|
155
|
+
* @param {string} toolName - The name of the tool to install.
|
|
156
|
+
* @param {object} context - The CLI context object.
|
|
157
|
+
* @returns {Promise<{ tool: string, alreadyInstalled: boolean, dependenciesInstalled: string[], installed: boolean }>}
|
|
158
|
+
* @throws {Error} If the tool is unknown, unsupported on the current platform, or installation fails.
|
|
159
|
+
*/
|
|
160
|
+
async function installTool(toolName, context) {
|
|
161
|
+
const tool = findTool(toolName);
|
|
162
|
+
if (!tool) {
|
|
163
|
+
throw new Error(`Tool '${toolName}' not found in registry.`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Check platform support
|
|
167
|
+
const platformType = context.platform.detect().type;
|
|
168
|
+
if (!tool.platforms.includes(platformType)) {
|
|
169
|
+
throw new Error(
|
|
170
|
+
`Tool '${toolName}' is not supported on ${platformType}. ` +
|
|
171
|
+
`Supported platforms: ${tool.platforms.join(', ')}`
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Resolve dependencies
|
|
176
|
+
const chain = resolveDependencies(toolName);
|
|
177
|
+
const dependenciesInstalled = [];
|
|
178
|
+
let alreadyInstalled = false;
|
|
179
|
+
let installed = false;
|
|
180
|
+
|
|
181
|
+
for (const depName of chain) {
|
|
182
|
+
const depTool = findTool(depName);
|
|
183
|
+
const depMod = loadInstaller(depTool);
|
|
184
|
+
const isAlready = await depMod.isInstalled(context);
|
|
185
|
+
|
|
186
|
+
if (isAlready) {
|
|
187
|
+
if (depName === toolName) {
|
|
188
|
+
alreadyInstalled = true;
|
|
189
|
+
}
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Check platform support for the dependency too
|
|
194
|
+
if (!depTool.platforms.includes(platformType)) {
|
|
195
|
+
throw new Error(
|
|
196
|
+
`Dependency '${depName}' is not supported on ${platformType}. ` +
|
|
197
|
+
`Cannot install '${toolName}'.`
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
await depMod.install(context);
|
|
202
|
+
|
|
203
|
+
if (depName === toolName) {
|
|
204
|
+
installed = true;
|
|
205
|
+
} else {
|
|
206
|
+
dependenciesInstalled.push(depName);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
tool: toolName,
|
|
212
|
+
alreadyInstalled,
|
|
213
|
+
dependenciesInstalled,
|
|
214
|
+
installed,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
module.exports = {
|
|
219
|
+
loadRegistry,
|
|
220
|
+
findTool,
|
|
221
|
+
loadInstaller,
|
|
222
|
+
resolveDependencies,
|
|
223
|
+
checkInstalled,
|
|
224
|
+
installTool,
|
|
225
|
+
};
|