mirror of
https://github.com/ScoopInstaller/Scoop.git
synced 2025-11-01 07:09:01 +00:00
Compare commits
200 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b588a06e41 | ||
|
|
9246250808 | ||
|
|
8c51a50856 | ||
|
|
ebd8c036fa | ||
|
|
ed6101a2ab | ||
|
|
26a03e2404 | ||
|
|
f9c4f9e175 | ||
|
|
3321b969a4 | ||
|
|
22f1672fcb | ||
|
|
4e528e1655 | ||
|
|
ac6f1baaf4 | ||
|
|
dc9d198014 | ||
|
|
84e00fdb77 | ||
|
|
7a309a1b00 | ||
|
|
3577f91d82 | ||
|
|
e0c682de7c | ||
|
|
79cf33d0b7 | ||
|
|
7f99c499d7 | ||
|
|
859d1db51b | ||
|
|
c7ec5c82b2 | ||
|
|
46624b00c9 | ||
|
|
49ee8ad6ec | ||
|
|
5dc5dd22f8 | ||
|
|
5971f1dda8 | ||
|
|
0cb479abf0 | ||
|
|
429ba7af5b | ||
|
|
be56faf290 | ||
|
|
69edc6dc54 | ||
|
|
0138dc4266 | ||
|
|
1c271f5b90 | ||
|
|
a76884af19 | ||
|
|
7cc4faea1d | ||
|
|
83f25a4673 | ||
|
|
42af27d16c | ||
|
|
716b6db3ae | ||
|
|
ade7aa4a15 | ||
|
|
7e0a2a28c6 | ||
|
|
2d02483fb8 | ||
|
|
93359a43a1 | ||
|
|
d8b3cc8a6c | ||
|
|
9239c26bbd | ||
|
|
3a39ba048f | ||
|
|
a9ca75c0cd | ||
|
|
d20819e147 | ||
|
|
dec4232754 | ||
|
|
fa1b42bf28 | ||
|
|
700a2f4f5e | ||
|
|
c9048ad978 | ||
|
|
5c896e901f | ||
|
|
8ea37387ae | ||
|
|
2544745695 | ||
|
|
5ce70c4139 | ||
|
|
2d50a02a32 | ||
|
|
f6f46f6cf4 | ||
|
|
d337bb1fa6 | ||
|
|
cddcd98e33 | ||
|
|
86d3fef187 | ||
|
|
e324fa4c5f | ||
|
|
d9c5c90285 | ||
|
|
82640c456e | ||
|
|
4dd2cfdc5f | ||
|
|
a5bd2297c6 | ||
|
|
b710ff6c0a | ||
|
|
1dd479f0be | ||
|
|
1752050614 | ||
|
|
edaae8d39f | ||
|
|
776135e0ab | ||
|
|
cc863353e2 | ||
|
|
5b86c302e5 | ||
|
|
b8580aa931 | ||
|
|
23d55ced72 | ||
|
|
36026f1804 | ||
|
|
3b34497eb4 | ||
|
|
bb88dfb2d4 | ||
|
|
d285bb08d4 | ||
|
|
eac58400db | ||
|
|
2dd91d5ba3 | ||
|
|
105e4161fc | ||
|
|
78f6fecd60 | ||
|
|
5819b5a1ef | ||
|
|
fa06e921c2 | ||
|
|
98cf8ae4da | ||
|
|
7054c9d338 | ||
|
|
6327146b97 | ||
|
|
92b71c6057 | ||
|
|
81e7dec78c | ||
|
|
b008fe5b11 | ||
|
|
5a06eacd9c | ||
|
|
0b135052ce | ||
|
|
9ef03c24fb | ||
|
|
dfbeace066 | ||
|
|
6772e61b3d | ||
|
|
77b66cc890 | ||
|
|
9770c86598 | ||
|
|
5153d7375b | ||
|
|
5354ab5d16 | ||
|
|
90766f9315 | ||
|
|
3186fef105 | ||
|
|
9d07c33e87 | ||
|
|
6f9ed1d464 | ||
|
|
54e0514833 | ||
|
|
7e3dc73b83 | ||
|
|
48f793532c | ||
|
|
5328bef269 | ||
|
|
fb3169629f | ||
|
|
7e81e49152 | ||
|
|
7b35e19d4c | ||
|
|
6cdcc75ad8 | ||
|
|
14b38b4092 | ||
|
|
2847e0a37c | ||
|
|
3a3f41c556 | ||
|
|
15f9bbec97 | ||
|
|
ab34b7fb61 | ||
|
|
863af42d4e | ||
|
|
b3c05e71fa | ||
|
|
acc271d115 | ||
|
|
6d79d62cc8 | ||
|
|
00c92b04d6 | ||
|
|
becc7a7b76 | ||
|
|
6898773a8d | ||
|
|
353137f0a9 | ||
|
|
43579714cc | ||
|
|
aa09601503 | ||
|
|
6a35a22b0b | ||
|
|
0b4919ca32 | ||
|
|
efdd6dd7ca | ||
|
|
3dfb4bfd97 | ||
|
|
1d140585a4 | ||
|
|
52059ca1ac | ||
|
|
cddc52e03b | ||
|
|
682a1e2c07 | ||
|
|
2accaae5d1 | ||
|
|
a20bb4f1a6 | ||
|
|
ad0f6178d0 | ||
|
|
41620bb169 | ||
|
|
559c6f9e64 | ||
|
|
7826d6fe2d | ||
|
|
8acfeeefcf | ||
|
|
c00dd42cae | ||
|
|
3f11454a3c | ||
|
|
0a39de86e2 | ||
|
|
c44e214743 | ||
|
|
7c6aeb240e | ||
|
|
32ca856f63 | ||
|
|
1d0bd434ab | ||
|
|
54e3613fca | ||
|
|
e2558ace75 | ||
|
|
68760de1e8 | ||
|
|
257304bbc7 | ||
|
|
52f9ce3a81 | ||
|
|
6369ba60ba | ||
|
|
af5ffcddab | ||
|
|
360daa706a | ||
|
|
f93028001f | ||
|
|
c60df9cdad | ||
|
|
ac71c6e1b7 | ||
|
|
0f795733d8 | ||
|
|
29ed3cb050 | ||
|
|
d7bfe52122 | ||
|
|
29e5898a45 | ||
|
|
ea6c73880a | ||
|
|
e4f9734b88 | ||
|
|
1630e5f908 | ||
|
|
2474ab73e4 | ||
|
|
d65fee6d26 | ||
|
|
01fe9ccd63 | ||
|
|
1c6ab39e90 | ||
|
|
c71376e12c | ||
|
|
c9dc41e7c4 | ||
|
|
5e6a9eeaa0 | ||
|
|
b308769b14 | ||
|
|
1eea29b0d1 | ||
|
|
7dcb7c0030 | ||
|
|
9fda5428ae | ||
|
|
d0cbc36a58 | ||
|
|
512ab44029 | ||
|
|
1f0f687a39 | ||
|
|
ec8161df6c | ||
|
|
24301ac028 | ||
|
|
8aee6f9980 | ||
|
|
9baf293ab6 | ||
|
|
7f47f662e2 | ||
|
|
7a599f062f | ||
|
|
146dab60d1 | ||
|
|
04595b417a | ||
|
|
782f3f1aa6 | ||
|
|
122fdc1a1c | ||
|
|
373007870c | ||
|
|
6fc65ed864 | ||
|
|
bfef3d8298 | ||
|
|
5ad35d6054 | ||
|
|
740322f74f | ||
|
|
e06c7f0c81 | ||
|
|
1985a05b59 | ||
|
|
dea9ebb01a | ||
|
|
08ecdd16a8 | ||
|
|
a9e5a974dd | ||
|
|
8619ee7603 | ||
|
|
bd123939dc | ||
|
|
ec04dd07bc |
4
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
4
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
@@ -31,9 +31,9 @@ labels: "bug"
|
||||
|
||||
### System details
|
||||
|
||||
**Windows version:** [e.g. 7, 8, 10]
|
||||
**Windows version:** [e.g. 7, 8, 10, 11]
|
||||
|
||||
**OS architecture:** [e.g. 32bit, 64bit]
|
||||
**OS architecture:** [e.g. 32bit, 64bit, arm64]
|
||||
|
||||
**PowerShell version:** [output of `"$($PSVersionTable.PSVersion)"`]
|
||||
|
||||
|
||||
8
.github/dependabot.yml
vendored
Normal file
8
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
# ~/.github/dependabot.yml
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/" # == /.github/workflows/
|
||||
schedule:
|
||||
interval: "daily"
|
||||
12
.github/workflows/ci.yml
vendored
12
.github/workflows/ci.yml
vendored
@@ -10,13 +10,13 @@ jobs:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@main
|
||||
with:
|
||||
fetch-depth: 2
|
||||
- name: Init Test Suite
|
||||
uses: potatoqualitee/psmodulecache@v4
|
||||
uses: potatoqualitee/psmodulecache@main
|
||||
with:
|
||||
modules-to-cache: PSScriptAnalyzer, BuildHelpers, Pester:4.10.1
|
||||
modules-to-cache: BuildHelpers
|
||||
shell: powershell
|
||||
- name: Test Scoop Core
|
||||
shell: powershell
|
||||
@@ -26,13 +26,13 @@ jobs:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@main
|
||||
with:
|
||||
fetch-depth: 2
|
||||
- name: Init Test Suite
|
||||
uses: potatoqualitee/psmodulecache@v4
|
||||
uses: potatoqualitee/psmodulecache@main
|
||||
with:
|
||||
modules-to-cache: PSScriptAnalyzer, BuildHelpers, Pester:4.10.1
|
||||
modules-to-cache: BuildHelpers
|
||||
shell: pwsh
|
||||
- name: Test Scoop Core
|
||||
shell: pwsh
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
*.log
|
||||
.DS_Store
|
||||
._.DS_Store
|
||||
scoop.sublime-workspace
|
||||
@@ -5,3 +6,4 @@ test/installer/tmp/*
|
||||
test/tmp/*
|
||||
*~
|
||||
TestResults.xml
|
||||
supporting/sqlite/*
|
||||
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -4,6 +4,9 @@
|
||||
"powershell.codeFormatting.preset": "OTBS",
|
||||
"powershell.codeFormatting.alignPropertyValuePairs": true,
|
||||
"powershell.codeFormatting.ignoreOneLineBlock": true,
|
||||
"powershell.codeFormatting.useConstantStrings": true,
|
||||
"powershell.codeFormatting.useCorrectCasing": true,
|
||||
"powershell.codeFormatting.whitespaceBetweenParameters": true,
|
||||
"files.exclude": {
|
||||
"**/.git": true,
|
||||
"**/.svn": true,
|
||||
|
||||
262
CHANGELOG.md
262
CHANGELOG.md
@@ -1,3 +1,265 @@
|
||||
## [v0.5.3](https://github.com/ScoopInstaller/Scoop/compare/v0.5.2...v0.5.3) - 2025-08-11
|
||||
|
||||
### Features
|
||||
|
||||
**autoupdate:** GitHub predefined hashes support ([#6416](https://github.com/ScoopInstaller/Scoop/issues/6416), [#6435](https://github.com/ScoopInstaller/Scoop/issues/6435))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **scoop-download|install|update:** Fallback to default downloader when aria2 fails ([#4292](https://github.com/ScoopInstaller/Scoop/issues/4292))
|
||||
- **decompress**: `Expand-7zipArchive` only delete temp dir / `$extractDir` if it is empty ([#6092](https://github.com/ScoopInstaller/Scoop/issues/6092))
|
||||
- **decompress**: Replace deprecated 7ZIPEXTRACT_USE_EXTERNAL config with USE_EXTERNAL_7ZIP ([#6327](https://github.com/ScoopInstaller/Scoop/issues/6327))
|
||||
- **commands**: Handling broken aliases ([#6141](https://github.com/ScoopInstaller/Scoop/issues/6141))
|
||||
- **shim:** Do not suppress `stderr`, properly check `wslpath`/`cygpath` command first ([#6114](https://github.com/ScoopInstaller/Scoop/issues/6114))
|
||||
- **scoop-bucket:** Add missing import for `no_junction` envs ([#6181](https://github.com/ScoopInstaller/Scoop/issues/6181))
|
||||
- **scoop-uninstall:** Fix uninstaller does not gain Global state ([#6430](https://github.com/ScoopInstaller/Scoop/issues/6430))
|
||||
- **scoop-depends-tests:** Mocking `USE_EXTERNAL_7ZIP` as $false to avoding error when it is $true ([#6431](https://github.com/ScoopInstaller/Scoop/issues/6431))
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
- **download:** Move download-related functions to 'download.ps1' ([#6095](https://github.com/ScoopInstaller/Scoop/issues/6095))
|
||||
- **Get-Manifest:** Select actual source for manifest ([#6142](https://github.com/ScoopInstaller/Scoop/issues/6142))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
- **shim:** Update kiennq-shim to v3.1.2 ([#6261](https://github.com/ScoopInstaller/Scoop/issues/6261))
|
||||
|
||||
## [v0.5.2](https://github.com/ScoopInstaller/Scoop/compare/v0.5.1...v0.5.2) - 2024-07-26
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **scoop-alias:** Fix 'Option --verbose not recognized.' ([#6062](https://github.com/ScoopInstaller/Scoop/issues/6062))
|
||||
- **scoop-hold:** Use 'foreach' loop to allow 'continue' statement ([#6078](https://github.com/ScoopInstaller/Scoop/issues/6078))
|
||||
- **core:** Use 'Join-Path' to construct cache file path ([#6079](https://github.com/ScoopInstaller/Scoop/issues/6079))
|
||||
- **json:** Don't serialize jsonpath return if only one result ([#6066](https://github.com/ScoopInstaller/Scoop/issues/6066), [#6073](https://github.com/ScoopInstaller/Scoop/issues/6073))
|
||||
|
||||
### Builds
|
||||
|
||||
- **supporting:** Update Json.Schema to 4.0.1 ([#6072](https://github.com/ScoopInstaller/Scoop/issues/6072))
|
||||
|
||||
## [v0.5.1](https://github.com/ScoopInstaller/Scoop/compare/v0.5.0...v0.5.1) - 2024-07-16
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **scoop-alias:** Pass options correctly ([#6003](https://github.com/ScoopInstaller/Scoop/issues/6003))
|
||||
- **scoop-virustotal:** Adjust `json_path` parameters to retrieve correct analysis stats ([#6044](https://github.com/ScoopInstaller/Scoop/issues/6044))
|
||||
- **bucket:** Implement error handling for failed bucket addition ([#6051](https://github.com/ScoopInstaller/Scoop/issues/6051))
|
||||
- **database:** Fix compatibility with Windows PowerShell ([#6045](https://github.com/ScoopInstaller/Scoop/issues/6045))
|
||||
- **install:** Expand `env_set` items before setting Environment Variables ([#6050](https://github.com/ScoopInstaller/Scoop/issues/6050))
|
||||
- **install:** Fix parsing error when installing multiple apps w/ specific version ([#6039](https://github.com/ScoopInstaller/Scoop/issues/6039))
|
||||
|
||||
## [v0.5.0](https://github.com/ScoopInstaller/Scoop/compare/v0.4.2...v0.5.0) - 2024-07-01
|
||||
|
||||
### Features
|
||||
|
||||
- **scoop-search:** Use SQLite for caching apps to speed up local search ([#5851](https://github.com/ScoopInstaller/Scoop/issues/5851), [#5918](https://github.com/ScoopInstaller/Scoop/issues/5918), [#5946](https://github.com/ScoopInstaller/Scoop/issues/5946), [#5949](https://github.com/ScoopInstaller/Scoop/issues/5949), [#5955](https://github.com/ScoopInstaller/Scoop/issues/5955), [#5966](https://github.com/ScoopInstaller/Scoop/issues/5966), [#5967](https://github.com/ScoopInstaller/Scoop/issues/5967), [#5981](https://github.com/ScoopInstaller/Scoop/issues/5981))
|
||||
- **core:** New cache filename format ([#5929](https://github.com/ScoopInstaller/Scoop/issues/5929), [#5944](https://github.com/ScoopInstaller/Scoop/issues/5944))
|
||||
- **decompress:** Use innounp-unicode as default Inno Setup Unpacker ([#6028](https://github.com/ScoopInstaller/Scoop/issues/6028))
|
||||
- **install:** Added the ability to install specific version of app from URL/file link ([#5988](https://github.com/ScoopInstaller/Scoop/issues/5988))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **scoop-download|install|update:** Use consistent options ([#5956](https://github.com/ScoopInstaller/Scoop/issues/5956))
|
||||
- **scoop-info:** Fix download size estimating ([#5958](https://github.com/ScoopInstaller/Scoop/issues/5958))
|
||||
- **scoop-search:** Catch error of parsing invalid manifest ([#5930](https://github.com/ScoopInstaller/Scoop/issues/5930))
|
||||
- **checkver:** Correct variable 'regex' to 'regexp' ([#5993](https://github.com/ScoopInstaller/Scoop/issues/5993))
|
||||
- **checkver:** Correct error messages ([#6024](https://github.com/ScoopInstaller/Scoop/issues/6024))
|
||||
- **core:** Search for Git executable instead of any cmdlet ([#5998](https://github.com/ScoopInstaller/Scoop/issues/5998))
|
||||
- **core:** Use correct path in 'bash' ([#6006](https://github.com/ScoopInstaller/Scoop/issues/6006))
|
||||
- **core:** Limit the number of commands to get when search for git executable ([#6013](https://github.com/ScoopInstaller/Scoop/issues/6013))
|
||||
- **decompress:** Match `extract_dir`/`extract_to` and archives ([#5983](https://github.com/ScoopInstaller/Scoop/issues/5983))
|
||||
- **json:** Serialize jsonpath return ([#5921](https://github.com/ScoopInstaller/Scoop/issues/5921))
|
||||
- **shim:** Restore original path for JAR cmd ([#6030](https://github.com/ScoopInstaller/Scoop/issues/6030))
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
- **decompress:** Use 7zip to extract Zstd archive ([#5973](https://github.com/ScoopInstaller/Scoop/issues/5973))
|
||||
- **install:** Separate archive extraction from downloader ([#5951](https://github.com/ScoopInstaller/Scoop/issues/5951))
|
||||
- **install:** Replace 'run_(un)installer()' with 'Invoke-Installer()' ([#5968](https://github.com/ScoopInstaller/Scoop/issues/5968), [#5971](https://github.com/ScoopInstaller/Scoop/issues/5971))
|
||||
|
||||
## [v0.4.2](https://github.com/ScoopInstaller/Scoop/compare/v0.4.1...v0.4.2) - 2024-05-14
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **autoupdate:** Copy `PSCustomObject`-type properties within `substitute()` to prevent reference changes ([#5934](https://github.com/ScoopInstaller/Scoop/issues/5934), [#5962](https://github.com/ScoopInstaller/Scoop/issues/5962))
|
||||
- **core:** Fix `Invoke-ExternalCommand` quoting rules ([#5945](https://github.com/ScoopInstaller/Scoop/issues/5945))
|
||||
- **system:** Fix argument passing to `Split-PathLikeEnvVar()` in deprecated `strip_path()` ([#5937](https://github.com/ScoopInstaller/Scoop/issues/5937))
|
||||
|
||||
## [v0.4.1](https://github.com/ScoopInstaller/Scoop/compare/v0.4.0...v0.4.1) - 2024-04-25
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **core:** Fix `Invoke-ExternalCommand` regression ([#5923](https://github.com/ScoopInstaller/Scoop/issues/5923))
|
||||
|
||||
## [v0.4.0](https://github.com/ScoopInstaller/Scoop/compare/v0.3.1...v0.4.0) - 2024-04-18
|
||||
|
||||
### Features
|
||||
|
||||
- **scoop-update:** Add support for parallel syncing buckets in PowerShell 7 and improve output ([#5122](https://github.com/ScoopInstaller/Scoop/issues/5122))
|
||||
- **bucket:** Switch nirsoft bucket to ScoopInstaller/Nirsoft ([#5328](https://github.com/ScoopInstaller/Scoop/issues/5328))
|
||||
- **bucket:** Make official buckets higher priority ([#5398](https://github.com/ScoopInstaller/Scoop/issues/5398))
|
||||
- **config:** Support portable config file ([#5369](https://github.com/ScoopInstaller/Scoop/issues/5369))
|
||||
- **core:** Add `-Quiet` switch for `Invoke-ExternalCommand` ([#5346](https://github.com/ScoopInstaller/Scoop/issues/5346))
|
||||
- **core:** Allow global install of PowerShell modules ([#5611](https://github.com/ScoopInstaller/Scoop/issues/5611))
|
||||
- **path:** Isolate Scoop apps' PATH ([#5840](https://github.com/ScoopInstaller/Scoop/issues/5840))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **scoop-alias:** Prevent overwrite existing file when adding alias ([#5577](https://github.com/ScoopInstaller/Scoop/issues/5577))
|
||||
- **scoop-checkup:** Skip defender check in Windows Sandbox ([#5519](https://github.com/ScoopInstaller/Scoop/issues/5519))
|
||||
- **scoop-checkup:** Change the message level of helpers from ERROR to WARN ([#5614](https://github.com/ScoopInstaller/Scoop/issues/5614))
|
||||
- **scoop-checkup:** Don't throw 7zip error when external 7zip is used ([#5703](https://github.com/ScoopInstaller/Scoop/issues/5703))
|
||||
- **scoop-(un)hold:** Correct output the messages when manifest not found, (already|not) held ([#5519](https://github.com/ScoopInstaller/Scoop/issues/5519))
|
||||
- **scoop-info:** Fix errors in file size collection when `--verbose` ([#5352](https://github.com/ScoopInstaller/Scoop/issues/5352))
|
||||
- **scoop-reset:** Don't abort when multiple apps are passed and an app is running ([#5687](https://github.com/ScoopInstaller/Scoop/issues/5687))
|
||||
- **scoop-update:** Change error message to a better instruction ([#5677](https://github.com/ScoopInstaller/Scoop/issues/5677))
|
||||
- **scoop-virustotal:** Fix `scoop-virustotal` when `--all` has been passed without app ([#5593](https://github.com/ScoopInstaller/Scoop/issues/5593))
|
||||
- **scoop-virustotal:** Fix the issue that escape character not available in PowerShell 5.1 ([#5870](https://github.com/ScoopInstaller/Scoop/issues/5870))
|
||||
- **autoupdate:** Fix file hash extraction ([#5295](https://github.com/ScoopInstaller/Scoop/issues/5295))
|
||||
- **autoupdate:** Fix bug that 'WebClient' doesn't auto-extract 'gzip' ([#5901](https://github.com/ScoopInstaller/Scoop/issues/5901))
|
||||
- **buckets:** Avoid error messages for unexpected dir ([#5549](https://github.com/ScoopInstaller/Scoop/issues/5549))
|
||||
- **config:** Warn users about misconfigured GitHub token ([#5777](https://github.com/ScoopInstaller/Scoop/issues/5777))
|
||||
- **core:** Fix scripts' calling parameters ([#5365](https://github.com/ScoopInstaller/Scoop/issues/5365))
|
||||
- **core:** Fix `is_in_dir` under Unix ([#5391](https://github.com/ScoopInstaller/Scoop/issues/5391))
|
||||
- **core:** Rewrite config file when needed ([#5439](https://github.com/ScoopInstaller/Scoop/issues/5439))
|
||||
- **core:** Prevents leaking HTTP(S)_PROXY env vars to current sessions after Invoke-Git in parallel execution ([#5436](https://github.com/ScoopInstaller/Scoop/issues/5436))
|
||||
- **core:** Handle scoop aliases and broken(edited,copied) shim ([#5551](https://github.com/ScoopInstaller/Scoop/issues/5551))
|
||||
- **core:** Avoid error messages when deleting non-existent environment variable ([#5547](https://github.com/ScoopInstaller/Scoop/issues/5547))
|
||||
- **core:** Use relative path as fallback of `$scoopdir` ([#5544](https://github.com/ScoopInstaller/Scoop/issues/5544))
|
||||
- **core:** Fix detection of Git ([#5545](https://github.com/ScoopInstaller/Scoop/issues/5545))
|
||||
- **core:** Do not call `scoop` externally from inside the code ([#5695](https://github.com/ScoopInstaller/Scoop/issues/5695))
|
||||
- **core:** Fix arguments parsing method of `Invoke-ExternalCommand()` ([#5839](https://github.com/ScoopInstaller/Scoop/issues/5839))
|
||||
- **decompress:** Exclude '*.nsis' that may cause error ([#5294](https://github.com/ScoopInstaller/Scoop/issues/5294))
|
||||
- **decompress:** Remove unused parent dir w/ 'extract_dir' ([#5682](https://github.com/ScoopInstaller/Scoop/issues/5682))
|
||||
- **decompress:** Use `wix.exe` in WiX Toolset v4+ as primary extractor of `Expand-DarkArchive()` ([#5871](https://github.com/ScoopInstaller/Scoop/issues/5871))
|
||||
- **env:** Avoid automatic expansion of `%%` in env ([#5395](https://github.com/ScoopInstaller/Scoop/issues/5395), [#5452](https://github.com/ScoopInstaller/Scoop/issues/5452), [#5631](https://github.com/ScoopInstaller/Scoop/issues/5631))
|
||||
- **getopt:** Stop split arguments in `getopt()` and ensure array by explicit arguments type ([#5326](https://github.com/ScoopInstaller/Scoop/issues/5326))
|
||||
- **install:** Fix download from private GitHub repositories ([#5361](https://github.com/ScoopInstaller/Scoop/issues/5361))
|
||||
- **install:** Avoid error when unlinking non-existent junction/hardlink ([#5552](https://github.com/ScoopInstaller/Scoop/issues/5552))
|
||||
- **manifest:** Correct source of manifest ([#5575](https://github.com/ScoopInstaller/Scoop/issues/5575))
|
||||
- **shim:** Remove console window for GUI applications ([#5559](https://github.com/ScoopInstaller/Scoop/issues/5559))
|
||||
- **shim:** Use bash executable directly ([#5433](https://github.com/ScoopInstaller/Scoop/issues/5433))
|
||||
- **shim:** Check literal path in `Get-ShimPath` ([#5680](https://github.com/ScoopInstaller/Scoop/issues/5680))
|
||||
- **shim:** Avoid unexpected output of `list` subcommand ([#5681](https://github.com/ScoopInstaller/Scoop/issues/5681))
|
||||
- **shim:** Allow GUI applications to attach to the shell's console when launched using the GUI shim ([#5721](https://github.com/ScoopInstaller/Scoop/issues/5721))
|
||||
- **shim:** Run JAR file from app's root directory ([#5872](https://github.com/ScoopInstaller/Scoop/issues/5872))
|
||||
- **shortcuts:** Output correctly formatted path ([#5333](https://github.com/ScoopInstaller/Scoop/issues/5333))
|
||||
- **update/uninstall:** Remove items from PATH correctly ([#5833](https://github.com/ScoopInstaller/Scoop/issues/5833))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
- **scoop-search:** Improve performance for local search ([#5644](https://github.com/ScoopInstaller/Scoop/issues/5644))
|
||||
- **scoop-update:** Check for running process before wasting time on download ([#5799](https://github.com/ScoopInstaller/Scoop/issues/5799))
|
||||
- **decompress:** Disable progress bar to improve `Expand-Archive` performance ([#5410](https://github.com/ScoopInstaller/Scoop/issues/5410))
|
||||
- **shim:** Update kiennq-shim to v3.1.1 ([#5841](https://github.com/ScoopInstaller/Scoop/issues/5841), [#5847](https://github.com/ScoopInstaller/Scoop/issues/5847))
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
- **scoop-download:** Output more detailed manifest information ([#5277](https://github.com/ScoopInstaller/Scoop/issues/5277))
|
||||
- **core:** Cleanup some old codes, e.g., msi section and config migration ([#5715](https://github.com/ScoopInstaller/Scoop/issues/5715), [#5824](https://github.com/ScoopInstaller/Scoop/issues/5824))
|
||||
- **core:** Rewrite and separate path-related functions to `system.ps1` ([#5836](https://github.com/ScoopInstaller/Scoop/issues/5836), [#5858](https://github.com/ScoopInstaller/Scoop/issues/5858), [#5864](https://github.com/ScoopInstaller/Scoop/issues/5864))
|
||||
- **core:** Get rid of 'fullpath' ([#3533](https://github.com/ScoopInstaller/Scoop/issues/3533))
|
||||
- **git:** Use Invoke-Git() with direct path to git.exe to prevent spawning shim subprocesses ([#5122](https://github.com/ScoopInstaller/Scoop/issues/5122), [#5375](https://github.com/ScoopInstaller/Scoop/issues/5375))
|
||||
- **helper:** Remove 7zip's fallback '7zip-zstd' ([#5548](https://github.com/ScoopInstaller/Scoop/issues/5548))
|
||||
- **shim:** Remove CS shim codebase ([#5903](https://github.com/ScoopInstaller/Scoop/issues/5903))
|
||||
|
||||
### Builds
|
||||
|
||||
- **checkver:** Read the private_host config variable ([#5381](https://github.com/ScoopInstaller/Scoop/issues/5381))
|
||||
- **supporting:** Update Json to 13.0.3, Json.Schema to 3.0.15 ([#5835](https://github.com/ScoopInstaller/Scoop/issues/5835))
|
||||
|
||||
### Continuous Integration
|
||||
|
||||
- **dependabot:** Add dependabot.yml for GitHub Actions ([#5377](https://github.com/ScoopInstaller/Scoop/issues/5377))
|
||||
- **module:** Update 'psmodulecache' version to 'main' ([#5828](https://github.com/ScoopInstaller/Scoop/issues/5828))
|
||||
|
||||
### Tests
|
||||
|
||||
- **bucket:** Skip manifest validation if no manifest changes ([#5270](https://github.com/ScoopInstaller/Scoop/issues/5270))
|
||||
|
||||
### Documentation
|
||||
|
||||
- **scoop-info:** Fix help message([#5445](https://github.com/ScoopInstaller/Scoop/issues/5445))
|
||||
- **readme:** Improve documentation language ([#5638](https://github.com/ScoopInstaller/Scoop/issues/5638))
|
||||
|
||||
## [v0.3.1](https://github.com/ScoopInstaller/Scoop/compare/v0.3.0...v0.3.1) - 2022-11-15
|
||||
|
||||
### Features
|
||||
|
||||
- **config:** Allow Scoop to check if apps versioned as 'nightly' are outdated ([#5166](https://github.com/ScoopInstaller/Scoop/issues/5166))
|
||||
- **checkup:** Add Windows Developer Mode check ([#5233](https://github.com/ScoopInstaller/Scoop/issues/5233))
|
||||
- **bucket:** Add 'sysinternals' bucket to known ([#5237](https://github.com/ScoopInstaller/Scoop/issues/5237))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **decompress:** Use PS's default 'Expand-Archive()' ([#5185](https://github.com/ScoopInstaller/Scoop/issues/5185))
|
||||
- **hash:** Fix SourceForge's hash extraction ([#5189](https://github.com/ScoopInstaller/Scoop/issues/5189))
|
||||
- **decompress:** Trim ending '/' ([#5195](https://github.com/ScoopInstaller/Scoop/issues/5195))
|
||||
- **shim:** Exit if shim creating failed 'cause no git ([#5225](https://github.com/ScoopInstaller/Scoop/issues/5225))
|
||||
- **scoop-import:** Add correct architecture argument ([#5210](https://github.com/ScoopInstaller/Scoop/issues/5210))
|
||||
- **scoop-config:** Output `[DateTime]` as `[String]` ([#5232](https://github.com/ScoopInstaller/Scoop/issues/5232))
|
||||
- **shim:** fixed shim add bug related to Resolve-Path ([#5492](https://github.com/ScoopInstaller/Scoop/issues/5492))
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
- **hash:** Use `Get-FileHash()` directly ([#5177](https://github.com/ScoopInstaller/Scoop/issues/5177))
|
||||
- **installer:** Drop the old installer ([#5186](https://github.com/ScoopInstaller/Scoop/issues/5186))
|
||||
- **unix:** Remove `unix.ps1` ([#5235](https://github.com/ScoopInstaller/Scoop/issues/5235))
|
||||
|
||||
### Builds
|
||||
|
||||
- **auto-pr:** Add `CommitMessageFormat` option ([#5171](https://github.com/ScoopInstaller/Scoop/issues/5171))
|
||||
- **checkver:** Support XML default namespace ([#5191](https://github.com/ScoopInstaller/Scoop/issues/5191))
|
||||
- **pssa:** Remove unused 'ExcludeRules' ([#5201](https://github.com/ScoopInstaller/Scoop/issues/5201))
|
||||
- **schema:** Add 'installer' and 'shortcuts' to 'autoupdate' ([#5220](https://github.com/ScoopInstaller/Scoop/issues/5220))
|
||||
- **checkhashes:** Use correct version number if `UseCache` ([#5240](https://github.com/ScoopInstaller/Scoop/issues/5240))
|
||||
|
||||
### Continuous Integration
|
||||
|
||||
- **module:** Update modules version ([#5209](https://github.com/ScoopInstaller/Scoop/issues/5209))
|
||||
|
||||
### Tests
|
||||
|
||||
- **unix:** Fix tests in Linux and macOS ([#5179](https://github.com/ScoopInstaller/Scoop/issues/5179))
|
||||
- **pester:** Update to Pester 5 ([#5222](https://github.com/ScoopInstaller/Scoop/issues/5222))
|
||||
- **bucket:** Use BuildHelpers' EnvVars ([#5226](https://github.com/ScoopInstaller/Scoop/issues/5226))
|
||||
|
||||
### Documentation
|
||||
|
||||
- **scoop-cat:** Fix help message([#5224](https://github.com/ScoopInstaller/Scoop/issues/5224))
|
||||
|
||||
## [v0.3.0](https://github.com/ScoopInstaller/Scoop/compare/v0.2.4...v0.3.0) - 2022-10-10
|
||||
|
||||
### Features
|
||||
|
||||
- **install:** Add support for ARM64 architecture ([#5154](https://github.com/ScoopInstaller/Scoop/issues/5154))
|
||||
- **install:** Show the running process ([#5102](https://github.com/ScoopInstaller/Scoop/issues/5102))
|
||||
- **getopt:** Support option terminator (`--`) ([#5121](https://github.com/ScoopInstaller/Scoop/issues/5121))
|
||||
- **subdir:** Allow subdir in 'bucket' ([#5119](https://github.com/ScoopInstaller/Scoop/issues/5119))
|
||||
- **scoop-config:** Allow 'hold_update_until' be set manually ([#5100](https://github.com/ScoopInstaller/Scoop/issues/5100))
|
||||
- **scoop-(un)hold:** Support `scoop (un)hold scoop` ([#5089](https://github.com/ScoopInstaller/Scoop/issues/5089))
|
||||
- **scoop-update:** Stash uncommitted changes before update ([#5091](https://github.com/ScoopInstaller/Scoop/issues/5091))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **config:** Change config option to snake_case in file and SCREAMING_CASE in code ([#5116](https://github.com/ScoopInstaller/Scoop/issues/5116))
|
||||
- **jsonpath:** Prevent converting date string to DateTime in JSONPath ([#5130](https://github.com/ScoopInstaller/Scoop/issues/5130))
|
||||
- **psmodule:** Remove folder recursively when unlinking previous module path ([#5127](https://github.com/ScoopInstaller/Scoop/issues/5127))
|
||||
- **scoop-update:** Add `uninstall_psmodule` to update process ([#5136](https://github.com/ScoopInstaller/Scoop/issues/5136))
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
- **download:** Rename `dl()` to `Invoke-Download()` ([#5143](https://github.com/ScoopInstaller/Scoop/issues/5143))
|
||||
- **path:** Use 'Convert-Path()' instead of 'Resolve-Path()' ([#5109](https://github.com/ScoopInstaller/Scoop/issues/5109))
|
||||
- **scoop-shim:** Use `getopt` to parse arguments ([#5125](https://github.com/ScoopInstaller/Scoop/issues/5125))
|
||||
|
||||
### Builds
|
||||
|
||||
- **checkver:** Implement SourceForge checkver functionality ([#5113](https://github.com/ScoopInstaller/Scoop/issues/5113), [#5163](https://github.com/ScoopInstaller/Scoop/issues/5163))
|
||||
- **checkurls:** Allow checking URLs from private_hosts ([#5152](https://github.com/ScoopInstaller/Scoop/issues/5152))
|
||||
- **schema:** Set manifest schema to be stricter ([#5093](https://github.com/ScoopInstaller/Scoop/issues/5093))
|
||||
- **vscode:** Tweak VSCode setting ([#5149](https://github.com/ScoopInstaller/Scoop/issues/5149))
|
||||
|
||||
## [v0.2.4](https://github.com/ScoopInstaller/Scoop/compare/v0.2.3...v0.2.4) - 2022-08-08
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
# The PowerShell Script Analyzer will generate a warning
|
||||
# diagnostic record for this file due to a bug -
|
||||
# https://github.com/PowerShell/PSScriptAnalyzer/issues/472
|
||||
@{
|
||||
# Only diagnostic records of the specified severity will be generated.
|
||||
# Uncomment the following line if you only want Errors and Warnings but
|
||||
@@ -26,12 +23,6 @@
|
||||
# will be excluded.
|
||||
ExcludeRules = @(
|
||||
# Currently Scoop widely uses Write-Host to output colored text.
|
||||
'PSAvoidUsingWriteHost',
|
||||
# Temporarily allow uses of Invoke-Expression,
|
||||
# this command is used by some core functions and hard to be removed.
|
||||
'PSAvoidUsingInvokeExpression',
|
||||
# PSUseDeclaredVarsMoreThanAssignments doesn't currently work due to:
|
||||
# https://github.com/PowerShell/PSScriptAnalyzer/issues/636
|
||||
'PSUseDeclaredVarsMoreThanAssignments'
|
||||
'PSAvoidUsingWriteHost'
|
||||
)
|
||||
}
|
||||
|
||||
109
README.md
109
README.md
@@ -1,17 +1,17 @@
|
||||
<p align="center">
|
||||
<h1 align="center">Scoop</h1>
|
||||
|
||||
<!--<img src="scoop.png" alt="Long live Scoop!"/>-->
|
||||
<h1 align="center">Scoop</h1>
|
||||
</p>
|
||||
<p align="center">
|
||||
<b><a href="https://github.com/ScoopInstaller/Scoop#what-does-scoop-do">Features</a></b>
|
||||
|
|
||||
<b><a href="https://github.com/ScoopInstaller/Scoop#installation">Installation</a></b>
|
||||
|
|
||||
<b><a href="https://github.com/ScoopInstaller/Scoop/wiki">Documentation</a></b>
|
||||
<a href="https://github.com/ScoopInstaller/Scoop#what-does-scoop-do">Features</a>
|
||||
|
|
||||
<a href="https://github.com/ScoopInstaller/Scoop#installation">Installation</a>
|
||||
|
|
||||
<a href="https://github.com/ScoopInstaller/Scoop/wiki">Documentation</a>
|
||||
</p>
|
||||
|
||||
- - -
|
||||
<p align="center" >
|
||||
---
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/ScoopInstaller/Scoop">
|
||||
<img src="https://img.shields.io/github/languages/code-size/ScoopInstaller/Scoop.svg" alt="Code Size" />
|
||||
</a>
|
||||
@@ -36,43 +36,48 @@ Scoop is a command-line installer for Windows.
|
||||
|
||||
## What does Scoop do?
|
||||
|
||||
Scoop installs programs from the command line with a minimal amount of friction. It:
|
||||
Scoop installs apps from the command line with a minimal amount of friction. It:
|
||||
|
||||
- Eliminates permission popup windows
|
||||
- Hides GUI wizard-style installers
|
||||
- Prevents PATH pollution from installing lots of programs
|
||||
- Avoids unexpected side-effects from installing and uninstalling programs
|
||||
- Finds and installs dependencies automatically
|
||||
- Performs all the extra setup steps itself to get a working program
|
||||
- Eliminates [User Account Control](https://learn.microsoft.com/windows/security/application-security/application-control/user-account-control/) (UAC) prompt notifications.
|
||||
- Hides the graphical user interface (GUI) of wizard-style installers.
|
||||
- Prevents polluting the `PATH` environment variable. Normally, this variable gets cluttered as different apps are installed on the device.
|
||||
- Avoids unexpected side effects from installing and uninstalling apps.
|
||||
- Resolves and installs dependencies automatically.
|
||||
- Performs all the necessary steps to get an app to a working state.
|
||||
|
||||
Scoop is very scriptable, so you can run repeatable setups to get your environment just the way you like, e.g.:
|
||||
Scoop is quite script-friendly. Your environment can become the way you like by using repeatable setups. For example:
|
||||
|
||||
```powershell
|
||||
```console
|
||||
scoop install sudo
|
||||
sudo scoop install 7zip git openssh --global
|
||||
scoop install aria2 curl grep sed less touch
|
||||
scoop install python ruby go perl
|
||||
```
|
||||
|
||||
If you've built software that you'd like others to use, Scoop is an alternative to building an installer (e.g. MSI or InnoSetup) — you just need to zip your program and provide a JSON manifest that describes how to install it.
|
||||
If you have built software that you would like others to use, Scoop is an alternative to building an installer (like MSI or InnoSetup). You just need to compress your app to a `.zip` file and provide a JSON manifest that describes how to install it.
|
||||
|
||||
## Installation
|
||||
|
||||
Run the following command from a **non-admin** PowerShell to install scoop to its default location `C:\Users\<YOUR USERNAME>\scoop`.
|
||||
Run the following commands from a regular (non-admin) PowerShell terminal to install Scoop:
|
||||
|
||||
```powershell
|
||||
iwr -useb get.scoop.sh | iex
|
||||
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
|
||||
Invoke-RestMethod -Uri https://get.scoop.sh | Invoke-Expression
|
||||
```
|
||||
|
||||
Advanced installation instruction and full documentation of the installer are available in [ScoopInstaller/Install](https://github.com/ScoopInstaller/Install). Please create new issues there if you have questions about the installation.
|
||||
**Note**: The first command makes your device allow running the installation and management scripts. This is necessary because Windows 10 client devices restrict execution of any PowerShell scripts by default.
|
||||
|
||||
## [Documentation](https://github.com/ScoopInstaller/Scoop/wiki)
|
||||
It will install Scoop to its default location:
|
||||
|
||||
`C:\Users\<YOUR USERNAME>\scoop`
|
||||
|
||||
You can find the complete documentation about the installer, including advanced installation configurations, in [ScoopInstaller/Install](https://github.com/ScoopInstaller/Install). Please create new issues there if you have questions about the installation.
|
||||
|
||||
## Multi-connection downloads with `aria2`
|
||||
|
||||
Scoop can utilize [`aria2`](https://github.com/aria2/aria2) to use multi-connection downloads. Simply install `aria2` through Scoop and it will be used for all downloads afterward.
|
||||
|
||||
```powershell
|
||||
```console
|
||||
scoop install aria2
|
||||
```
|
||||
|
||||
@@ -90,50 +95,54 @@ You can tweak the following `aria2` settings with the `scoop config` command:
|
||||
|
||||
## Inspiration
|
||||
|
||||
- [Homebrew](http://mxcl.github.io/homebrew/)
|
||||
- [sub](https://github.com/37signals/sub#readme)
|
||||
- [Homebrew](https://brew.sh/)
|
||||
- [Sub](https://signalvnoise.com/posts/3264-automating-with-convention-introducing-sub)
|
||||
|
||||
## What sort of apps can Scoop install?
|
||||
|
||||
The apps that install best with Scoop are commonly called "portable" apps: i.e. compressed program files that run stand-alone when extracted and don't have side-effects like changing the registry or putting files outside the program directory.
|
||||
The apps that are most likely to get installed fine with Scoop are those referred to as "portable" apps. These apps are compressed files which can run standalone after being extracted. This type of apps does not produce side effects like changing the Windows Registry or placing files outside the app directory.
|
||||
|
||||
Since installers are common, Scoop supports them too (and their uninstallers).
|
||||
|
||||
Scoop is also great at handling single-file programs and Powershell scripts. These don't even need to be compressed. See the [runat](https://github.com/ScoopInstaller/Main/blob/master/bucket/runat.json) package for an example: it's really just a GitHub gist.
|
||||
Scoop also supports installer files and their uninstallation methods. Likewise, it can handle single-file apps and PowerShell scripts. These do not even need to be compressed. See the [runat](https://github.com/ScoopInstaller/Main/blob/master/bucket/runat.json) package for an example: it is simply a GitHub gist.
|
||||
|
||||
### Contribute to this project
|
||||
|
||||
If you'd like to improve Scoop by adding features or fixing bugs, please read our [Contributing Guide](https://github.com/ScoopInstaller/.github/blob/main/.github/CONTRIBUTING.md).
|
||||
If you would like to improve Scoop by adding features or fixing bugs, please read our [Contributing Guide](https://github.com/ScoopInstaller/.github/blob/main/.github/CONTRIBUTING.md).
|
||||
|
||||
### Support this project
|
||||
|
||||
If you find Scoop useful and would like to support ongoing development and maintenance, here's how:
|
||||
If you find Scoop useful and would like to support the ongoing development and maintenance of this project, you can donate here:
|
||||
|
||||
- [PayPal](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=DM2SUH9EUXSKJ) (one-time donation)
|
||||
- [PayPal](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=DM2SUH9EUXSKJ) (one-time donations)
|
||||
|
||||
## Known application buckets
|
||||
|
||||
The following buckets are known to scoop:
|
||||
The following buckets are known to Scoop:
|
||||
|
||||
- [main](https://github.com/ScoopInstaller/Main) - Default bucket for the most common (mostly CLI) apps
|
||||
- [extras](https://github.com/ScoopInstaller/Extras) - Apps that don't fit the main bucket's [criteria](https://github.com/ScoopInstaller/Scoop/wiki/Criteria-for-including-apps-in-the-main-bucket)
|
||||
- [games](https://github.com/Calinou/scoop-games) - Open source/freeware games and game-related tools
|
||||
- [nerd-fonts](https://github.com/matthewjberger/scoop-nerd-fonts) - Nerd Fonts
|
||||
- [nirsoft](https://github.com/kodybrown/scoop-nirsoft) - Almost all of the [250+](https://rasa.github.io/scoop-directory/by-apps#kodybrown_scoop-nirsoft) apps from [Nirsoft](https://nirsoft.net)
|
||||
- [java](https://github.com/ScoopInstaller/Java) - A collection of Java development kits (JDKs), Java runtime engines (JREs), Java's virtual machine debugging tools and Java based runtime engines.
|
||||
- [nonportable](https://github.com/ScoopInstaller/Nonportable) - Non-portable apps (may require UAC)
|
||||
- [php](https://github.com/ScoopInstaller/PHP) - Installers for most versions of PHP
|
||||
- [versions](https://github.com/ScoopInstaller/Versions) - Alternative versions of apps found in other buckets
|
||||
- [main](https://github.com/ScoopInstaller/Main) - Default bucket which contains popular non-GUI apps.
|
||||
- [extras](https://github.com/ScoopInstaller/Extras) - Apps that do not fit the main bucket's [criteria](https://github.com/ScoopInstaller/Scoop/wiki/Criteria-for-including-apps-in-the-main-bucket).
|
||||
- [games](https://github.com/Calinou/scoop-games) - Open-source and freeware video games and game-related tools.
|
||||
- [nerd-fonts](https://github.com/matthewjberger/scoop-nerd-fonts) - Nerd Fonts.
|
||||
- [nirsoft](https://github.com/ScoopInstaller/Nirsoft) - A collection of over 250+ apps from [Nirsoft](https://nirsoft.net).
|
||||
- [sysinternals](https://github.com/niheaven/scoop-sysinternals) - The Sysinternals suite from [Microsoft](https://learn.microsoft.com/sysinternals/).
|
||||
- [java](https://github.com/ScoopInstaller/Java) - A collection of Java development kits (JDKs) and Java runtime engines (JREs), Java's virtual machine debugging tools and Java based runtime engines.
|
||||
- [nonportable](https://github.com/ScoopInstaller/Nonportable) - Non-portable apps (may trigger UAC prompts).
|
||||
- [php](https://github.com/ScoopInstaller/PHP) - Installers for most versions of PHP.
|
||||
- [versions](https://github.com/ScoopInstaller/Versions) - Alternative versions of apps found in other buckets.
|
||||
|
||||
The main bucket is installed by default. To add any of the other buckets, type:
|
||||
```
|
||||
scoop bucket add bucketname
|
||||
```
|
||||
For example, to add the extras bucket, type:
|
||||
The `main` bucket is installed by default. You can make use of more buckets by typing:
|
||||
|
||||
```console
|
||||
scoop bucket add <name>
|
||||
```
|
||||
|
||||
For example, to add the `extras` bucket, type:
|
||||
|
||||
```console
|
||||
scoop bucket add extras
|
||||
```
|
||||
|
||||
You would be able to install apps from the `extras` bucket now.
|
||||
|
||||
## Other application buckets
|
||||
|
||||
Many other application buckets hosted on Github can be found in the [Scoop Directory](https://rasa.github.io/scoop-directory/) or via [other search engines](https://rasa.github.io/scoop-directory/#other-search-engines).
|
||||
Many other application buckets hosted on GitHub can be found on [ScoopSearch](https://scoop.sh/) or via [other search engines](https://rasa.github.io/scoop-directory/#other-search-engines).
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
.PARAMETER App
|
||||
Manifest name to search.
|
||||
Placeholders are supported.
|
||||
.PARAMETER CommitMessageFormat
|
||||
The format of the commit message.
|
||||
<app> will be replaced with the file name of manifest.
|
||||
<version> will be replaced with the version of the latest manifest.
|
||||
.PARAMETER Dir
|
||||
The directory where to search for manifests.
|
||||
.PARAMETER Push
|
||||
@@ -43,6 +47,7 @@ param(
|
||||
[String] $Upstream,
|
||||
[String] $OriginBranch = 'master',
|
||||
[String] $App = '*',
|
||||
[String] $CommitMessageFormat = '<app>: Update to version <version>',
|
||||
[ValidateScript( {
|
||||
if (!(Test-Path $_ -Type Container)) {
|
||||
throw "$_ is not a directory!"
|
||||
@@ -61,12 +66,11 @@ param(
|
||||
|
||||
. "$PSScriptRoot\..\lib\manifest.ps1"
|
||||
. "$PSScriptRoot\..\lib\json.ps1"
|
||||
. "$PSScriptRoot\..\lib\unix.ps1"
|
||||
|
||||
if ($App -ne '*' -and (Test-Path $App -PathType Leaf)) {
|
||||
$Dir = Split-Path $App
|
||||
} elseif ($Dir) {
|
||||
$Dir = Resolve-Path $Dir
|
||||
$Dir = Convert-Path $Dir
|
||||
} else {
|
||||
throw "'-Dir' parameter required if '-App' is not a filepath!"
|
||||
}
|
||||
@@ -87,7 +91,7 @@ Optional options:
|
||||
exit 0
|
||||
}
|
||||
|
||||
if (is_unix) {
|
||||
if ($IsLinux -or $IsMacOS) {
|
||||
if (!(which hub)) {
|
||||
Write-Host "Please install hub ('brew install hub' or visit: https://hub.github.com/)" -ForegroundColor Yellow
|
||||
exit 1
|
||||
@@ -110,7 +114,7 @@ function execute($cmd) {
|
||||
return $output
|
||||
}
|
||||
|
||||
function pull_requests($json, [String] $app, [String] $upstream, [String] $manifest) {
|
||||
function pull_requests($json, [String] $app, [String] $upstream, [String] $manifest, [String] $commitMessage) {
|
||||
$version = $json.version
|
||||
$homepage = $json.homepage
|
||||
$branch = "manifest/$app-$version"
|
||||
@@ -127,7 +131,7 @@ function pull_requests($json, [String] $app, [String] $upstream, [String] $manif
|
||||
Write-Host "Creating update $app ($version) ..." -ForegroundColor DarkCyan
|
||||
execute "hub checkout -b $branch"
|
||||
execute "hub add $manifest"
|
||||
execute "hub commit -m '${app}: Update to version $version'"
|
||||
execute "hub commit -m '$commitMessage"
|
||||
Write-Host "Pushing update $app ($version) ..." -ForegroundColor DarkCyan
|
||||
execute "hub push origin $branch"
|
||||
|
||||
@@ -142,7 +146,7 @@ function pull_requests($json, [String] $app, [String] $upstream, [String] $manif
|
||||
Write-Host "hub pull-request -m '<msg>' -b '$upstream' -h '$branch'" -ForegroundColor Green
|
||||
|
||||
$msg = @"
|
||||
$app`: Update to version $version
|
||||
$commitMessage
|
||||
|
||||
Hello lovely humans,
|
||||
a new version of [$app]($homepage) is available.
|
||||
@@ -155,7 +159,7 @@ a new version of [$app]($homepage) is available.
|
||||
hub pull-request -m "$msg" -b "$upstream" -h "$branch"
|
||||
if ($LASTEXITCODE -gt 0) {
|
||||
execute 'hub reset'
|
||||
abort "Pull Request failed! (hub pull-request -m '${app}: Update to version $version' -b '$upstream' -h '$branch')"
|
||||
abort "Pull Request failed! (hub pull-request -m '$commitMessage' -b '$upstream' -h '$branch')"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,7 +193,7 @@ hub diff --name-only | ForEach-Object {
|
||||
return
|
||||
}
|
||||
$version = $json.version
|
||||
|
||||
$CommitMessage = $CommitMessageFormat -replace '<app>',$app -replace '<version>',$version
|
||||
if ($Push) {
|
||||
Write-Host "Creating update $app ($version) ..." -ForegroundColor DarkCyan
|
||||
execute "hub add $manifest"
|
||||
@@ -198,12 +202,12 @@ hub diff --name-only | ForEach-Object {
|
||||
$status = execute 'hub status --porcelain -uno'
|
||||
$status = $status | Where-Object { $_ -match "M\s{2}.*$app.json" }
|
||||
if ($status -and $status.StartsWith('M ') -and $status.EndsWith("$app.json")) {
|
||||
execute "hub commit -m '${app}: Update to version $version'"
|
||||
execute "hub commit -m '$commitMessage'"
|
||||
} else {
|
||||
Write-Host "Skipping $app because only LF/CRLF changes were detected ..." -ForegroundColor Yellow
|
||||
}
|
||||
} else {
|
||||
pull_requests $json $app $Upstream $manifest
|
||||
pull_requests $json $app $Upstream $manifest $CommitMessage
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,10 +46,9 @@ param(
|
||||
. "$PSScriptRoot\..\lib\autoupdate.ps1"
|
||||
. "$PSScriptRoot\..\lib\json.ps1"
|
||||
. "$PSScriptRoot\..\lib\versions.ps1"
|
||||
. "$PSScriptRoot\..\lib\install.ps1"
|
||||
. "$PSScriptRoot\..\lib\unix.ps1"
|
||||
. "$PSScriptRoot\..\lib\download.ps1"
|
||||
|
||||
$Dir = Resolve-Path $Dir
|
||||
$Dir = Convert-Path $Dir
|
||||
if ($ForceUpdate) { $Update = $true }
|
||||
# Cleanup
|
||||
if (!$UseCache) { Remove-Item "$cachedir\*HASH_CHECK*" -Force }
|
||||
@@ -60,9 +59,10 @@ function err ([String] $name, [String[]] $message) {
|
||||
}
|
||||
|
||||
$MANIFESTS = @()
|
||||
foreach ($single in Get-ChildItem $Dir "$App.json") {
|
||||
$name = (strip_ext $single.Name)
|
||||
$manifest = parse_json "$Dir\$($single.Name)"
|
||||
foreach ($single in Get-ChildItem $Dir -Filter "$App.json" -Recurse) {
|
||||
$name = $single.BaseName
|
||||
$file = $single.FullName
|
||||
$manifest = parse_json $file
|
||||
|
||||
# Skip nighly manifests, since their hash validation is skipped
|
||||
if ($manifest.version -eq 'nightly') { continue }
|
||||
@@ -79,6 +79,8 @@ foreach ($single in Get-ChildItem $Dir "$App.json") {
|
||||
hash $manifest '64bit' | ForEach-Object { $hashes += $_ }
|
||||
script:url $manifest '32bit' | ForEach-Object { $urls += $_ }
|
||||
hash $manifest '32bit' | ForEach-Object { $hashes += $_ }
|
||||
script:url $manifest 'arm64' | ForEach-Object { $urls += $_ }
|
||||
hash $manifest 'arm64' | ForEach-Object { $hashes += $_ }
|
||||
} else {
|
||||
err $name 'Manifest does not contain URL property.'
|
||||
continue
|
||||
@@ -92,6 +94,7 @@ foreach ($single in Get-ChildItem $Dir "$App.json") {
|
||||
|
||||
$MANIFESTS += @{
|
||||
app = $name
|
||||
file = $file
|
||||
manifest = $manifest
|
||||
urls = $urls
|
||||
hashes = $hashes
|
||||
@@ -110,13 +113,16 @@ foreach ($current in $MANIFESTS) {
|
||||
|
||||
$current.urls | ForEach-Object {
|
||||
$algorithm, $expected = get_hash $current.hashes[$count]
|
||||
$version = 'HASH_CHECK'
|
||||
$tmp = $expected_hash -split ':'
|
||||
if ($UseCache) {
|
||||
$version = $current.manifest.version
|
||||
} else {
|
||||
$version = 'HASH_CHECK'
|
||||
}
|
||||
|
||||
dl_with_cache $current.app $version $_ $null $null -use_cache:$UseCache
|
||||
Invoke-CachedDownload $current.app $version $_ $null $null -use_cache:$UseCache
|
||||
|
||||
$to_check = fullpath (cache_path $current.app $version $_)
|
||||
$actual_hash = compute_hash $to_check $algorithm
|
||||
$to_check = cache_path $current.app $version $_
|
||||
$actual_hash = (Get-FileHash -Path $to_check -Algorithm $algorithm).Hash.ToLower()
|
||||
|
||||
# Append type of algorithm to both expected and actual if it's not sha256
|
||||
if ($algorithm -ne 'sha256') {
|
||||
@@ -140,13 +146,13 @@ foreach ($current in $MANIFESTS) {
|
||||
Write-Host "$($current.app): " -NoNewline
|
||||
Write-Host 'Mismatch found ' -ForegroundColor Red
|
||||
$mismatched | ForEach-Object {
|
||||
$file = fullpath (cache_path $current.app $version $current.urls[$_])
|
||||
Write-Host "`tURL:`t`t$($current.urls[$_])"
|
||||
$file = cache_path $current.app $version $current.urls[$_]
|
||||
Write-Host "`tURL:`t`t$($current.urls[$_])"
|
||||
if (Test-Path $file) {
|
||||
Write-Host "`tFirst bytes:`t$((get_magic_bytes_pretty $file ' ').ToUpper())"
|
||||
Write-Host "`tFirst bytes:`t$((get_magic_bytes_pretty $file ' ').ToUpper())"
|
||||
}
|
||||
Write-Host "`tExpected:`t$($current.hashes[$_])" -ForegroundColor Green
|
||||
Write-Host "`tActual:`t`t$($actuals[$_])" -ForegroundColor Red
|
||||
Write-Host "`tExpected:`t$($current.hashes[$_])" -ForegroundColor Green
|
||||
Write-Host "`tActual:`t`t$($actuals[$_])" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,23 +164,27 @@ foreach ($current in $MANIFESTS) {
|
||||
# Defaults to zero, don't know, which architecture is available
|
||||
$64bit_count = 0
|
||||
$32bit_count = 0
|
||||
$arm64_count = 0
|
||||
|
||||
# 64bit is get, donwloaded and added first
|
||||
if ($platforms.Contains('64bit')) {
|
||||
$64bit_count = $current.manifest.architecture.'64bit'.hash.Count
|
||||
# 64bit is get, donwloaded and added first
|
||||
$current.manifest.architecture.'64bit'.hash = $actuals[0..($64bit_count - 1)]
|
||||
}
|
||||
if ($platforms.Contains('32bit')) {
|
||||
$32bit_count = $current.manifest.architecture.'32bit'.hash.Count
|
||||
$max = $64bit_count + $32bit_count - 1 # Edge case if manifest contains 64bit and 32bit.
|
||||
$current.manifest.architecture.'32bit'.hash = $actuals[($64bit_count)..$max]
|
||||
$current.manifest.architecture.'32bit'.hash = $actuals[($64bit_count)..($64bit_count + $32bit_count - 1)]
|
||||
}
|
||||
if ($platforms.Contains('arm64')) {
|
||||
$arm64_count = $current.manifest.architecture.'arm64'.hash.Count
|
||||
$current.manifest.architecture.'arm64'.hash = $actuals[($64bit_count + $32bit_count)..($64bit_count + $32bit_count + $arm64_count - 1)]
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "Writing updated $($current.app) manifest" -ForegroundColor DarkGreen
|
||||
|
||||
$current.manifest = $current.manifest | ConvertToPrettyJson
|
||||
$path = Resolve-Path "$Dir\$($current.app).json"
|
||||
$path = Convert-Path $current.file
|
||||
[System.IO.File]::WriteAllLines($path, $current.manifest)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,14 +28,14 @@ param(
|
||||
|
||||
. "$PSScriptRoot\..\lib\core.ps1"
|
||||
. "$PSScriptRoot\..\lib\manifest.ps1"
|
||||
. "$PSScriptRoot\..\lib\install.ps1"
|
||||
. "$PSScriptRoot\..\lib\download.ps1"
|
||||
|
||||
$Dir = Resolve-Path $Dir
|
||||
$Dir = Convert-Path $Dir
|
||||
$Queue = @()
|
||||
|
||||
Get-ChildItem $Dir "$App.json" | ForEach-Object {
|
||||
$manifest = parse_json "$Dir\$($_.Name)"
|
||||
$Queue += , @($_.Name, $manifest)
|
||||
Get-ChildItem $Dir -Filter "$App.json" -Recurse | ForEach-Object {
|
||||
$manifest = parse_json $_.FullName
|
||||
$Queue += , @($_.BaseName, $manifest)
|
||||
}
|
||||
|
||||
Write-Host '[' -NoNewLine
|
||||
@@ -62,6 +62,13 @@ function test_dl([String] $url, $cookies) {
|
||||
$wreq.Headers.Add('Cookie', (cookie_header $cookies))
|
||||
}
|
||||
}
|
||||
|
||||
get_config PRIVATE_HOSTS | Where-Object { $_ -ne $null -and $url -match $_.match } | ForEach-Object {
|
||||
(ConvertFrom-StringData -StringData $_.Headers).GetEnumerator() | ForEach-Object {
|
||||
$wreq.Headers[$_.Key] = $_.Value
|
||||
}
|
||||
}
|
||||
|
||||
$wres = $null
|
||||
try {
|
||||
$wres = $wreq.GetResponse()
|
||||
@@ -91,6 +98,7 @@ foreach ($man in $Queue) {
|
||||
} else {
|
||||
script:url $manifest '64bit' | ForEach-Object { $urls += $_ }
|
||||
script:url $manifest '32bit' | ForEach-Object { $urls += $_ }
|
||||
script:url $manifest 'arm64' | ForEach-Object { $urls += $_ }
|
||||
}
|
||||
|
||||
$urls | ForEach-Object {
|
||||
@@ -125,7 +133,7 @@ foreach ($man in $Queue) {
|
||||
Write-Host $failed -NoNewLine -ForegroundColor Red
|
||||
}
|
||||
Write-Host '] ' -NoNewLine
|
||||
Write-Host (strip_ext $name)
|
||||
Write-Host $name
|
||||
|
||||
$errors | ForEach-Object {
|
||||
Write-Host " > $_" -ForegroundColor DarkRed
|
||||
|
||||
143
bin/checkver.ps1
143
bin/checkver.ps1
@@ -73,15 +73,14 @@ param(
|
||||
. "$PSScriptRoot\..\lib\buckets.ps1"
|
||||
. "$PSScriptRoot\..\lib\json.ps1"
|
||||
. "$PSScriptRoot\..\lib\versions.ps1"
|
||||
. "$PSScriptRoot\..\lib\install.ps1" # needed for hash generation
|
||||
. "$PSScriptRoot\..\lib\unix.ps1"
|
||||
. "$PSScriptRoot\..\lib\download.ps1"
|
||||
|
||||
if ($App -ne '*' -and (Test-Path $App -PathType Leaf)) {
|
||||
$Dir = Split-Path $App
|
||||
$files = Get-ChildItem $Dir (Split-Path $App -Leaf)
|
||||
$files = Get-ChildItem $Dir -Filter (Split-Path $App -Leaf)
|
||||
} elseif ($Dir) {
|
||||
$Dir = Resolve-Path $Dir
|
||||
$files = Get-ChildItem $Dir "$App.json"
|
||||
$Dir = Convert-Path $Dir
|
||||
$files = Get-ChildItem $Dir -Filter "$App.json" -Recurse
|
||||
} else {
|
||||
throw "'-Dir' parameter required if '-App' is not a filepath!"
|
||||
}
|
||||
@@ -97,9 +96,10 @@ if ($App -eq '*' -and $Version -ne '') {
|
||||
$Queue = @()
|
||||
$json = ''
|
||||
$files | ForEach-Object {
|
||||
$json = parse_json "$Dir\$($_.Name)"
|
||||
$file = $_.FullName
|
||||
$json = parse_json $file
|
||||
if ($json.checkver) {
|
||||
$Queue += , @($_.Name, $json)
|
||||
$Queue += , @($_.BaseName, $json, $file)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ Get-EventSubscriber | Unregister-Event
|
||||
|
||||
# start all downloads
|
||||
$Queue | ForEach-Object {
|
||||
$name, $json = $_
|
||||
$name, $json, $file = $_
|
||||
|
||||
$substitutions = Get-VersionSubstitution $json.version # 'autoupdate.ps1'
|
||||
|
||||
@@ -121,18 +121,32 @@ $Queue | ForEach-Object {
|
||||
}
|
||||
Register-ObjectEvent $wc downloadDataCompleted -ErrorAction Stop | Out-Null
|
||||
|
||||
$githubRegex = '\/releases\/tag\/(?:v|V)?([\d.]+)'
|
||||
|
||||
$url = $json.homepage
|
||||
# Not Specified
|
||||
if ($json.checkver.url) {
|
||||
$url = $json.checkver.url
|
||||
} else {
|
||||
$url = $json.homepage
|
||||
}
|
||||
$regex = ''
|
||||
|
||||
if ($json.checkver.re) {
|
||||
$regex = $json.checkver.re
|
||||
} elseif ($json.checkver.regex) {
|
||||
$regex = $json.checkver.regex
|
||||
} else {
|
||||
$regex = ''
|
||||
}
|
||||
|
||||
$jsonpath = ''
|
||||
$xpath = ''
|
||||
$replace = ''
|
||||
$useGithubAPI = $false
|
||||
|
||||
# GitHub
|
||||
if ($regex) {
|
||||
$githubRegex = $regex
|
||||
} else {
|
||||
$githubRegex = '/releases/tag/(?:v|V)?([\d.]+)'
|
||||
}
|
||||
if ($json.checkver -eq 'github') {
|
||||
if (!$json.homepage.StartsWith('https://github.com/')) {
|
||||
error "$name checkver expects the homepage to be a github repository"
|
||||
@@ -148,11 +162,38 @@ $Queue | ForEach-Object {
|
||||
if ($json.checkver.PSObject.Properties.Count -eq 1) { $useGithubAPI = $true }
|
||||
}
|
||||
|
||||
if ($json.checkver.re) {
|
||||
$regex = $json.checkver.re
|
||||
# SourceForge
|
||||
if ($regex) {
|
||||
$sourceforgeRegex = $regex
|
||||
} else {
|
||||
$sourceforgeRegex = '(?!\.)([\d.]+)(?<=\d)'
|
||||
}
|
||||
if ($json.checkver.regex) {
|
||||
$regex = $json.checkver.regex
|
||||
if ($json.checkver -eq 'sourceforge') {
|
||||
if ($json.homepage -match '//(sourceforge|sf)\.net/projects/(?<project>[^/]+)(/files/(?<path>[^/]+))?|//(?<project>[^.]+)\.(sourceforge\.(net|io)|sf\.net)') {
|
||||
$project = $Matches['project']
|
||||
$path = $Matches['path']
|
||||
} else {
|
||||
$project = strip_ext $name
|
||||
}
|
||||
$url = "https://sourceforge.net/projects/$project/rss"
|
||||
if ($path) {
|
||||
$url = $url + '?path=/' + $path.TrimStart('/')
|
||||
}
|
||||
$regex = "CDATA\[/$path/.*?$sourceforgeRegex.*?\]".Replace('//', '/')
|
||||
}
|
||||
if ($json.checkver.sourceforge) {
|
||||
if ($json.checkver.sourceforge -is [System.String] -and $json.checkver.sourceforge -match '(?<project>[\w-]*)(/(?<path>.*))?') {
|
||||
$project = $Matches['project']
|
||||
$path = $Matches['path']
|
||||
} else {
|
||||
$project = $json.checkver.sourceforge.project
|
||||
$path = $json.checkver.sourceforge.path
|
||||
}
|
||||
$url = "https://sourceforge.net/projects/$project/rss"
|
||||
if ($path) {
|
||||
$url = $url + '?path=/' + $path.TrimStart('/')
|
||||
}
|
||||
$regex = "CDATA\[/$path/.*?$sourceforgeRegex.*?\]".Replace('//', '/')
|
||||
}
|
||||
|
||||
if ($json.checkver.jp) {
|
||||
@@ -165,7 +206,7 @@ $Queue | ForEach-Object {
|
||||
$xpath = $json.checkver.xpath
|
||||
}
|
||||
|
||||
if ($json.checkver.replace -and $json.checkver.replace.GetType() -eq [System.String]) {
|
||||
if ($json.checkver.replace -is [System.String]) { # If `checkver` is [System.String], it has a method called `Replace`
|
||||
$replace = $json.checkver.replace
|
||||
}
|
||||
|
||||
@@ -185,14 +226,21 @@ $Queue | ForEach-Object {
|
||||
$url = substitute $url $substitutions
|
||||
|
||||
$state = New-Object psobject @{
|
||||
app = (strip_ext $name);
|
||||
url = $url;
|
||||
regex = $regex;
|
||||
json = $json;
|
||||
jsonpath = $jsonpath;
|
||||
xpath = $xpath;
|
||||
reverse = $reverse;
|
||||
replace = $replace;
|
||||
app = $name
|
||||
file = $file
|
||||
url = $url
|
||||
regex = $regex
|
||||
json = $json
|
||||
jsonpath = $jsonpath
|
||||
xpath = $xpath
|
||||
reverse = $reverse
|
||||
replace = $replace
|
||||
}
|
||||
|
||||
get_config PRIVATE_HOSTS | Where-Object { $_ -ne $null -and $url -match $_.match } | ForEach-Object {
|
||||
(ConvertFrom-StringData -StringData $_.Headers).GetEnumerator() | ForEach-Object {
|
||||
$wc.Headers[$_.Key] = $_.Value
|
||||
}
|
||||
}
|
||||
|
||||
$wc.Headers.Add('Referer', (strip_filename $url))
|
||||
@@ -212,7 +260,9 @@ while ($in_progress -gt 0) {
|
||||
$in_progress--
|
||||
|
||||
$state = $ev.SourceEventArgs.UserState
|
||||
$result = $ev.SourceEventArgs.Result
|
||||
$app = $state.app
|
||||
$file = $state.file
|
||||
$json = $state.json
|
||||
$url = $state.url
|
||||
$regexp = $state.regex
|
||||
@@ -225,7 +275,7 @@ while ($in_progress -gt 0) {
|
||||
$ver = $Version
|
||||
|
||||
if (!$ver) {
|
||||
if (!$regex -and $replace) {
|
||||
if (!$regexp -and $replace) {
|
||||
next "'replace' requires 're' or 'regex'"
|
||||
continue
|
||||
}
|
||||
@@ -236,15 +286,23 @@ while ($in_progress -gt 0) {
|
||||
}
|
||||
|
||||
if ($url) {
|
||||
$page = (Get-Encoding($wc)).GetString($ev.SourceEventArgs.Result)
|
||||
$ms = New-Object System.IO.MemoryStream
|
||||
$ms.Write($result, 0, $result.Length)
|
||||
$ms.Seek(0, 0) | Out-Null
|
||||
if ($result[0] -eq 0x1F -and $result[1] -eq 0x8B) {
|
||||
$ms = New-Object System.IO.Compression.GZipStream($ms, [System.IO.Compression.CompressionMode]::Decompress)
|
||||
}
|
||||
$page = (New-Object System.IO.StreamReader($ms, (Get-Encoding $wc))).ReadToEnd()
|
||||
}
|
||||
$source = $url
|
||||
if ($script) {
|
||||
$page = Invoke-Command ([scriptblock]::Create($script -join "`r`n"))
|
||||
$source = 'the output of script'
|
||||
}
|
||||
|
||||
if ($jsonpath) {
|
||||
# Return only a single value if regex is absent
|
||||
$noregex = [String]::IsNullOrEmpty($regex)
|
||||
$noregex = [String]::IsNullOrEmpty($regexp)
|
||||
# If reverse is ON and regex is ON,
|
||||
# Then reverse would have no effect because regex handles reverse
|
||||
# on its own
|
||||
@@ -254,7 +312,7 @@ while ($in_progress -gt 0) {
|
||||
$ver = json_path_legacy $page $jsonpath
|
||||
}
|
||||
if (!$ver) {
|
||||
next "couldn't find '$jsonpath' in $url"
|
||||
next "couldn't find '$jsonpath' in $source"
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -266,12 +324,17 @@ while ($in_progress -gt 0) {
|
||||
# Then add them into the NamespaceManager
|
||||
$nsmgr = New-Object System.Xml.XmlNamespaceManager($xml.NameTable)
|
||||
$nsList | ForEach-Object {
|
||||
$nsmgr.AddNamespace($_.LocalName, $_.Value)
|
||||
if ($_.LocalName -eq 'xmlns') {
|
||||
$nsmgr.AddNamespace('ns', $_.Value)
|
||||
$xpath = $xpath -replace '/([^:/]+)((?=/)|(?=$))', '/ns:$1'
|
||||
} else {
|
||||
$nsmgr.AddNamespace($_.LocalName, $_.Value)
|
||||
}
|
||||
}
|
||||
# Getting version from XML, using XPath
|
||||
$ver = $xml.SelectSingleNode($xpath, $nsmgr).'#text'
|
||||
if (!$ver) {
|
||||
next "couldn't find '$xpath' in $url"
|
||||
next "couldn't find '$($xpath -replace 'ns:', '')' in $source"
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -287,31 +350,31 @@ while ($in_progress -gt 0) {
|
||||
}
|
||||
|
||||
if ($regexp) {
|
||||
$regex = New-Object System.Text.RegularExpressions.Regex($regexp)
|
||||
$re = New-Object System.Text.RegularExpressions.Regex($regexp)
|
||||
if ($reverse) {
|
||||
$match = $regex.Matches($page) | Select-Object -Last 1
|
||||
$match = $re.Matches($page) | Select-Object -Last 1
|
||||
} else {
|
||||
$match = $regex.Matches($page) | Select-Object -First 1
|
||||
$match = $re.Matches($page) | Select-Object -First 1
|
||||
}
|
||||
|
||||
if ($match -and $match.Success) {
|
||||
$matchesHashtable = @{}
|
||||
$regex.GetGroupNames() | ForEach-Object { $matchesHashtable.Add($_, $match.Groups[$_].Value) }
|
||||
$re.GetGroupNames() | ForEach-Object { $matchesHashtable.Add($_, $match.Groups[$_].Value) }
|
||||
$ver = $matchesHashtable['1']
|
||||
if ($replace) {
|
||||
$ver = $regex.Replace($match.Value, $replace)
|
||||
$ver = $re.Replace($match.Value, $replace)
|
||||
}
|
||||
if (!$ver) {
|
||||
$ver = $matchesHashtable['version']
|
||||
}
|
||||
} else {
|
||||
next "couldn't match '$regexp' in $url"
|
||||
next "couldn't match '$regexp' in $source"
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if (!$ver) {
|
||||
next "couldn't find new version in $url"
|
||||
next "couldn't find new version in $source"
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -319,7 +382,7 @@ while ($in_progress -gt 0) {
|
||||
# Skip actual only if versions are same and there is no -f
|
||||
if (($ver -eq $expected_ver) -and !$ForceUpdate -and $SkipUpdated) { continue }
|
||||
|
||||
Write-Host "$App`: " -NoNewline
|
||||
Write-Host "$app`: " -NoNewline
|
||||
|
||||
# version hasn't changed (step over if forced update)
|
||||
if ($ver -eq $expected_ver -and !$ForceUpdate) {
|
||||
@@ -345,7 +408,7 @@ while ($in_progress -gt 0) {
|
||||
Write-Host 'Forcing autoupdate!' -ForegroundColor DarkMagenta
|
||||
}
|
||||
try {
|
||||
Invoke-AutoUpdate $App $Dir $json $ver $matchesHashtable # 'autoupdate.ps1'
|
||||
Invoke-AutoUpdate $app $file $json $ver $matchesHashtable # 'autoupdate.ps1'
|
||||
} catch {
|
||||
if ($ThrowError) {
|
||||
throw $_
|
||||
|
||||
@@ -23,13 +23,14 @@ param(
|
||||
. "$PSScriptRoot\..\lib\core.ps1"
|
||||
. "$PSScriptRoot\..\lib\manifest.ps1"
|
||||
. "$PSScriptRoot\..\lib\description.ps1"
|
||||
. "$PSScriptRoot\..\lib\download.ps1"
|
||||
|
||||
$Dir = Resolve-Path $Dir
|
||||
$Dir = Convert-Path $Dir
|
||||
$Queue = @()
|
||||
|
||||
Get-ChildItem $Dir "$App.json" | ForEach-Object {
|
||||
$manifest = parse_json "$Dir\$($_.Name)"
|
||||
$Queue += , @(($_.Name -replace '\.json$', ''), $manifest)
|
||||
Get-ChildItem $Dir -Filter "$App.json" -Recurse | ForEach-Object {
|
||||
$manifest = parse_json $_.FullName
|
||||
$Queue += , @($_.BaseName, $manifest)
|
||||
}
|
||||
|
||||
$Queue | ForEach-Object {
|
||||
|
||||
@@ -31,15 +31,14 @@ param(
|
||||
. "$PSScriptRoot\..\lib\manifest.ps1"
|
||||
. "$PSScriptRoot\..\lib\json.ps1"
|
||||
|
||||
$Dir = Resolve-Path $Dir
|
||||
|
||||
Get-ChildItem $Dir "$App.json" | ForEach-Object {
|
||||
if ($PSVersionTable.PSVersion.Major -gt 5) { $_ = $_.Name } # Fix for pwsh
|
||||
$Dir = Convert-Path $Dir
|
||||
|
||||
Get-ChildItem $Dir -Filter "$App.json" -Recurse | ForEach-Object {
|
||||
$file = $_.FullName
|
||||
# beautify
|
||||
$json = parse_json "$Dir\$_" | ConvertToPrettyJson
|
||||
$json = parse_json $file | ConvertToPrettyJson
|
||||
|
||||
# convert to 4 spaces
|
||||
$json = $json -replace "`t", ' '
|
||||
[System.IO.File]::WriteAllLines("$Dir\$_", $json)
|
||||
[System.IO.File]::WriteAllLines($file, $json)
|
||||
}
|
||||
|
||||
@@ -1,78 +1,2 @@
|
||||
#Requires -Version 5
|
||||
|
||||
# remote install:
|
||||
# Invoke-Expression (New-Object System.Net.WebClient).DownloadString('https://get.scoop.sh')
|
||||
$old_erroractionpreference = $erroractionpreference
|
||||
$erroractionpreference = 'stop' # quit if anything goes wrong
|
||||
|
||||
if (($PSVersionTable.PSVersion.Major) -lt 5) {
|
||||
Write-Output "PowerShell 5 or later is required to run Scoop."
|
||||
Write-Output "Upgrade PowerShell: https://docs.microsoft.com/en-us/powershell/scripting/setup/installing-windows-powershell"
|
||||
break
|
||||
}
|
||||
|
||||
# show notification to change execution policy:
|
||||
$allowedExecutionPolicy = @('Unrestricted', 'RemoteSigned', 'ByPass')
|
||||
if ((Get-ExecutionPolicy).ToString() -notin $allowedExecutionPolicy) {
|
||||
Write-Output "PowerShell requires an execution policy in [$($allowedExecutionPolicy -join ", ")] to run Scoop."
|
||||
Write-Output "For example, to set the execution policy to 'RemoteSigned' please run :"
|
||||
Write-Output "'Set-ExecutionPolicy RemoteSigned -scope CurrentUser'"
|
||||
break
|
||||
}
|
||||
|
||||
if ([System.Enum]::GetNames([System.Net.SecurityProtocolType]) -notcontains 'Tls12') {
|
||||
Write-Output "Scoop requires at least .NET Framework 4.5"
|
||||
Write-Output "Please download and install it first:"
|
||||
Write-Output "https://www.microsoft.com/net/download"
|
||||
break
|
||||
}
|
||||
|
||||
# get core functions
|
||||
$core_url = 'https://raw.githubusercontent.com/ScoopInstaller/Scoop/master/lib/core.ps1'
|
||||
Write-Output 'Initializing...'
|
||||
Invoke-Expression (new-object net.webclient).downloadstring($core_url)
|
||||
|
||||
# prep
|
||||
if (Get-Command -Name 'scoop' -ErrorAction SilentlyContinue) {
|
||||
write-host "Scoop is already installed. Run 'scoop update' to get the latest version." -f red
|
||||
# don't abort if invoked with iex that would close the PS session
|
||||
if ($myinvocation.mycommand.commandtype -eq 'Script') { return } else { exit 1 }
|
||||
}
|
||||
$dir = ensure (versiondir 'scoop' 'current')
|
||||
|
||||
# download scoop zip
|
||||
$zipurl = 'https://github.com/ScoopInstaller/Scoop/archive/master.zip'
|
||||
$zipfile = "$dir\scoop.zip"
|
||||
Write-Output 'Downloading scoop...'
|
||||
dl $zipurl $zipfile
|
||||
|
||||
Write-Output 'Extracting...'
|
||||
Add-Type -Assembly "System.IO.Compression.FileSystem"
|
||||
[IO.Compression.ZipFile]::ExtractToDirectory($zipfile, "$dir\_tmp")
|
||||
Copy-Item "$dir\_tmp\*master\*" $dir -Recurse -Force
|
||||
Remove-Item "$dir\_tmp", $zipfile -Recurse -Force
|
||||
|
||||
Write-Output 'Creating shim...'
|
||||
shim "$dir\bin\scoop.ps1" $false
|
||||
|
||||
# download main bucket
|
||||
$dir = "$scoopdir\buckets\main"
|
||||
$zipurl = 'https://github.com/ScoopInstaller/Main/archive/master.zip'
|
||||
$zipfile = "$dir\main-bucket.zip"
|
||||
Write-Output 'Downloading main bucket...'
|
||||
New-Item $dir -Type Directory -Force | Out-Null
|
||||
dl $zipurl $zipfile
|
||||
|
||||
Write-Output 'Extracting...'
|
||||
[IO.Compression.ZipFile]::ExtractToDirectory($zipfile, "$dir\_tmp")
|
||||
Copy-Item "$dir\_tmp\*-master\*" $dir -Recurse -Force
|
||||
Remove-Item "$dir\_tmp", $zipfile -Recurse -Force
|
||||
|
||||
ensure_robocopy_in_path
|
||||
|
||||
scoop config lastupdate ([System.DateTime]::Now.ToString('o'))
|
||||
success 'Scoop was installed successfully!'
|
||||
|
||||
Write-Output "Type 'scoop help' for instructions."
|
||||
|
||||
$erroractionpreference = $old_erroractionpreference # Reset $erroractionpreference to original value
|
||||
Invoke-RestMethod https://get.scoop.sh | Invoke-Expression
|
||||
|
||||
@@ -26,7 +26,7 @@ param(
|
||||
. "$PSScriptRoot\..\lib\core.ps1"
|
||||
. "$PSScriptRoot\..\lib\manifest.ps1"
|
||||
|
||||
$Dir = Resolve-Path $Dir
|
||||
$Dir = Convert-Path $Dir
|
||||
|
||||
Write-Host '[' -NoNewLine
|
||||
Write-Host 'C' -NoNewLine -ForegroundColor Green
|
||||
@@ -36,8 +36,8 @@ Write-Host 'A' -NoNewLine -ForegroundColor Cyan
|
||||
Write-Host ']utoupdate'
|
||||
Write-Host ' | |'
|
||||
|
||||
Get-ChildItem $Dir "$App.json" | ForEach-Object {
|
||||
$json = parse_json "$Dir\$($_.Name)"
|
||||
Get-ChildItem $Dir -Filter "$App.json" -Recurse | ForEach-Object {
|
||||
$json = parse_json $_.FullName
|
||||
|
||||
if ($SkipSupported -and $json.checkver -and $json.autoupdate) { return }
|
||||
|
||||
@@ -48,5 +48,5 @@ Get-ChildItem $Dir "$App.json" | ForEach-Object {
|
||||
Write-Host '[' -NoNewLine
|
||||
Write-Host $(if ($json.autoupdate) { 'A' } else { ' ' }) -NoNewLine -ForegroundColor Cyan
|
||||
Write-Host '] ' -NoNewLine
|
||||
Write-Host (strip_ext $_.Name)
|
||||
Write-Host $_.BaseName
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@ switch ($subCommand) {
|
||||
}
|
||||
({ $subCommand -in @('-v', '--version') }) {
|
||||
Write-Host 'Current Scoop version:'
|
||||
if ((Test-CommandAvailable git) -and (Test-Path "$PSScriptRoot\..\.git") -and (get_config SCOOP_BRANCH 'master') -ne 'master') {
|
||||
git -C "$PSScriptRoot\.." --no-pager log --oneline HEAD -n 1
|
||||
if (Test-GitAvailable -and (Test-Path "$PSScriptRoot\..\.git") -and (get_config SCOOP_BRANCH 'master') -ne 'master') {
|
||||
Invoke-Git -Path "$PSScriptRoot\.." -ArgumentList @('log', 'HEAD', '-1', '--oneline')
|
||||
} else {
|
||||
$version = Select-String -Pattern '^## \[(v[\d.]+)\].*?([\d-]+)$' -Path "$PSScriptRoot\..\CHANGELOG.md"
|
||||
Write-Host $version.Matches.Groups[1].Value -ForegroundColor Cyan -NoNewline
|
||||
@@ -31,9 +31,9 @@ switch ($subCommand) {
|
||||
|
||||
Get-LocalBucket | ForEach-Object {
|
||||
$bucketLoc = Find-BucketDirectory $_ -Root
|
||||
if ((Test-Path "$bucketLoc\.git") -and (Test-CommandAvailable git)) {
|
||||
if (Test-GitAvailable -and (Test-Path "$bucketLoc\.git")) {
|
||||
Write-Host "'$_' bucket:"
|
||||
git -C "$bucketLoc" --no-pager log --oneline HEAD -n 1
|
||||
Invoke-Git -Path $bucketLoc -ArgumentList @('log', 'HEAD', '-1', '--oneline')
|
||||
Write-Host ''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ param(
|
||||
)
|
||||
|
||||
. "$PSScriptRoot\..\lib\core.ps1"
|
||||
. "$PSScriptRoot\..\lib\system.ps1"
|
||||
. "$PSScriptRoot\..\lib\install.ps1"
|
||||
. "$PSScriptRoot\..\lib\shortcuts.ps1"
|
||||
. "$PSScriptRoot\..\lib\versions.ps1"
|
||||
@@ -41,7 +42,7 @@ function do_uninstall($app, $global) {
|
||||
$architecture = $install.architecture
|
||||
|
||||
Write-Output "Uninstalling '$app'"
|
||||
run_uninstaller $manifest $architecture $dir
|
||||
Invoke-Installer -Path $dir -Manifest $manifest -ProcessorArchitecture $architecture -Uninstall
|
||||
rm_shims $app $manifest $global $architecture
|
||||
|
||||
# If a junction was used during install, that will have been used
|
||||
@@ -98,7 +99,9 @@ if ($purge) {
|
||||
if ($global) { keep_onlypersist $globaldir }
|
||||
}
|
||||
|
||||
remove_from_path (shimdir $false)
|
||||
if ($global) { remove_from_path (shimdir $true) }
|
||||
Remove-Path -Path (shimdir $global) -Global:$global
|
||||
if (get_config USE_ISOLATED_PATH) {
|
||||
Remove-Path -Path ('%' + $scoopPathEnvVar + '%') -Global:$global
|
||||
}
|
||||
|
||||
success 'Scoop has been uninstalled.'
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
"main": "https://github.com/ScoopInstaller/Main",
|
||||
"extras": "https://github.com/ScoopInstaller/Extras",
|
||||
"versions": "https://github.com/ScoopInstaller/Versions",
|
||||
"nirsoft": "https://github.com/kodybrown/scoop-nirsoft",
|
||||
"nirsoft": "https://github.com/ScoopInstaller/Nirsoft",
|
||||
"sysinternals": "https://github.com/niheaven/scoop-sysinternals",
|
||||
"php": "https://github.com/ScoopInstaller/PHP",
|
||||
"nerd-fonts": "https://github.com/matthewjberger/scoop-nerd-fonts",
|
||||
"nonportable": "https://github.com/ScoopInstaller/Nonportable",
|
||||
|
||||
@@ -1,4 +1,22 @@
|
||||
# Must included with 'json.ps1'
|
||||
|
||||
function format_hash([String] $hash) {
|
||||
$hash = $hash.toLower()
|
||||
|
||||
if ($hash -like 'sha256:*') {
|
||||
$hash = $hash.Substring(7) # Remove prefix 'sha256:'
|
||||
}
|
||||
|
||||
switch ($hash.Length) {
|
||||
32 { $hash = "md5:$hash" } # md5
|
||||
40 { $hash = "sha1:$hash" } # sha1
|
||||
64 { $hash = $hash } # sha256
|
||||
128 { $hash = "sha512:$hash" } # sha512
|
||||
default { $hash = $null }
|
||||
}
|
||||
return $hash
|
||||
}
|
||||
|
||||
function find_hash_in_rdf([String] $url, [String] $basename) {
|
||||
$xml = $null
|
||||
try {
|
||||
@@ -8,9 +26,9 @@ function find_hash_in_rdf([String] $url, [String] $basename) {
|
||||
$wc.Headers.Add('User-Agent', (Get-UserAgent))
|
||||
$data = $wc.DownloadData($url)
|
||||
[xml]$xml = (Get-Encoding($wc)).GetString($data)
|
||||
} catch [system.net.webexception] {
|
||||
write-host -f darkred $_
|
||||
write-host -f darkred "URL $url is not valid"
|
||||
} catch [System.Net.WebException] {
|
||||
Write-Host $_ -ForegroundColor DarkRed
|
||||
Write-Host "URL $url is not valid" -ForegroundColor DarkRed
|
||||
return $null
|
||||
}
|
||||
|
||||
@@ -24,12 +42,12 @@ function find_hash_in_textfile([String] $url, [Hashtable] $substitutions, [Strin
|
||||
$hashfile = $null
|
||||
|
||||
$templates = @{
|
||||
'$md5' = '([a-fA-F0-9]{32})';
|
||||
'$sha1' = '([a-fA-F0-9]{40})';
|
||||
'$sha256' = '([a-fA-F0-9]{64})';
|
||||
'$sha512' = '([a-fA-F0-9]{128})';
|
||||
'$checksum' = '([a-fA-F0-9]{32,128})';
|
||||
'$base64' = '([a-zA-Z0-9+\/=]{24,88})';
|
||||
'$md5' = '([a-fA-F0-9]{32})'
|
||||
'$sha1' = '([a-fA-F0-9]{40})'
|
||||
'$sha256' = '([a-fA-F0-9]{64})'
|
||||
'$sha512' = '([a-fA-F0-9]{128})'
|
||||
'$checksum' = '([a-fA-F0-9]{32,128})'
|
||||
'$base64' = '([a-zA-Z0-9+\/=]{24,88})'
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -37,10 +55,16 @@ function find_hash_in_textfile([String] $url, [Hashtable] $substitutions, [Strin
|
||||
$wc.Headers.Add('Referer', (strip_filename $url))
|
||||
$wc.Headers.Add('User-Agent', (Get-UserAgent))
|
||||
$data = $wc.DownloadData($url)
|
||||
$hashfile = (Get-Encoding($wc)).GetString($data)
|
||||
$ms = New-Object System.IO.MemoryStream
|
||||
$ms.Write($data, 0, $data.Length)
|
||||
$ms.Seek(0, 0) | Out-Null
|
||||
if ($data[0] -eq 0x1F -and $data[1] -eq 0x8B) {
|
||||
$ms = New-Object System.IO.Compression.GZipStream($ms, [System.IO.Compression.CompressionMode]::Decompress)
|
||||
}
|
||||
$hashfile = (New-Object System.IO.StreamReader($ms, (Get-Encoding $wc))).ReadToEnd()
|
||||
} catch [system.net.webexception] {
|
||||
write-host -f darkred $_
|
||||
write-host -f darkred "URL $url is not valid"
|
||||
Write-Host $_ -ForegroundColor DarkRed
|
||||
Write-Host "URL $url is not valid" -ForegroundColor DarkRed
|
||||
return
|
||||
}
|
||||
|
||||
@@ -50,15 +74,15 @@ function find_hash_in_textfile([String] $url, [Hashtable] $substitutions, [Strin
|
||||
|
||||
$regex = substitute $regex $templates $false
|
||||
$regex = substitute $regex $substitutions $true
|
||||
debug $regex
|
||||
if ($hashfile -match $regex) {
|
||||
$hash = $matches[1] -replace '\s',''
|
||||
debug $regex
|
||||
$hash = $matches[1] -replace '\s', ''
|
||||
}
|
||||
|
||||
# convert base64 encoded hash values
|
||||
if ($hash -match '^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=|[A-Za-z0-9+\/]{4})$') {
|
||||
$base64 = $matches[0]
|
||||
if(!($hash -match '^[a-fA-F0-9]+$') -and $hash.length -notin @(32, 40, 64, 128)) {
|
||||
if (!($hash -match '^[a-fA-F0-9]+$') -and $hash.Length -notin @(32, 40, 64, 128)) {
|
||||
try {
|
||||
$hash = ([System.Convert]::FromBase64String($base64) | ForEach-Object { $_.ToString('x2') }) -join ''
|
||||
} catch {
|
||||
@@ -69,13 +93,15 @@ function find_hash_in_textfile([String] $url, [Hashtable] $substitutions, [Strin
|
||||
|
||||
# find hash with filename in $hashfile
|
||||
if ($hash.Length -eq 0) {
|
||||
$filenameRegex = "([a-fA-F0-9]{32,128})[\x20\t]+.*`$basename(?:[\x20\t]+\d+)?"
|
||||
$filenameRegex = "([a-fA-F0-9]{32,128})[\x20\t]+.*`$basename(?:\s|$)|`$basename[\x20\t]+.*?([a-fA-F0-9]{32,128})"
|
||||
$filenameRegex = substitute $filenameRegex $substitutions $true
|
||||
if ($hashfile -match $filenameRegex) {
|
||||
debug $filenameRegex
|
||||
$hash = $matches[1]
|
||||
}
|
||||
$metalinkRegex = "<hash[^>]+>([a-fA-F0-9]{64})"
|
||||
$metalinkRegex = '<hash[^>]+>([a-fA-F0-9]{64})'
|
||||
if ($hashfile -match $metalinkRegex) {
|
||||
debug $metalinkRegex
|
||||
$hash = $matches[1]
|
||||
}
|
||||
}
|
||||
@@ -91,14 +117,21 @@ function find_hash_in_json([String] $url, [Hashtable] $substitutions, [String] $
|
||||
$wc.Headers.Add('Referer', (strip_filename $url))
|
||||
$wc.Headers.Add('User-Agent', (Get-UserAgent))
|
||||
$data = $wc.DownloadData($url)
|
||||
$json = (Get-Encoding($wc)).GetString($data)
|
||||
} catch [system.net.webexception] {
|
||||
write-host -f darkred $_
|
||||
write-host -f darkred "URL $url is not valid"
|
||||
$ms = New-Object System.IO.MemoryStream
|
||||
$ms.Write($data, 0, $data.Length)
|
||||
$ms.Seek(0, 0) | Out-Null
|
||||
if ($data[0] -eq 0x1F -and $data[1] -eq 0x8B) {
|
||||
$ms = New-Object System.IO.Compression.GZipStream($ms, [System.IO.Compression.CompressionMode]::Decompress)
|
||||
}
|
||||
$json = (New-Object System.IO.StreamReader($ms, (Get-Encoding $wc))).ReadToEnd()
|
||||
} catch [System.Net.WebException] {
|
||||
Write-Host $_ -ForegroundColor DarkRed
|
||||
Write-Host "URL $url is not valid" -ForegroundColor DarkRed
|
||||
return
|
||||
}
|
||||
debug $jsonpath
|
||||
$hash = json_path $json $jsonpath $substitutions
|
||||
if(!$hash) {
|
||||
if (!$hash) {
|
||||
$hash = json_path_legacy $json $jsonpath $substitutions
|
||||
}
|
||||
return format_hash $hash
|
||||
@@ -112,10 +145,16 @@ function find_hash_in_xml([String] $url, [Hashtable] $substitutions, [String] $x
|
||||
$wc.Headers.Add('Referer', (strip_filename $url))
|
||||
$wc.Headers.Add('User-Agent', (Get-UserAgent))
|
||||
$data = $wc.DownloadData($url)
|
||||
$xml = [xml]((Get-Encoding($wc)).GetString($data))
|
||||
$ms = New-Object System.IO.MemoryStream
|
||||
$ms.Write($data, 0, $data.Length)
|
||||
$ms.Seek(0, 0) | Out-Null
|
||||
if ($data[0] -eq 0x1F -and $data[1] -eq 0x8B) {
|
||||
$ms = New-Object System.IO.Compression.GZipStream($ms, [System.IO.Compression.CompressionMode]::Decompress)
|
||||
}
|
||||
$xml = [xml]((New-Object System.IO.StreamReader($ms, (Get-Encoding $wc))).ReadToEnd())
|
||||
} catch [system.net.webexception] {
|
||||
write-host -f darkred $_
|
||||
write-host -f darkred "URL $url is not valid"
|
||||
Write-Host $_ -ForegroundColor DarkRed
|
||||
Write-Host "URL $url is not valid" -ForegroundColor DarkRed
|
||||
return
|
||||
}
|
||||
|
||||
@@ -125,13 +164,15 @@ function find_hash_in_xml([String] $url, [Hashtable] $substitutions, [String] $x
|
||||
}
|
||||
|
||||
# Find all `significant namespace declarations` from the XML file
|
||||
$nsList = $xml.SelectNodes("//namespace::*[not(. = ../../namespace::*)]")
|
||||
$nsList = $xml.SelectNodes('//namespace::*[not(. = ../../namespace::*)]')
|
||||
# Then add them into the NamespaceManager
|
||||
$nsmgr = New-Object System.Xml.XmlNamespaceManager($xml.NameTable)
|
||||
$nsList | ForEach-Object {
|
||||
$nsmgr.AddNamespace($_.LocalName, $_.Value)
|
||||
}
|
||||
|
||||
debug $xpath
|
||||
debug $nsmgr
|
||||
# Getting hash from XML, using XPath
|
||||
$hash = $xml.SelectSingleNode($xpath, $nsmgr).'#text'
|
||||
return format_hash $hash
|
||||
@@ -148,16 +189,16 @@ function find_hash_in_headers([String] $url) {
|
||||
$req.Timeout = 2000
|
||||
$req.Method = 'HEAD'
|
||||
$res = $req.GetResponse()
|
||||
if(([int]$res.StatusCode -ge 300) -and ([int]$res.StatusCode -lt 400)) {
|
||||
if($res.Headers['Digest'] -match 'SHA-256=([^,]+)' -or $res.Headers['Digest'] -match 'SHA=([^,]+)' -or $res.Headers['Digest'] -match 'MD5=([^,]+)') {
|
||||
if (([int]$res.StatusCode -ge 300) -and ([int]$res.StatusCode -lt 400)) {
|
||||
if ($res.Headers['Digest'] -match 'SHA-256=([^,]+)' -or $res.Headers['Digest'] -match 'SHA=([^,]+)' -or $res.Headers['Digest'] -match 'MD5=([^,]+)') {
|
||||
$hash = ([System.Convert]::FromBase64String($matches[1]) | ForEach-Object { $_.ToString('x2') }) -join ''
|
||||
debug $hash
|
||||
}
|
||||
}
|
||||
$res.Close()
|
||||
} catch [system.net.webexception] {
|
||||
write-host -f darkred $_
|
||||
write-host -f darkred "URL $url is not valid"
|
||||
} catch [System.Net.WebException] {
|
||||
Write-Host $_ -ForegroundColor DarkRed
|
||||
Write-Host "URL $url is not valid" -ForegroundColor DarkRed
|
||||
return
|
||||
}
|
||||
|
||||
@@ -182,10 +223,10 @@ function get_hash_for_app([String] $app, $config, [String] $version, [String] $u
|
||||
$hashfile_url = substitute $config.url $substitutions
|
||||
debug $hashfile_url
|
||||
if ($hashfile_url) {
|
||||
write-host -f DarkYellow 'Searching hash for ' -NoNewline
|
||||
write-host -f Green $basename -NoNewline
|
||||
write-host -f DarkYellow ' in ' -NoNewline
|
||||
write-host -f Green $hashfile_url
|
||||
Write-Host 'Searching hash for ' -ForegroundColor DarkYellow -NoNewline
|
||||
Write-Host $basename -ForegroundColor Green -NoNewline
|
||||
Write-Host ' in ' -ForegroundColor DarkYellow -NoNewline
|
||||
Write-Host $hashfile_url -ForegroundColor Green
|
||||
}
|
||||
|
||||
if ($hashmode.Length -eq 0 -and $config.url.Length -ne 0) {
|
||||
@@ -215,14 +256,18 @@ function get_hash_for_app([String] $app, $config, [String] $version, [String] $u
|
||||
$hashmode = 'xpath'
|
||||
}
|
||||
|
||||
if (!$hashfile_url -and $url -match "^(?:.*fosshub.com\/).*(?:\/|\?dwl=)(?<filename>.*)$") {
|
||||
if (!$hashfile_url -and $url -match '^(?:.*fosshub.com\/).*(?:\/|\?dwl=)(?<filename>.*)$') {
|
||||
$hashmode = 'fosshub'
|
||||
}
|
||||
|
||||
if (!$hashfile_url -and $url -match "(?:downloads\.)?sourceforge.net\/projects?\/(?<project>[^\/]+)\/(?:files\/)?(?<file>.*)") {
|
||||
if (!$hashfile_url -and $url -match '(?:downloads\.)?sourceforge.net\/projects?\/(?<project>[^\/]+)\/(?:files\/)?(?<file>.*)') {
|
||||
$hashmode = 'sourceforge'
|
||||
}
|
||||
|
||||
if (!$hashfile_url -and $url -match 'https:\/\/github\.com\/(?<owner>[^\/]+)\/(?<repo>[^\/]+)\/releases\/download\/[^\/]+\/[^\/]+') {
|
||||
$hashmode = 'github'
|
||||
}
|
||||
|
||||
switch ($hashmode) {
|
||||
'extract' {
|
||||
$hash = find_hash_in_textfile $hashfile_url $substitutions $regex
|
||||
@@ -243,40 +288,44 @@ function get_hash_for_app([String] $app, $config, [String] $version, [String] $u
|
||||
}
|
||||
}
|
||||
'fosshub' {
|
||||
$hash = find_hash_in_textfile $url $substitutions ($Matches.filename+'.*?"sha256":"([a-fA-F0-9]{64})"')
|
||||
$hash = find_hash_in_textfile $url $substitutions ($matches.filename + '.*?"sha256":"([a-fA-F0-9]{64})"')
|
||||
}
|
||||
'sourceforge' {
|
||||
# change the URL because downloads.sourceforge.net doesn't have checksums
|
||||
$hashfile_url = (strip_filename (strip_fragment "https://sourceforge.net/projects/$($matches['project'])/files/$($matches['file'])")).TrimEnd('/')
|
||||
$hash = find_hash_in_textfile $hashfile_url $substitutions '"$basename":.*?"sha1":\s"([a-fA-F0-9]{40})"'
|
||||
$hash = find_hash_in_textfile $hashfile_url $substitutions '"$basename":.*?"sha1":\s*"([a-fA-F0-9]{40})"'
|
||||
}
|
||||
'github' {
|
||||
$hashfile_url = "https://api.github.com/repos/$($matches['owner'])/$($matches['repo'])/releases"
|
||||
$hash = find_hash_in_json $hashfile_url $substitutions ("$..assets[?(@.browser_download_url == '" + $url + "')].digest")
|
||||
}
|
||||
}
|
||||
|
||||
if ($hash) {
|
||||
# got one!
|
||||
write-host -f DarkYellow 'Found: ' -NoNewline
|
||||
write-host -f Green $hash -NoNewline
|
||||
write-host -f DarkYellow ' using ' -NoNewline
|
||||
write-host -f Green "$((Get-Culture).TextInfo.ToTitleCase($hashmode)) Mode"
|
||||
Write-Host 'Found: ' -ForegroundColor DarkYellow -NoNewline
|
||||
Write-Host $hash -ForegroundColor Green -NoNewline
|
||||
Write-Host ' using ' -ForegroundColor DarkYellow -NoNewline
|
||||
Write-Host "$((Get-Culture).TextInfo.ToTitleCase($hashmode)) Mode" -ForegroundColor Green
|
||||
return $hash
|
||||
} elseif ($hashfile_url) {
|
||||
write-host -f DarkYellow "Could not find hash in $hashfile_url"
|
||||
Write-Host -f DarkYellow "Could not find hash in $hashfile_url"
|
||||
}
|
||||
|
||||
write-host -f DarkYellow 'Downloading ' -NoNewline
|
||||
write-host -f Green $basename -NoNewline
|
||||
write-host -f DarkYellow ' to compute hashes!'
|
||||
Write-Host 'Downloading ' -ForegroundColor DarkYellow -NoNewline
|
||||
Write-Host $basename -ForegroundColor Green -NoNewline
|
||||
Write-Host ' to compute hashes!' -ForegroundColor DarkYellow
|
||||
try {
|
||||
dl_with_cache $app $version $url $null $null $true
|
||||
Invoke-CachedDownload $app $version $url $null $null $true
|
||||
} catch [system.net.webexception] {
|
||||
write-host -f darkred $_
|
||||
write-host -f darkred "URL $url is not valid"
|
||||
Write-Host $_ -ForegroundColor DarkRed
|
||||
Write-Host "URL $url is not valid" -ForegroundColor DarkRed
|
||||
return $null
|
||||
}
|
||||
$file = fullpath (cache_path $app $version $url)
|
||||
$hash = compute_hash $file 'sha256'
|
||||
write-host -f DarkYellow 'Computed hash: ' -NoNewline
|
||||
write-host -f Green $hash
|
||||
$file = cache_path $app $version $url
|
||||
$hash = (Get-FileHash -Path $file -Algorithm SHA256).Hash.ToLower()
|
||||
Write-Host 'Computed hash: ' -ForegroundColor DarkYellow -NoNewline
|
||||
Write-Host $hash -ForegroundColor Green
|
||||
return $hash
|
||||
}
|
||||
|
||||
@@ -346,7 +395,7 @@ function Update-ManifestProperty {
|
||||
$newValue = substitute $autoupdateProperty $Substitutions
|
||||
if (($autoupdateProperty.GetType().Name -eq 'Object[]') -and ($autoupdateProperty.Length -eq 1)) {
|
||||
# Make sure it's an array
|
||||
$newValue = ,$newValue
|
||||
$newValue = , $newValue
|
||||
}
|
||||
$Manifest.$currentProperty, $hasPropertyChanged = PropertyHelper -Property $Manifest.$currentProperty -Value $newValue
|
||||
$hasManifestChanged = $hasManifestChanged -or $hasPropertyChanged
|
||||
@@ -359,7 +408,7 @@ function Update-ManifestProperty {
|
||||
$newValue = substitute $autoupdateProperty $Substitutions
|
||||
if (($autoupdateProperty.GetType().Name -eq 'Object[]') -and ($autoupdateProperty.Length -eq 1)) {
|
||||
# Make sure it's an array
|
||||
$newValue = ,$newValue
|
||||
$newValue = , $newValue
|
||||
}
|
||||
$Manifest.architecture.$arch.$currentProperty, $hasPropertyChanged = PropertyHelper -Property $Manifest.architecture.$arch.$currentProperty -Value $newValue
|
||||
$hasManifestChanged = $hasManifestChanged -or $hasPropertyChanged
|
||||
@@ -388,25 +437,25 @@ function Get-VersionSubstitution {
|
||||
$firstPart = $Version.Split('-') | Select-Object -First 1
|
||||
$lastPart = $Version.Split('-') | Select-Object -Last 1
|
||||
$versionVariables = @{
|
||||
'$version' = $Version;
|
||||
'$dotVersion' = ($Version -replace '[._-]', '.');
|
||||
'$underscoreVersion' = ($Version -replace '[._-]', '_');
|
||||
'$dashVersion' = ($Version -replace '[._-]', '-');
|
||||
'$cleanVersion' = ($Version -replace '[._-]', '');
|
||||
'$majorVersion' = $firstPart.Split('.') | Select-Object -First 1;
|
||||
'$minorVersion' = $firstPart.Split('.') | Select-Object -Skip 1 -First 1;
|
||||
'$patchVersion' = $firstPart.Split('.') | Select-Object -Skip 2 -First 1;
|
||||
'$buildVersion' = $firstPart.Split('.') | Select-Object -Skip 3 -First 1;
|
||||
'$preReleaseVersion' = $lastPart;
|
||||
'$version' = $Version
|
||||
'$dotVersion' = ($Version -replace '[._-]', '.')
|
||||
'$underscoreVersion' = ($Version -replace '[._-]', '_')
|
||||
'$dashVersion' = ($Version -replace '[._-]', '-')
|
||||
'$cleanVersion' = ($Version -replace '[._-]', '')
|
||||
'$majorVersion' = $firstPart.Split('.') | Select-Object -First 1
|
||||
'$minorVersion' = $firstPart.Split('.') | Select-Object -Skip 1 -First 1
|
||||
'$patchVersion' = $firstPart.Split('.') | Select-Object -Skip 2 -First 1
|
||||
'$buildVersion' = $firstPart.Split('.') | Select-Object -Skip 3 -First 1
|
||||
'$preReleaseVersion' = $lastPart
|
||||
}
|
||||
if($Version -match "(?<head>\d+\.\d+(?:\.\d+)?)(?<tail>.*)") {
|
||||
$versionVariables.Set_Item('$matchHead', $Matches['head'])
|
||||
$versionVariables.Set_Item('$matchTail', $Matches['tail'])
|
||||
if ($Version -match '(?<head>\d+\.\d+(?:\.\d+)?)(?<tail>.*)') {
|
||||
$versionVariables.Add('$matchHead', $Matches['head'])
|
||||
$versionVariables.Add('$matchTail', $Matches['tail'])
|
||||
}
|
||||
if($CustomMatches) {
|
||||
if ($CustomMatches) {
|
||||
$CustomMatches.GetEnumerator() | ForEach-Object {
|
||||
if($_.Name -ne "0") {
|
||||
$versionVariables.Set_Item('$match' + (Get-Culture).TextInfo.ToTitleCase($_.Name), $_.Value)
|
||||
if ($_.Name -ne '0') {
|
||||
$versionVariables.Add('$match' + (Get-Culture).TextInfo.ToTitleCase($_.Name), $_.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -449,7 +498,7 @@ function Invoke-AutoUpdate {
|
||||
# 'Set-Content -Encoding ASCII' don't works in PowerShell 5
|
||||
# Wait for 'UTF8NoBOM' Encoding in PowerShell 7
|
||||
# $Manifest | ConvertToPrettyJson | Set-Content -Path (Join-Path $Path "$AppName.json") -Encoding UTF8NoBOM
|
||||
[System.IO.File]::WriteAllLines((Join-Path $Path "$AppName.json"), (ConvertToPrettyJson $Manifest))
|
||||
[System.IO.File]::WriteAllLines($Path, (ConvertToPrettyJson $Manifest))
|
||||
# notes
|
||||
$note = "`nUpdating note:"
|
||||
if ($Manifest.autoupdate.note) {
|
||||
@@ -457,7 +506,7 @@ function Invoke-AutoUpdate {
|
||||
$hasNote = $true
|
||||
}
|
||||
if ($Manifest.autoupdate.architecture) {
|
||||
'64bit', '32bit' | ForEach-Object {
|
||||
'64bit', '32bit', 'arm64' | ForEach-Object {
|
||||
if ($Manifest.autoupdate.architecture.$_.note) {
|
||||
$note += "`n$_-arch: $($Manifest.autoupdate.architecture.$_.note)"
|
||||
$hasNote = $true
|
||||
|
||||
@@ -50,7 +50,7 @@ function known_buckets {
|
||||
}
|
||||
|
||||
function apps_in_bucket($dir) {
|
||||
return Get-ChildItem $dir | Where-Object { $_.Name.EndsWith('.json') } | ForEach-Object { $_.Name -replace '.json$', '' }
|
||||
return (Get-ChildItem $dir -Filter '*.json' -Recurse).BaseName
|
||||
}
|
||||
|
||||
function Get-LocalBucket {
|
||||
@@ -58,10 +58,18 @@ function Get-LocalBucket {
|
||||
.SYNOPSIS
|
||||
List all local buckets.
|
||||
#>
|
||||
$bucketNames = (Get-ChildItem -Path $bucketsdir -Directory).Name
|
||||
$bucketNames = [System.Collections.Generic.List[String]](Get-ChildItem -Path $bucketsdir -Directory).Name
|
||||
if ($null -eq $bucketNames) {
|
||||
return @() # Return a zero-length list instead of $null.
|
||||
} else {
|
||||
$knownBuckets = known_buckets
|
||||
for ($i = $knownBuckets.Count - 1; $i -ge 0 ; $i--) {
|
||||
$name = $knownBuckets[$i]
|
||||
if ($bucketNames.Contains($name)) {
|
||||
[void]$bucketNames.Remove($name)
|
||||
$bucketNames.Insert(0, $name)
|
||||
}
|
||||
}
|
||||
return $bucketNames
|
||||
}
|
||||
}
|
||||
@@ -99,11 +107,11 @@ function list_buckets {
|
||||
$bucket = [Ordered]@{ Name = $_ }
|
||||
$path = Find-BucketDirectory $_ -Root
|
||||
if ((Test-Path (Join-Path $path '.git')) -and (Get-Command git -ErrorAction SilentlyContinue)) {
|
||||
$bucket.Source = git -C $path config remote.origin.url
|
||||
$bucket.Updated = git -C $path log --format='%aD' -n 1 | Get-Date
|
||||
$bucket.Source = Invoke-Git -Path $path -ArgumentList @('config', 'remote.origin.url')
|
||||
$bucket.Updated = Invoke-Git -Path $path -ArgumentList @('log', '--format=%aD', '-n', '1') | Get-Date
|
||||
} else {
|
||||
$bucket.Source = friendly_path $path
|
||||
$bucket.Updated = (Get-Item "$path\bucket").LastWriteTime
|
||||
$bucket.Updated = (Get-Item "$path\bucket" -ErrorAction SilentlyContinue).LastWriteTime
|
||||
}
|
||||
$bucket.Manifests = Get-ChildItem "$path\bucket" -Force -Recurse -ErrorAction SilentlyContinue |
|
||||
Measure-Object | Select-Object -ExpandProperty Count
|
||||
@@ -113,7 +121,7 @@ function list_buckets {
|
||||
}
|
||||
|
||||
function add_bucket($name, $repo) {
|
||||
if (!(Test-CommandAvailable git)) {
|
||||
if (!(Test-GitAvailable)) {
|
||||
error "Git is required for buckets. Run 'scoop install git' and try again."
|
||||
return 1
|
||||
}
|
||||
@@ -130,7 +138,7 @@ function add_bucket($name, $repo) {
|
||||
}
|
||||
foreach ($bucket in Get-LocalBucket) {
|
||||
if (Test-Path -Path "$bucketsdir\$bucket\.git") {
|
||||
$remote = git -C "$bucketsdir\$bucket" config --get remote.origin.url
|
||||
$remote = Invoke-Git -Path "$bucketsdir\$bucket" -ArgumentList @('config', '--get', 'remote.origin.url')
|
||||
if ((Convert-RepositoryUri -Uri $remote) -eq $uni_repo) {
|
||||
warn "Bucket $bucket already exists for $repo"
|
||||
return 2
|
||||
@@ -139,15 +147,24 @@ function add_bucket($name, $repo) {
|
||||
}
|
||||
|
||||
Write-Host 'Checking repo... ' -NoNewline
|
||||
$out = git_cmd ls-remote $repo 2>&1
|
||||
$out = Invoke-Git -ArgumentList @('ls-remote', $repo) 2>&1
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
error "'$repo' doesn't look like a valid git repository`n`nError given:`n$out"
|
||||
return 1
|
||||
}
|
||||
ensure $bucketsdir | Out-Null
|
||||
$dir = ensure $dir
|
||||
git_cmd clone "$repo" "`"$dir`"" -q
|
||||
$out = Invoke-Git -ArgumentList @('clone', $repo, $dir, '-q')
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
error "Failed to clone '$repo' to '$dir'.`n`nError given:`n$out`n`nPlease check the repository URL or network connection and try again."
|
||||
Remove-Item $dir -Recurse -Force -ErrorAction SilentlyContinue
|
||||
return 1
|
||||
}
|
||||
Write-Host 'OK'
|
||||
if (get_config USE_SQLITE_CACHE) {
|
||||
info 'Updating cache...'
|
||||
Set-ScoopDB -Path (Get-ChildItem (Find-BucketDirectory $name) -Filter '*.json' -Recurse).FullName
|
||||
}
|
||||
success "The $name bucket was added successfully."
|
||||
return 0
|
||||
}
|
||||
@@ -160,6 +177,11 @@ function rm_bucket($name) {
|
||||
}
|
||||
|
||||
Remove-Item $dir -Recurse -Force -ErrorAction Stop
|
||||
if (get_config USE_SQLITE_CACHE) {
|
||||
info 'Updating cache...'
|
||||
Remove-ScoopDBItem -Bucket $name
|
||||
}
|
||||
success "The $name bucket was removed successfully."
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -169,7 +191,7 @@ function new_issue_msg($app, $bucket, $title, $body) {
|
||||
$bucket_path = "$bucketsdir\$bucket"
|
||||
|
||||
if (Test-Path $bucket_path) {
|
||||
$remote = git -C "$bucket_path" config --get remote.origin.url
|
||||
$remote = Invoke-Git -Path $bucket_path -ArgumentList @('config', '--get', 'remote.origin.url')
|
||||
# Support ssh and http syntax
|
||||
# git@PROVIDER:USER/REPO.git
|
||||
# https://PROVIDER/USER/REPO.git
|
||||
|
||||
100
lib/commands.ps1
100
lib/commands.ps1
@@ -1,6 +1,10 @@
|
||||
# Description: Functions for managing commands and aliases.
|
||||
|
||||
## Functions for commands
|
||||
|
||||
function command_files {
|
||||
(Get-ChildItem "$PSScriptRoot\..\libexec") + (Get-ChildItem "$scoopdir\shims") |
|
||||
Where-Object 'scoop-.*?\.ps1$' -Property Name -Match
|
||||
Where-Object 'scoop-.*?\.ps1$' -Property Name -Match
|
||||
}
|
||||
|
||||
function commands {
|
||||
@@ -19,11 +23,10 @@ function command_path($cmd) {
|
||||
# get path from shim
|
||||
$shim_path = "$scoopdir\shims\scoop-$cmd.ps1"
|
||||
$line = ((Get-Content $shim_path) | Where-Object { $_.startswith('$path') })
|
||||
if($line) {
|
||||
if ($line) {
|
||||
Invoke-Command ([scriptblock]::Create($line)) -NoNewScope
|
||||
$cmd_path = $path
|
||||
}
|
||||
else { $cmd_path = $shim_path }
|
||||
} else { $cmd_path = $shim_path }
|
||||
}
|
||||
|
||||
$cmd_path
|
||||
@@ -34,3 +37,92 @@ function exec($cmd, $arguments) {
|
||||
|
||||
& $cmd_path @arguments
|
||||
}
|
||||
|
||||
## Functions for aliases
|
||||
|
||||
function add_alias {
|
||||
param(
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string]$name,
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string]$command,
|
||||
[string]$description
|
||||
)
|
||||
|
||||
$aliases = get_config ALIAS ([PSCustomObject]@{})
|
||||
if ($aliases.$name) {
|
||||
abort "Alias '$name' already exists."
|
||||
}
|
||||
|
||||
$alias_script_name = "scoop-$name"
|
||||
$shimdir = shimdir $false
|
||||
if (Test-Path "$shimdir\$alias_script_name.ps1") {
|
||||
abort "File '$alias_script_name.ps1' already exists in shims directory."
|
||||
}
|
||||
$script = @(
|
||||
"# Summary: $description",
|
||||
"$command"
|
||||
) -join "`n"
|
||||
try {
|
||||
$script | Out-UTF8File "$shimdir\$alias_script_name.ps1"
|
||||
} catch {
|
||||
abort $_.Exception
|
||||
}
|
||||
|
||||
# Add the new alias to the config.
|
||||
$aliases | Add-Member -MemberType NoteProperty -Name $name -Value $alias_script_name
|
||||
set_config ALIAS $aliases | Out-Null
|
||||
}
|
||||
|
||||
function rm_alias {
|
||||
param(
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string]$name
|
||||
)
|
||||
|
||||
$aliases = get_config ALIAS ([PSCustomObject]@{})
|
||||
if (!$aliases.$name) {
|
||||
abort "Alias '$name' doesn't exist."
|
||||
}
|
||||
|
||||
info "Removing alias '$name'..."
|
||||
if (Test-Path "$(shimdir $false)\scoop-$name.ps1") {
|
||||
Remove-Item "$(shimdir $false)\scoop-$name.ps1"
|
||||
}
|
||||
$aliases.PSObject.Properties.Remove($name)
|
||||
set_config ALIAS $aliases | Out-Null
|
||||
}
|
||||
|
||||
function list_aliases {
|
||||
param(
|
||||
[bool]$verbose
|
||||
)
|
||||
|
||||
$aliases = get_config ALIAS ([PSCustomObject]@{})
|
||||
$alias_info = $aliases.PSObject.Properties.Name | Where-Object { $_ } | ForEach-Object {
|
||||
# Mark the alias as <BROKEN>, if the alias script file does NOT exist.
|
||||
if (!(Test-Path "$(shimdir $false)\scoop-$_.ps1")) {
|
||||
[PSCustomObject]@{
|
||||
Name = $_
|
||||
Command = '<BROKEN>'
|
||||
}
|
||||
return
|
||||
}
|
||||
$content = Get-Content (command_path $_)
|
||||
[PSCustomObject]@{
|
||||
Name = $_
|
||||
Command = ($content | Select-Object -Skip 1).Trim()
|
||||
Summary = (summary $content).Trim()
|
||||
}
|
||||
}
|
||||
if (!$alias_info) {
|
||||
info 'No alias found.'
|
||||
return
|
||||
}
|
||||
$alias_info = $alias_info | Sort-Object Name
|
||||
$properties = @('Name', 'Command')
|
||||
if ($verbose) {
|
||||
$properties += 'Summary'
|
||||
}
|
||||
$alias_info | Select-Object $properties
|
||||
}
|
||||
|
||||
815
lib/core.ps1
815
lib/core.ps1
File diff suppressed because it is too large
Load Diff
390
lib/database.ps1
Normal file
390
lib/database.ps1
Normal file
@@ -0,0 +1,390 @@
|
||||
# Description: Functions for interacting with the Scoop database cache
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Get SQLite .NET driver
|
||||
.DESCRIPTION
|
||||
Download and extract the SQLite .NET driver from NuGet.
|
||||
.PARAMETER Version
|
||||
System.String
|
||||
The version of the SQLite .NET driver to download.
|
||||
.INPUTS
|
||||
None
|
||||
.OUTPUTS
|
||||
System.Boolean
|
||||
True if the SQLite .NET driver was successfully downloaded and extracted, otherwise false.
|
||||
#>
|
||||
function Get-SQLite {
|
||||
param (
|
||||
[string]$Version = '1.0.118'
|
||||
)
|
||||
# Install SQLite
|
||||
try {
|
||||
Write-Host "Downloading SQLite $Version..." -ForegroundColor DarkYellow
|
||||
$sqlitePkgPath = "$env:TEMP\sqlite.zip"
|
||||
$sqliteTempPath = "$env:TEMP\sqlite"
|
||||
$sqlitePath = "$PSScriptRoot\..\supporting\sqlite"
|
||||
Invoke-WebRequest -Uri "https://api.nuget.org/v3-flatcontainer/stub.system.data.sqlite.core.netframework/$version/stub.system.data.sqlite.core.netframework.$version.nupkg" -OutFile $sqlitePkgPath
|
||||
Write-Host "Extracting SQLite $Version..." -ForegroundColor DarkYellow -NoNewline
|
||||
Expand-Archive -Path $sqlitePkgPath -DestinationPath $sqliteTempPath -Force
|
||||
New-Item -Path $sqlitePath -ItemType Directory -Force | Out-Null
|
||||
Move-Item -Path "$sqliteTempPath\build\net451\*", "$sqliteTempPath\lib\net451\System.Data.SQLite.dll" -Destination $sqlitePath -Force
|
||||
Remove-Item -Path $sqlitePkgPath, $sqliteTempPath -Recurse -Force
|
||||
Write-Host ' Done' -ForegroundColor DarkYellow
|
||||
return $true
|
||||
} catch {
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Open Scoop SQLite database.
|
||||
.DESCRIPTION
|
||||
Open Scoop SQLite database connection and create the necessary tables if not exists.
|
||||
.INPUTS
|
||||
None
|
||||
.OUTPUTS
|
||||
System.Data.SQLite.SQLiteConnection
|
||||
The SQLite database connection if **PassThru** is used.
|
||||
#>
|
||||
function Open-ScoopDB {
|
||||
# Load System.Data.SQLite
|
||||
if (!('System.Data.SQLite.SQLiteConnection' -as [Type])) {
|
||||
try {
|
||||
if (!(Test-Path -Path "$PSScriptRoot\..\supporting\sqlite\System.Data.SQLite.dll")) {
|
||||
Get-SQLite | Out-Null
|
||||
}
|
||||
Add-Type -Path "$PSScriptRoot\..\supporting\sqlite\System.Data.SQLite.dll"
|
||||
} catch {
|
||||
throw "Scoop's Database cache requires the ADO.NET driver:`n`thttp://system.data.sqlite.org/index.html/doc/trunk/www/downloads.wiki"
|
||||
}
|
||||
}
|
||||
$dbPath = Join-Path $scoopdir 'scoop.db'
|
||||
$db = New-Object -TypeName System.Data.SQLite.SQLiteConnection
|
||||
$db.ConnectionString = "Data Source=$dbPath"
|
||||
$db.ParseViaFramework = $true # Allow UNC path
|
||||
$db.Open()
|
||||
$tableCommand = $db.CreateCommand()
|
||||
$tableCommand.CommandText = "CREATE TABLE IF NOT EXISTS 'app' (
|
||||
name TEXT NOT NULL COLLATE NOCASE,
|
||||
description TEXT NOT NULL,
|
||||
version TEXT NOT NULL,
|
||||
bucket VARCHAR NOT NULL,
|
||||
manifest JSON NOT NULL,
|
||||
binary TEXT,
|
||||
shortcut TEXT,
|
||||
dependency TEXT,
|
||||
suggest TEXT,
|
||||
PRIMARY KEY (name, version, bucket)
|
||||
)"
|
||||
$tableCommand.CommandType = [System.Data.CommandType]::Text
|
||||
$tableCommand.ExecuteNonQuery() | Out-Null
|
||||
$tableCommand.Dispose()
|
||||
return $db
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Set Scoop database item(s).
|
||||
.DESCRIPTION
|
||||
Insert or replace item(s) into the Scoop SQLite database.
|
||||
.PARAMETER InputObject
|
||||
System.Object[]
|
||||
The database item(s) to insert or replace.
|
||||
.INPUTS
|
||||
System.Object[]
|
||||
.OUTPUTS
|
||||
None
|
||||
#>
|
||||
function Set-ScoopDBItem {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[Parameter(Mandatory, Position = 0, ValueFromPipeline)]
|
||||
[psobject[]]
|
||||
$InputObject
|
||||
)
|
||||
|
||||
begin {
|
||||
$db = Open-ScoopDB
|
||||
$dbTrans = $db.BeginTransaction()
|
||||
# TODO Support [hashtable]$InputObject
|
||||
$colName = @($InputObject | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name)
|
||||
$dbQuery = "INSERT OR REPLACE INTO app ($($colName -join ', ')) VALUES ($('@' + ($colName -join ', @')))"
|
||||
$dbCommand = $db.CreateCommand()
|
||||
$dbCommand.CommandText = $dbQuery
|
||||
$dbCommand.CommandType = [System.Data.CommandType]::Text
|
||||
}
|
||||
process {
|
||||
foreach ($item in $InputObject) {
|
||||
$item.PSObject.Properties | ForEach-Object {
|
||||
$dbCommand.Parameters.AddWithValue("@$($_.Name)", $_.Value) | Out-Null
|
||||
}
|
||||
$dbCommand.ExecuteNonQuery() | Out-Null
|
||||
}
|
||||
}
|
||||
end {
|
||||
try {
|
||||
$dbTrans.Commit()
|
||||
} catch {
|
||||
$dbTrans.Rollback()
|
||||
throw $_
|
||||
} finally {
|
||||
$dbCommand.Dispose()
|
||||
$dbTrans.Dispose()
|
||||
$db.Dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Set Scoop app database item(s).
|
||||
.DESCRIPTION
|
||||
Insert or replace Scoop app(s) into the database.
|
||||
.PARAMETER Path
|
||||
System.String
|
||||
The path to the bucket.
|
||||
.PARAMETER CommitHash
|
||||
System.String
|
||||
The commit hash to compare with the HEAD.
|
||||
.INPUTS
|
||||
None
|
||||
.OUTPUTS
|
||||
None
|
||||
#>
|
||||
function Set-ScoopDB {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[Parameter(Position = 0, ValueFromPipeline)]
|
||||
[string[]]
|
||||
$Path
|
||||
)
|
||||
|
||||
begin {
|
||||
$list = [System.Collections.Generic.List[psobject]]::new()
|
||||
$arch = Get-DefaultArchitecture
|
||||
}
|
||||
process {
|
||||
if ($Path.Count -eq 0) {
|
||||
$bucketPath = Get-LocalBucket | ForEach-Object { Find-BucketDirectory $_ }
|
||||
$Path = (Get-ChildItem $bucketPath -Filter '*.json' -Recurse).FullName
|
||||
}
|
||||
$Path | ForEach-Object {
|
||||
$manifestRaw = [System.IO.File]::ReadAllText($_)
|
||||
$manifest = ConvertFrom-Json $manifestRaw -ErrorAction SilentlyContinue
|
||||
if ($null -ne $manifest.version) {
|
||||
$list.Add([pscustomobject]@{
|
||||
name = $($_ -replace '.*[\\/]([^\\/]+)\.json$', '$1')
|
||||
description = if ($manifest.description) { $manifest.description } else { '' }
|
||||
version = $manifest.version
|
||||
bucket = $($_ -replace '.*buckets[\\/]([^\\/]+)(?:[\\/].*)', '$1')
|
||||
manifest = $manifestRaw
|
||||
binary = $(
|
||||
$result = @()
|
||||
@(arch_specific 'bin' $manifest $arch) | ForEach-Object {
|
||||
if ($_ -is [System.Array]) {
|
||||
$result += "$($_[1]).$($_[0].Split('.')[-1])"
|
||||
} else {
|
||||
$result += $_
|
||||
}
|
||||
}
|
||||
$result -replace '.*?([^\\/]+)?(\.(exe|bat|cmd|ps1|jar|py))$', '$1' -join ' | '
|
||||
)
|
||||
shortcut = $(
|
||||
$result = @()
|
||||
@(arch_specific 'shortcuts' $manifest $arch) | ForEach-Object {
|
||||
$result += $_[1]
|
||||
}
|
||||
$result -replace '.*?([^\\/]+$)', '$1' -join ' | '
|
||||
)
|
||||
dependency = $manifest.depends -join ' | '
|
||||
suggest = $(
|
||||
$suggest_output = @()
|
||||
$manifest.suggest.PSObject.Properties | ForEach-Object {
|
||||
$suggest_output += $_.Value -join ' | '
|
||||
}
|
||||
$suggest_output -join ' | '
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
end {
|
||||
if ($list.Count -ne 0) {
|
||||
Set-ScoopDBItem $list
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Select Scoop database item(s).
|
||||
.DESCRIPTION
|
||||
Select item(s) from the Scoop SQLite database.
|
||||
The pattern is matched against the name, binaries, and shortcuts columns for apps.
|
||||
.PARAMETER Pattern
|
||||
System.String
|
||||
The pattern to search for. If is an empty string, all items will be returned.
|
||||
.PARAMETER From
|
||||
System.String[]
|
||||
The fields to search from.
|
||||
.INPUTS
|
||||
System.String
|
||||
.OUTPUTS
|
||||
System.Data.DataTable
|
||||
The selected database item(s).
|
||||
#>
|
||||
function Select-ScoopDBItem {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[Parameter(Mandatory, Position = 0, ValueFromPipeline)]
|
||||
[AllowEmptyString()]
|
||||
[string]
|
||||
$Pattern,
|
||||
[Parameter(Mandatory, Position = 1)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string[]]
|
||||
$From
|
||||
)
|
||||
|
||||
begin {
|
||||
$db = Open-ScoopDB
|
||||
$dbAdapter = New-Object -TypeName System.Data.SQLite.SQLiteDataAdapter
|
||||
$result = New-Object System.Data.DataTable
|
||||
$dbQuery = "SELECT * FROM app WHERE $(($From -join ' LIKE @Pattern OR ') + ' LIKE @Pattern')"
|
||||
$dbQuery = "SELECT * FROM ($($dbQuery + ' ORDER BY version DESC')) GROUP BY name, bucket"
|
||||
$dbCommand = $db.CreateCommand()
|
||||
$dbCommand.CommandText = $dbQuery
|
||||
$dbCommand.CommandType = [System.Data.CommandType]::Text
|
||||
$dbAdapter.SelectCommand = $dbCommand
|
||||
}
|
||||
process {
|
||||
$dbCommand.Parameters.AddWithValue('@Pattern', $(if ($Pattern -eq '') { '%' } else { '%' + $Pattern + '%' })) | Out-Null
|
||||
[void]$dbAdapter.Fill($result)
|
||||
}
|
||||
end {
|
||||
$dbAdapter.Dispose()
|
||||
$db.Dispose()
|
||||
return $result
|
||||
}
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Get Scoop database item.
|
||||
.DESCRIPTION
|
||||
Get item from the Scoop SQLite database.
|
||||
.PARAMETER Name
|
||||
System.String
|
||||
The name of the item to get.
|
||||
.PARAMETER Bucket
|
||||
System.String
|
||||
The bucket of the item to get.
|
||||
.PARAMETER Version
|
||||
System.String
|
||||
The version of the item to get. If not provided, the latest version will be returned.
|
||||
.INPUTS
|
||||
System.String
|
||||
.OUTPUTS
|
||||
System.Data.DataTable
|
||||
The selected database item.
|
||||
#>
|
||||
function Get-ScoopDBItem {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[Parameter(Mandatory, Position = 0, ValueFromPipeline)]
|
||||
[string]
|
||||
$Name,
|
||||
[Parameter(Mandatory, Position = 1)]
|
||||
[string]
|
||||
$Bucket,
|
||||
[Parameter(Position = 2)]
|
||||
[string]
|
||||
$Version
|
||||
)
|
||||
|
||||
begin {
|
||||
$db = Open-ScoopDB
|
||||
$dbAdapter = New-Object -TypeName System.Data.SQLite.SQLiteDataAdapter
|
||||
$result = New-Object System.Data.DataTable
|
||||
$dbQuery = 'SELECT * FROM app WHERE name = @Name AND bucket = @Bucket'
|
||||
if ($Version) {
|
||||
$dbQuery += ' AND version = @Version'
|
||||
} else {
|
||||
$dbQuery += ' ORDER BY version DESC LIMIT 1'
|
||||
}
|
||||
$dbCommand = $db.CreateCommand()
|
||||
$dbCommand.CommandText = $dbQuery
|
||||
$dbCommand.CommandType = [System.Data.CommandType]::Text
|
||||
$dbAdapter.SelectCommand = $dbCommand
|
||||
}
|
||||
process {
|
||||
$dbCommand.Parameters.AddWithValue('@Name', $Name) | Out-Null
|
||||
$dbCommand.Parameters.AddWithValue('@Bucket', $Bucket) | Out-Null
|
||||
$dbCommand.Parameters.AddWithValue('@Version', $Version) | Out-Null
|
||||
[void]$dbAdapter.Fill($result)
|
||||
}
|
||||
end {
|
||||
$dbAdapter.Dispose()
|
||||
$db.Dispose()
|
||||
return $result
|
||||
}
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Remove Scoop database item(s).
|
||||
.DESCRIPTION
|
||||
Remove item(s) from the Scoop SQLite database.
|
||||
.PARAMETER Name
|
||||
System.String
|
||||
The name of the item to remove.
|
||||
.PARAMETER Bucket
|
||||
System.String
|
||||
The bucket of the item to remove.
|
||||
.INPUTS
|
||||
System.String
|
||||
.OUTPUTS
|
||||
None
|
||||
#>
|
||||
function Remove-ScoopDBItem {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[Parameter(Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)]
|
||||
[string]
|
||||
$Name,
|
||||
[Parameter(Mandatory, Position = 1, ValueFromPipelineByPropertyName)]
|
||||
[string]
|
||||
$Bucket
|
||||
)
|
||||
|
||||
begin {
|
||||
$db = Open-ScoopDB
|
||||
$dbTrans = $db.BeginTransaction()
|
||||
$dbQuery = 'DELETE FROM app WHERE bucket = @Bucket'
|
||||
$dbCommand = $db.CreateCommand()
|
||||
$dbCommand.CommandText = $dbQuery
|
||||
$dbCommand.CommandType = [System.Data.CommandType]::Text
|
||||
}
|
||||
process {
|
||||
$dbCommand.Parameters.AddWithValue('@Bucket', $Bucket) | Out-Null
|
||||
if ($Name) {
|
||||
$dbCommand.CommandText = $dbQuery + ' AND name = @Name'
|
||||
$dbCommand.Parameters.AddWithValue('@Name', $Name) | Out-Null
|
||||
}
|
||||
$dbCommand.ExecuteNonQuery() | Out-Null
|
||||
}
|
||||
end {
|
||||
try {
|
||||
$dbTrans.Commit()
|
||||
} catch {
|
||||
$dbTrans.Rollback()
|
||||
throw $_
|
||||
} finally {
|
||||
$dbCommand.Dispose()
|
||||
$dbTrans.Dispose()
|
||||
$db.Dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,67 @@
|
||||
# Description: Functions for decompressing archives or installers
|
||||
|
||||
function Invoke-Extraction {
|
||||
param (
|
||||
[string]
|
||||
$Path,
|
||||
[string[]]
|
||||
$Name,
|
||||
[psobject]
|
||||
$Manifest,
|
||||
[Alias('Arch', 'Architecture')]
|
||||
[string]
|
||||
$ProcessorArchitecture
|
||||
)
|
||||
|
||||
$uri = @(url $Manifest $ProcessorArchitecture)
|
||||
# 'extract_dir' and 'extract_to' are paired
|
||||
$extractDir = @(extract_dir $Manifest $ProcessorArchitecture)
|
||||
$extractTo = @(extract_to $Manifest $ProcessorArchitecture)
|
||||
$extracted = 0
|
||||
|
||||
for ($i = 0; $i -lt $Name.Length; $i++) {
|
||||
# work out extraction method, if applicable
|
||||
$extractFn = $null
|
||||
switch -regex ($Name[$i]) {
|
||||
'\.zip$' {
|
||||
if ((Test-HelperInstalled -Helper 7zip) -or ((get_config USE_EXTERNAL_7ZIP) -and (Test-CommandAvailable 7z))) {
|
||||
$extractFn = 'Expand-7zipArchive'
|
||||
} else {
|
||||
$extractFn = 'Expand-ZipArchive'
|
||||
}
|
||||
continue
|
||||
}
|
||||
'\.msi$' {
|
||||
$extractFn = 'Expand-MsiArchive'
|
||||
continue
|
||||
}
|
||||
'\.exe$' {
|
||||
if ($Manifest.innosetup) {
|
||||
$extractFn = 'Expand-InnoArchive'
|
||||
}
|
||||
continue
|
||||
}
|
||||
{ Test-7zipRequirement -Uri $_ } {
|
||||
$extractFn = 'Expand-7zipArchive'
|
||||
continue
|
||||
}
|
||||
}
|
||||
if ($extractFn) {
|
||||
$fnArgs = @{
|
||||
Path = Join-Path $Path $Name[$i]
|
||||
DestinationPath = Join-Path $Path $extractTo[$extracted]
|
||||
ExtractDir = $extractDir[$extracted]
|
||||
}
|
||||
Write-Host 'Extracting ' -NoNewline
|
||||
Write-Host $(url_remote_filename $uri[$i]) -ForegroundColor Cyan -NoNewline
|
||||
Write-Host ' ... ' -NoNewline
|
||||
& $extractFn @fnArgs -Removal
|
||||
Write-Host 'done.' -ForegroundColor Green
|
||||
$extracted++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Expand-7zipArchive {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
@@ -18,17 +82,18 @@ function Expand-7zipArchive {
|
||||
[Switch]
|
||||
$Removal
|
||||
)
|
||||
if ((get_config 7ZIPEXTRACT_USE_EXTERNAL)) {
|
||||
if ((get_config USE_EXTERNAL_7ZIP)) {
|
||||
try {
|
||||
$7zPath = (Get-Command '7z' -CommandType Application -ErrorAction Stop | Select-Object -First 1).Source
|
||||
} catch [System.Management.Automation.CommandNotFoundException] {
|
||||
abort "`nCannot find external 7-Zip (7z.exe) while '7ZIPEXTRACT_USE_EXTERNAL' is 'true'!`nRun 'scoop config 7ZIPEXTRACT_USE_EXTERNAL false' or install 7-Zip manually and try again."
|
||||
abort "`nCannot find external 7-Zip (7z.exe) while 'use_external_7zip' is 'true'!`nRun 'scoop config use_external_7zip false' or install 7-Zip manually and try again."
|
||||
}
|
||||
} else {
|
||||
$7zPath = Get-HelperPath -Helper 7zip
|
||||
}
|
||||
$LogPath = "$(Split-Path $Path)\7zip.log"
|
||||
$ArgList = @('x', $Path, "-o$DestinationPath", '-y')
|
||||
$DestinationPath = $DestinationPath.TrimEnd('\')
|
||||
$ArgList = @('x', $Path, "-o$DestinationPath", '-xr!*.nsis', '-y')
|
||||
$IsTar = ((strip_ext $Path) -match '\.tar$') -or ($Path -match '\.t[abgpx]z2?$')
|
||||
if (!$IsTar -and $ExtractDir) {
|
||||
$ArgList += "-ir!$ExtractDir\*"
|
||||
@@ -45,12 +110,6 @@ function Expand-7zipArchive {
|
||||
if (!$Status) {
|
||||
abort "Failed to extract files from $Path.`nLog file:`n $(friendly_path $LogPath)`n$(new_issue_msg $app $bucket 'decompress error')"
|
||||
}
|
||||
if (!$IsTar -and $ExtractDir) {
|
||||
movedir "$DestinationPath\$ExtractDir" $DestinationPath | Out-Null
|
||||
}
|
||||
if (Test-Path $LogPath) {
|
||||
Remove-Item $LogPath -Force
|
||||
}
|
||||
if ($IsTar) {
|
||||
# Check for tar
|
||||
$Status = Invoke-ExternalCommand $7zPath @('l', $Path) -LogPath $LogPath
|
||||
@@ -62,15 +121,26 @@ function Expand-7zipArchive {
|
||||
abort "Failed to list files in $Path.`nNot a 7-Zip supported archive file."
|
||||
}
|
||||
}
|
||||
if (!$IsTar -and $ExtractDir) {
|
||||
movedir "$DestinationPath\$ExtractDir" $DestinationPath | Out-Null
|
||||
# Remove temporary directory if it is empty
|
||||
$ExtractDirTopPath = [string] "$DestinationPath\$($ExtractDir -replace '[\\/].*')"
|
||||
if ((Get-ChildItem -Path $ExtractDirTopPath -Force -ErrorAction Ignore).Count -eq 0) {
|
||||
Remove-Item -Path $ExtractDirTopPath -Recurse -Force -ErrorAction Ignore
|
||||
}
|
||||
}
|
||||
if (Test-Path $LogPath) {
|
||||
Remove-Item $LogPath -Force
|
||||
}
|
||||
if ($Removal) {
|
||||
# Remove original archive file
|
||||
if (($Path -replace '.*\.([^\.]*)$', '$1') -eq '001') {
|
||||
# Remove splited 7-zip archive parts
|
||||
# Remove splitted 7-zip archive parts
|
||||
Get-ChildItem "$($Path -replace '\.[^\.]*$', '').???" | Remove-Item -Force
|
||||
} elseif (($Path -replace '.*\.part(\d+)\.rar$', '$1')[-1] -eq '1') {
|
||||
# Remove splitted RAR archive parts
|
||||
Get-ChildItem "$($Path -replace '\.part(\d+)\.rar$', '').part*.rar" | Remove-Item -Force
|
||||
} else {
|
||||
# Remove original archive file
|
||||
Remove-Item $Path -Force
|
||||
}
|
||||
}
|
||||
@@ -93,35 +163,9 @@ function Expand-ZstdArchive {
|
||||
[Switch]
|
||||
$Removal
|
||||
)
|
||||
$ZstdPath = Get-HelperPath -Helper Zstd
|
||||
$LogPath = Join-Path (Split-Path $Path) 'zstd.log'
|
||||
$DestinationPath = $DestinationPath.TrimEnd('\')
|
||||
ensure $DestinationPath | Out-Null
|
||||
$ArgList = @('-d', $Path, '--output-dir-flat', $DestinationPath, '-f', '-v')
|
||||
|
||||
if ($Switches) {
|
||||
$ArgList += (-split $Switches)
|
||||
}
|
||||
if ($Removal) {
|
||||
# Remove original archive file
|
||||
$ArgList += '--rm'
|
||||
}
|
||||
$Status = Invoke-ExternalCommand $ZstdPath $ArgList -LogPath $LogPath
|
||||
if (!$Status) {
|
||||
abort "Failed to extract files from $Path.`nLog file:`n $(friendly_path $LogPath)`n$(new_issue_msg $app $bucket 'decompress error')"
|
||||
}
|
||||
$IsTar = (strip_ext $Path) -match '\.tar$'
|
||||
if (!$IsTar -and $ExtractDir) {
|
||||
movedir (Join-Path $DestinationPath $ExtractDir) $DestinationPath | Out-Null
|
||||
}
|
||||
if (Test-Path $LogPath) {
|
||||
Remove-Item $LogPath -Force
|
||||
}
|
||||
if ($IsTar) {
|
||||
# Check for tar
|
||||
$TarFile = Join-Path $DestinationPath (strip_ext (fname $Path))
|
||||
Expand-7zipArchive -Path $TarFile -DestinationPath $DestinationPath -ExtractDir $ExtractDir -Removal
|
||||
}
|
||||
# TODO: Remove this function after 2024/12/31
|
||||
Show-DeprecatedWarning $MyInvocation 'Expand-7zipArchive'
|
||||
Expand-7zipArchive -Path $Path -DestinationPath $DestinationPath -ExtractDir $ExtractDir -Switches $Switches -Removal:$Removal
|
||||
}
|
||||
|
||||
function Expand-MsiArchive {
|
||||
@@ -146,12 +190,12 @@ function Expand-MsiArchive {
|
||||
$OriDestinationPath = $DestinationPath
|
||||
$DestinationPath = "$DestinationPath\_tmp"
|
||||
}
|
||||
if ((get_config MSIEXTRACT_USE_LESSMSI)) {
|
||||
if ((get_config USE_LESSMSI)) {
|
||||
$MsiPath = Get-HelperPath -Helper Lessmsi
|
||||
$ArgList = @('x', $Path, "$DestinationPath\")
|
||||
} else {
|
||||
$MsiPath = 'msiexec.exe'
|
||||
$ArgList = @('/a', "`"$Path`"", '/qn', "TARGETDIR=`"$DestinationPath\SourceDir`"")
|
||||
$ArgList = @('/a', $Path, '/qn', "TARGETDIR=$DestinationPath\SourceDir")
|
||||
}
|
||||
$LogPath = "$(Split-Path $Path)\msi.log"
|
||||
if ($Switches) {
|
||||
@@ -240,7 +284,14 @@ function Expand-ZipArchive {
|
||||
$OriDestinationPath = $DestinationPath
|
||||
$DestinationPath = "$DestinationPath\_tmp"
|
||||
}
|
||||
Expand-Archive -Path $Path -DestinationPath $DestinationPath -Force
|
||||
# Disable progress bar to gain performance
|
||||
$oldProgressPreference = $ProgressPreference
|
||||
$global:ProgressPreference = 'SilentlyContinue'
|
||||
|
||||
# Compatible with Pscx v3 (https://github.com/Pscx/Pscx) ('Microsoft.PowerShell.Archive' is not needed for Pscx v4)
|
||||
Microsoft.PowerShell.Archive\Expand-Archive -Path $Path -DestinationPath $DestinationPath -Force
|
||||
|
||||
$global:ProgressPreference = $oldProgressPreference
|
||||
if ($ExtractDir) {
|
||||
movedir "$DestinationPath\$ExtractDir" $OriDestinationPath | Out-Null
|
||||
Remove-Item $DestinationPath -Recurse -Force
|
||||
@@ -267,14 +318,32 @@ function Expand-DarkArchive {
|
||||
$Removal
|
||||
)
|
||||
$LogPath = "$(Split-Path $Path)\dark.log"
|
||||
$ArgList = @('-nologo', '-x', $DestinationPath, $Path)
|
||||
$DarkPath = Get-HelperPath -Helper Dark
|
||||
if ((Split-Path $DarkPath -Leaf) -eq 'wix.exe') {
|
||||
$ArgList = @('burn', 'extract', $Path, '-out', $DestinationPath, '-outba', "$DestinationPath\UX")
|
||||
} else {
|
||||
$ArgList = @('-nologo', '-x', $DestinationPath, $Path)
|
||||
}
|
||||
if ($Switches) {
|
||||
$ArgList += (-split $Switches)
|
||||
}
|
||||
$Status = Invoke-ExternalCommand (Get-HelperPath -Helper Dark) $ArgList -LogPath $LogPath
|
||||
$Status = Invoke-ExternalCommand $DarkPath $ArgList -LogPath $LogPath
|
||||
if (!$Status) {
|
||||
abort "Failed to extract files from $Path.`nLog file:`n $(friendly_path $LogPath)`n$(new_issue_msg $app $bucket 'decompress error')"
|
||||
}
|
||||
if (Test-Path "$DestinationPath\WixAttachedContainer") {
|
||||
Rename-Item "$DestinationPath\WixAttachedContainer" 'AttachedContainer' -ErrorAction Ignore
|
||||
} else {
|
||||
if (Test-Path "$DestinationPath\AttachedContainer\a0") {
|
||||
$Xml = [xml](Get-Content -Raw "$DestinationPath\UX\manifest.xml" -Encoding utf8)
|
||||
$Xml.BurnManifest.UX.Payload | ForEach-Object {
|
||||
Rename-Item "$DestinationPath\UX\$($_.SourcePath)" $_.FilePath -ErrorAction Ignore
|
||||
}
|
||||
$Xml.BurnManifest.Payload | ForEach-Object {
|
||||
Rename-Item "$DestinationPath\AttachedContainer\$($_.SourcePath)" $_.FilePath -ErrorAction Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Test-Path $LogPath) {
|
||||
Remove-Item $LogPath -Force
|
||||
}
|
||||
|
||||
@@ -37,9 +37,9 @@ function Get-Dependency {
|
||||
|
||||
if (!$manifest) {
|
||||
if (((Get-LocalBucket) -notcontains $bucket) -and $bucket) {
|
||||
warn "Bucket '$bucket' not installed. Add it with 'scoop bucket add $bucket' or 'scoop bucket add $bucket <repo>'."
|
||||
warn "Bucket '$bucket' not added. Add it with $(if($bucket -in (known_buckets)) { "'scoop bucket add $bucket' or " })'scoop bucket add $bucket <repo>'."
|
||||
}
|
||||
abort "Couldn't find manifest for '$AppName'$(if(!$bucket) { '.' } else { " from '$bucket' bucket." })"
|
||||
abort "Couldn't find manifest for '$AppName'$(if($bucket) { " from '$bucket' bucket" } elseif($url) { " at '$url'" })."
|
||||
}
|
||||
|
||||
$deps = @(Get-InstallationHelper $manifest $Architecture) + @($manifest.depends) | Select-Object -Unique
|
||||
@@ -106,10 +106,10 @@ function Get-InstallationHelper {
|
||||
$installer = arch_specific 'installer' $Manifest $Architecture
|
||||
$post_install = arch_specific 'post_install' $Manifest $Architecture
|
||||
$script = $pre_install + $installer.script + $post_install
|
||||
if (((Test-7zipRequirement -Uri $url) -or ($script -like '*Expand-7zipArchive *')) -and !(get_config 7ZIPEXTRACT_USE_EXTERNAL)) {
|
||||
if (((Test-7zipRequirement -Uri $url) -or ($script -like '*Expand-7zipArchive *')) -and !(get_config USE_EXTERNAL_7ZIP)) {
|
||||
$helper += '7zip'
|
||||
}
|
||||
if (((Test-LessmsiRequirement -Uri $url) -or ($script -like '*Expand-MsiArchive *')) -and (get_config MSIEXTRACT_USE_LESSMSI)) {
|
||||
if (((Test-LessmsiRequirement -Uri $url) -or ($script -like '*Expand-MsiArchive *')) -and (get_config USE_LESSMSI)) {
|
||||
$helper += 'lessmsi'
|
||||
}
|
||||
if ($Manifest.innosetup -or ($script -like '*Expand-InnoArchive *')) {
|
||||
@@ -118,11 +118,8 @@ function Get-InstallationHelper {
|
||||
if ($script -like '*Expand-DarkArchive *') {
|
||||
$helper += 'dark'
|
||||
}
|
||||
if ((Test-ZstdRequirement -Uri $url) -or ($script -like '*Expand-ZstdArchive *')) {
|
||||
$helper += 'zstd'
|
||||
}
|
||||
if (!$All) {
|
||||
'7zip', 'lessmsi', 'innounp', 'dark', 'zstd' | ForEach-Object {
|
||||
'7zip', 'lessmsi', 'innounp', 'dark' | ForEach-Object {
|
||||
if (Test-HelperInstalled -Helper $_) {
|
||||
$helper = $helper -ne $_
|
||||
}
|
||||
@@ -144,22 +141,10 @@ function Test-7zipRequirement {
|
||||
$Uri
|
||||
)
|
||||
return ($Uri | Where-Object {
|
||||
$_ -match '\.((gz)|(tar)|(t[abgpx]z2?)|(lzma)|(bz2?)|(7z)|(001)|(rar)|(iso)|(xz)|(lzh)|(nupkg))(\.[^\d.]+)?$'
|
||||
$_ -match '\.(001|7z|bz(ip)?2?|gz|img|iso|lzma|lzh|nupkg|rar|tar|t[abgpx]z2?|t?zst|xz)(\.[^\d.]+)?$'
|
||||
}).Count -gt 0
|
||||
}
|
||||
|
||||
function Test-ZstdRequirement {
|
||||
[CmdletBinding()]
|
||||
[OutputType([Boolean])]
|
||||
param (
|
||||
[Parameter(Mandatory = $true)]
|
||||
[AllowNull()]
|
||||
[String[]]
|
||||
$Uri
|
||||
)
|
||||
return ($Uri | Where-Object { $_ -match '\.zst$' }).Count -gt 0
|
||||
}
|
||||
|
||||
function Test-LessmsiRequirement {
|
||||
[CmdletBinding()]
|
||||
[OutputType([Boolean])]
|
||||
|
||||
@@ -53,3 +53,17 @@ function check_long_paths {
|
||||
|
||||
return $true
|
||||
}
|
||||
|
||||
function Get-WindowsDeveloperModeStatus {
|
||||
$DevModRegistryPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock"
|
||||
if (!(Test-Path -Path $DevModRegistryPath) -or (Get-ItemProperty -Path `
|
||||
$DevModRegistryPath -Name AllowDevelopmentWithoutDevLicense -ErrorAction `
|
||||
SilentlyContinue).AllowDevelopmentWithoutDevLicense -ne 1) {
|
||||
warn "Windows Developer Mode is not enabled. Operations relevant to symlinks may fail without proper rights."
|
||||
Write-Host " You may read more about the symlinks support here:"
|
||||
Write-Host " https://blogs.windows.com/windowsdeveloper/2016/12/02/symlinks-windows-10/"
|
||||
return $false
|
||||
}
|
||||
|
||||
return $true
|
||||
}
|
||||
|
||||
769
lib/download.ps1
Normal file
769
lib/download.ps1
Normal file
@@ -0,0 +1,769 @@
|
||||
# Description: Functions for downloading files
|
||||
|
||||
## Meta downloader
|
||||
|
||||
function Invoke-ScoopDownload ($app, $version, $manifest, $bucket, $architecture, $dir, $use_cache = $true, $check_hash = $true) {
|
||||
# we only want to show this warning once
|
||||
if (!$use_cache) { warn 'Cache is being ignored.' }
|
||||
|
||||
# can be multiple urls: if there are, then installer should go first to make 'installer.args' section work
|
||||
$urls = @(script:url $manifest $architecture)
|
||||
|
||||
# can be multiple cookies: they will be used for all HTTP requests.
|
||||
$cookies = $manifest.cookie
|
||||
|
||||
# download first
|
||||
if (Test-Aria2Enabled) {
|
||||
Invoke-CachedAria2Download $app $version $manifest $architecture $dir $cookies $use_cache $check_hash
|
||||
} else {
|
||||
foreach ($url in $urls) {
|
||||
$fname = url_filename $url
|
||||
|
||||
try {
|
||||
Invoke-CachedDownload $app $version $url "$dir\$fname" $cookies $use_cache
|
||||
} catch {
|
||||
Write-Host -ForegroundColor DarkRed $_
|
||||
abort "URL $url is not valid"
|
||||
}
|
||||
|
||||
if ($check_hash) {
|
||||
$manifest_hash = hash_for_url $manifest $url $architecture
|
||||
$ok, $err = check_hash "$dir\$fname" $manifest_hash $(show_app $app $bucket)
|
||||
if (!$ok) {
|
||||
error $err
|
||||
$cached = cache_path $app $version $url
|
||||
if (Test-Path $cached) {
|
||||
# rm cached file
|
||||
Remove-Item -Force $cached
|
||||
}
|
||||
if ($url.Contains('sourceforge.net')) {
|
||||
Write-Host -ForegroundColor Yellow 'SourceForge.net is known for causing hash validation fails. Please try again before opening a ticket.'
|
||||
}
|
||||
abort $(new_issue_msg $app $bucket 'hash check failed')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $urls.ForEach({ url_filename $_ })
|
||||
}
|
||||
|
||||
## [System.Net] downloader
|
||||
|
||||
function Invoke-CachedDownload ($app, $version, $url, $to, $cookies = $null, $use_cache = $true) {
|
||||
$cached = cache_path $app $version $url
|
||||
|
||||
if (!(Test-Path $cached) -or !$use_cache) {
|
||||
ensure $cachedir | Out-Null
|
||||
Start-Download $url "$cached.download" $cookies
|
||||
Move-Item "$cached.download" $cached -Force
|
||||
} else { Write-Host "Loading $(url_remote_filename $url) from cache" }
|
||||
|
||||
if (!($null -eq $to)) {
|
||||
if ($use_cache) {
|
||||
Copy-Item $cached $to
|
||||
} else {
|
||||
Move-Item $cached $to -Force
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Start-Download ($url, $to, $cookies) {
|
||||
$progress = [console]::isoutputredirected -eq $false -and
|
||||
$host.name -ne 'Windows PowerShell ISE Host'
|
||||
|
||||
try {
|
||||
$url = handle_special_urls $url
|
||||
Invoke-Download $url $to $cookies $progress
|
||||
} catch {
|
||||
$e = $_.exception
|
||||
if ($e.Response.StatusCode -eq 'Unauthorized') {
|
||||
warn 'Token might be misconfigured.'
|
||||
}
|
||||
if ($e.innerexception) { $e = $e.innerexception }
|
||||
throw $e
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-Download ($url, $to, $cookies, $progress) {
|
||||
# download with filesize and progress indicator
|
||||
$reqUrl = ($url -split '#')[0]
|
||||
$wreq = [Net.WebRequest]::Create($reqUrl)
|
||||
if ($wreq -is [Net.HttpWebRequest]) {
|
||||
$wreq.UserAgent = Get-UserAgent
|
||||
if (-not ($url -match 'sourceforge\.net' -or $url -match 'portableapps\.com')) {
|
||||
$wreq.Referer = strip_filename $url
|
||||
}
|
||||
if ($url -match 'api\.github\.com/repos') {
|
||||
$wreq.Accept = 'application/octet-stream'
|
||||
$wreq.Headers['Authorization'] = "Bearer $(Get-GitHubToken)"
|
||||
$wreq.Headers['X-GitHub-Api-Version'] = '2022-11-28'
|
||||
}
|
||||
if ($cookies) {
|
||||
$wreq.Headers.Add('Cookie', (cookie_header $cookies))
|
||||
}
|
||||
|
||||
get_config PRIVATE_HOSTS | Where-Object { $_ -ne $null -and $url -match $_.match } | ForEach-Object {
|
||||
(ConvertFrom-StringData -StringData $_.Headers).GetEnumerator() | ForEach-Object {
|
||||
$wreq.Headers[$_.Key] = $_.Value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$wres = $wreq.GetResponse()
|
||||
} catch [System.Net.WebException] {
|
||||
$exc = $_.Exception
|
||||
$handledCodes = @(
|
||||
[System.Net.HttpStatusCode]::MovedPermanently, # HTTP 301
|
||||
[System.Net.HttpStatusCode]::Found, # HTTP 302
|
||||
[System.Net.HttpStatusCode]::SeeOther, # HTTP 303
|
||||
[System.Net.HttpStatusCode]::TemporaryRedirect # HTTP 307
|
||||
)
|
||||
|
||||
# Only handle redirection codes
|
||||
$redirectRes = $exc.Response
|
||||
if ($handledCodes -notcontains $redirectRes.StatusCode) {
|
||||
throw $exc
|
||||
}
|
||||
|
||||
# Get the new location of the file
|
||||
if ((-not $redirectRes.Headers) -or ($redirectRes.Headers -notcontains 'Location')) {
|
||||
throw $exc
|
||||
}
|
||||
|
||||
$newUrl = $redirectRes.Headers['Location']
|
||||
info "Following redirect to $newUrl..."
|
||||
|
||||
# Handle manual file rename
|
||||
if ($url -like '*#/*') {
|
||||
$null, $postfix = $url -split '#/'
|
||||
$newUrl = "$newUrl`#/$postfix"
|
||||
}
|
||||
|
||||
Invoke-Download $newUrl $to $cookies $progress
|
||||
return
|
||||
}
|
||||
|
||||
$total = $wres.ContentLength
|
||||
if ($total -eq -1 -and $wreq -is [net.ftpwebrequest]) {
|
||||
$total = ftp_file_size($url)
|
||||
}
|
||||
|
||||
if ($progress -and ($total -gt 0)) {
|
||||
[console]::CursorVisible = $false
|
||||
function Trace-DownloadProgress ($read) {
|
||||
Write-DownloadProgress $read $total $url
|
||||
}
|
||||
} else {
|
||||
Write-Host "Downloading $url ($(filesize $total))..."
|
||||
function Trace-DownloadProgress {
|
||||
#no op
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$s = $wres.getresponsestream()
|
||||
$fs = [io.file]::openwrite($to)
|
||||
$buffer = New-Object byte[] 2048
|
||||
$totalRead = 0
|
||||
$sw = [diagnostics.stopwatch]::StartNew()
|
||||
|
||||
Trace-DownloadProgress $totalRead
|
||||
while (($read = $s.read($buffer, 0, $buffer.length)) -gt 0) {
|
||||
$fs.write($buffer, 0, $read)
|
||||
$totalRead += $read
|
||||
if ($sw.elapsedmilliseconds -gt 100) {
|
||||
$sw.restart()
|
||||
Trace-DownloadProgress $totalRead
|
||||
}
|
||||
}
|
||||
$sw.stop()
|
||||
Trace-DownloadProgress $totalRead
|
||||
} finally {
|
||||
if ($progress) {
|
||||
[console]::CursorVisible = $true
|
||||
Write-Host
|
||||
}
|
||||
if ($fs) {
|
||||
$fs.close()
|
||||
}
|
||||
if ($s) {
|
||||
$s.close()
|
||||
}
|
||||
$wres.close()
|
||||
}
|
||||
}
|
||||
|
||||
function Format-DownloadProgress ($url, $read, $total, $console) {
|
||||
$filename = url_remote_filename $url
|
||||
|
||||
# calculate current percentage done
|
||||
$p = [math]::Round($read / $total * 100, 0)
|
||||
|
||||
# pre-generate LHS and RHS of progress string
|
||||
# so we know how much space we have
|
||||
$left = "$filename ($(filesize $total))"
|
||||
$right = [string]::Format('{0,3}%', $p)
|
||||
|
||||
# calculate remaining width for progress bar
|
||||
$midwidth = $console.BufferSize.Width - ($left.Length + $right.Length + 8)
|
||||
|
||||
# calculate how many characters are completed
|
||||
$completed = [math]::Abs([math]::Round(($p / 100) * $midwidth, 0) - 1)
|
||||
|
||||
# generate dashes to symbolise completed
|
||||
if ($completed -gt 1) {
|
||||
$dashes = [string]::Join('', ((1..$completed) | ForEach-Object { '=' }))
|
||||
}
|
||||
|
||||
# this is why we calculate $completed - 1 above
|
||||
$dashes += switch ($p) {
|
||||
100 { '=' }
|
||||
default { '>' }
|
||||
}
|
||||
|
||||
# the remaining characters are filled with spaces
|
||||
$spaces = switch ($dashes.Length) {
|
||||
$midwidth { [string]::Empty }
|
||||
default {
|
||||
[string]::Join('', ((1..($midwidth - $dashes.Length)) | ForEach-Object { ' ' }))
|
||||
}
|
||||
}
|
||||
|
||||
"$left [$dashes$spaces] $right"
|
||||
}
|
||||
|
||||
function Write-DownloadProgress ($read, $total, $url) {
|
||||
$console = $Host.UI.RawUI
|
||||
$left = $console.CursorPosition.X
|
||||
$top = $console.CursorPosition.Y
|
||||
$width = $console.BufferSize.Width
|
||||
|
||||
if ($read -eq 0) {
|
||||
$maxOutputLength = $(Format-DownloadProgress $url 100 $total $console).Length
|
||||
if (($left + $maxOutputLength) -gt $width) {
|
||||
# not enough room to print progress on this line
|
||||
# print on new line
|
||||
Write-Host
|
||||
$left = 0
|
||||
$top = $top + 1
|
||||
if ($top -gt $console.CursorPosition.Y) { $top = $console.CursorPosition.Y }
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host $(Format-DownloadProgress $url $read $total $console) -NoNewline
|
||||
[console]::SetCursorPosition($left, $top)
|
||||
}
|
||||
|
||||
## Aria2 downloader
|
||||
|
||||
function Test-Aria2Enabled {
|
||||
return (Test-HelperInstalled -Helper Aria2) -and (get_config 'aria2-enabled' $true)
|
||||
}
|
||||
|
||||
function aria_exit_code($exitcode) {
|
||||
$codes = @{
|
||||
0 = 'All downloads were successful'
|
||||
1 = 'An unknown error occurred'
|
||||
2 = 'Timeout'
|
||||
3 = 'Resource was not found'
|
||||
4 = 'Aria2 saw the specified number of "resource not found" error. See --max-file-not-found option'
|
||||
5 = 'Download aborted because download speed was too slow. See --lowest-speed-limit option'
|
||||
6 = 'Network problem occurred.'
|
||||
7 = 'There were unfinished downloads. This error is only reported if all finished downloads were successful and there were unfinished downloads in a queue when aria2 exited by pressing Ctrl-C by an user or sending TERM or INT signal'
|
||||
8 = 'Remote server did not support resume when resume was required to complete download'
|
||||
9 = 'There was not enough disk space available'
|
||||
10 = 'Piece length was different from one in .aria2 control file. See --allow-piece-length-change option'
|
||||
11 = 'Aria2 was downloading same file at that moment'
|
||||
12 = 'Aria2 was downloading same info hash torrent at that moment'
|
||||
13 = 'File already existed. See --allow-overwrite option'
|
||||
14 = 'Renaming file failed. See --auto-file-renaming option'
|
||||
15 = 'Aria2 could not open existing file'
|
||||
16 = 'Aria2 could not create new file or truncate existing file'
|
||||
17 = 'File I/O error occurred'
|
||||
18 = 'Aria2 could not create directory'
|
||||
19 = 'Name resolution failed'
|
||||
20 = 'Aria2 could not parse Metalink document'
|
||||
21 = 'FTP command failed'
|
||||
22 = 'HTTP response header was bad or unexpected'
|
||||
23 = 'Too many redirects occurred'
|
||||
24 = 'HTTP authorization failed'
|
||||
25 = 'Aria2 could not parse bencoded file (usually ".torrent" file)'
|
||||
26 = '".torrent" file was corrupted or missing information that aria2 needed'
|
||||
27 = 'Magnet URI was bad'
|
||||
28 = 'Bad/unrecognized option was given or unexpected option argument was given'
|
||||
29 = 'The remote server was unable to handle the request due to a temporary overloading or maintenance'
|
||||
30 = 'Aria2 could not parse JSON-RPC request'
|
||||
31 = 'Reserved. Not used'
|
||||
32 = 'Checksum validation failed'
|
||||
}
|
||||
if ($null -eq $codes[$exitcode]) {
|
||||
return 'An unknown error occurred'
|
||||
}
|
||||
return $codes[$exitcode]
|
||||
}
|
||||
|
||||
function get_filename_from_metalink($file) {
|
||||
$bytes = get_magic_bytes_pretty $file ''
|
||||
# check if file starts with '<?xml'
|
||||
if (!($bytes.StartsWith('3c3f786d6c'))) {
|
||||
return $null
|
||||
}
|
||||
|
||||
# Add System.Xml for reading metalink files
|
||||
Add-Type -AssemblyName 'System.Xml'
|
||||
$xr = [System.Xml.XmlReader]::Create($file)
|
||||
$filename = $null
|
||||
try {
|
||||
$xr.ReadStartElement('metalink')
|
||||
if ($xr.ReadToFollowing('file') -and $xr.MoveToFirstAttribute()) {
|
||||
$filename = $xr.Value
|
||||
}
|
||||
} catch [System.Xml.XmlException] {
|
||||
return $null
|
||||
} finally {
|
||||
$xr.Close()
|
||||
}
|
||||
|
||||
return $filename
|
||||
}
|
||||
|
||||
function Invoke-CachedAria2Download ($app, $version, $manifest, $architecture, $dir, $cookies = $null, $use_cache = $true, $check_hash = $true) {
|
||||
$data = @{}
|
||||
$urls = @(script:url $manifest $architecture)
|
||||
|
||||
# aria2 input file
|
||||
$urlstxt = Join-Path $cachedir "$app.txt"
|
||||
$urlstxt_content = ''
|
||||
$download_finished = $true
|
||||
|
||||
# aria2 options
|
||||
$options = @(
|
||||
"--input-file='$urlstxt'"
|
||||
"--user-agent='$(Get-UserAgent)'"
|
||||
'--allow-overwrite=true'
|
||||
'--auto-file-renaming=false'
|
||||
"--retry-wait=$(get_config 'aria2-retry-wait' 2)"
|
||||
"--split=$(get_config 'aria2-split' 5)"
|
||||
"--max-connection-per-server=$(get_config 'aria2-max-connection-per-server' 5)"
|
||||
"--min-split-size=$(get_config 'aria2-min-split-size' '5M')"
|
||||
'--console-log-level=warn'
|
||||
'--enable-color=false'
|
||||
'--no-conf=true'
|
||||
'--follow-metalink=true'
|
||||
'--metalink-preferred-protocol=https'
|
||||
'--min-tls-version=TLSv1.2'
|
||||
"--stop-with-process=$PID"
|
||||
'--continue'
|
||||
'--summary-interval=0'
|
||||
'--auto-save-interval=1'
|
||||
)
|
||||
|
||||
if ($cookies) {
|
||||
$options += "--header='Cookie: $(cookie_header $cookies)'"
|
||||
}
|
||||
|
||||
$proxy = get_config PROXY
|
||||
if ($proxy -ne 'none') {
|
||||
if ([Net.Webrequest]::DefaultWebProxy.Address) {
|
||||
$options += "--all-proxy='$([Net.Webrequest]::DefaultWebProxy.Address.Authority)'"
|
||||
}
|
||||
if ([Net.Webrequest]::DefaultWebProxy.Credentials.UserName) {
|
||||
$options += "--all-proxy-user='$([Net.Webrequest]::DefaultWebProxy.Credentials.UserName)'"
|
||||
}
|
||||
if ([Net.Webrequest]::DefaultWebProxy.Credentials.Password) {
|
||||
$options += "--all-proxy-passwd='$([Net.Webrequest]::DefaultWebProxy.Credentials.Password)'"
|
||||
}
|
||||
}
|
||||
|
||||
$more_options = get_config 'aria2-options'
|
||||
if ($more_options) {
|
||||
$options += $more_options
|
||||
}
|
||||
|
||||
foreach ($url in $urls) {
|
||||
$data.$url = @{
|
||||
'target' = Join-Path $dir (url_filename $url)
|
||||
'cachename' = fname (cache_path $app $version $url)
|
||||
'source' = cache_path $app $version $url
|
||||
}
|
||||
|
||||
if ((Test-Path $data.$url.source) -and -not((Test-Path "$($data.$url.source).aria2") -or (Test-Path $urlstxt)) -and $use_cache) {
|
||||
Write-Host 'Loading ' -NoNewline
|
||||
Write-Host $(url_remote_filename $url) -ForegroundColor Cyan -NoNewline
|
||||
Write-Host ' from cache.'
|
||||
} else {
|
||||
$download_finished = $false
|
||||
# create aria2 input file content
|
||||
try {
|
||||
$try_url = handle_special_urls $url
|
||||
} catch {
|
||||
if ($_.Exception.Response.StatusCode -eq 'Unauthorized') {
|
||||
warn 'Token might be misconfigured.'
|
||||
}
|
||||
}
|
||||
$urlstxt_content += "$try_url`n"
|
||||
if (!$url.Contains('sourceforge.net')) {
|
||||
$urlstxt_content += " referer=$(strip_filename $url)`n"
|
||||
}
|
||||
$urlstxt_content += " dir=$cachedir`n"
|
||||
$urlstxt_content += " out=$($data.$url.cachename)`n"
|
||||
}
|
||||
}
|
||||
|
||||
if (-not($download_finished)) {
|
||||
# write aria2 input file
|
||||
if ($urlstxt_content -ne '') {
|
||||
ensure $cachedir | Out-Null
|
||||
# Write aria2 input-file with UTF8NoBOM encoding
|
||||
$urlstxt_content | Out-UTF8File -FilePath $urlstxt
|
||||
}
|
||||
|
||||
# build aria2 command
|
||||
$aria2 = "& '$(Get-HelperPath -Helper Aria2)' $($options -join ' ')"
|
||||
|
||||
# handle aria2 console output
|
||||
Write-Host 'Starting download with aria2 ...'
|
||||
|
||||
# Set console output encoding to UTF8 for non-ASCII characters printing
|
||||
$oriConsoleEncoding = [Console]::OutputEncoding
|
||||
[Console]::OutputEncoding = New-Object System.Text.UTF8Encoding
|
||||
|
||||
Invoke-Command ([scriptblock]::Create($aria2)) | ForEach-Object {
|
||||
# Skip blank lines
|
||||
if ([String]::IsNullOrWhiteSpace($_)) { return }
|
||||
|
||||
# Prevent potential overlaping of text when one line is shorter
|
||||
$len = $Host.UI.RawUI.WindowSize.Width - $_.Length - 20
|
||||
$blank = if ($len -gt 0) { ' ' * $len } else { '' }
|
||||
$color = 'Gray'
|
||||
|
||||
if ($_.StartsWith('(OK):')) {
|
||||
$noNewLine = $true
|
||||
$color = 'Green'
|
||||
} elseif ($_.StartsWith('[') -and $_.EndsWith(']')) {
|
||||
$noNewLine = $true
|
||||
$color = 'Cyan'
|
||||
} elseif ($_.StartsWith('Download Results:')) {
|
||||
$noNewLine = $false
|
||||
}
|
||||
|
||||
Write-Host "`rDownload: $_$blank" -ForegroundColor $color -NoNewline:$noNewLine
|
||||
}
|
||||
Write-Host ''
|
||||
|
||||
if ($lastexitcode -gt 0) {
|
||||
warn "Download failed! (Error $lastexitcode) $(aria_exit_code $lastexitcode)"
|
||||
warn $urlstxt_content
|
||||
warn $aria2
|
||||
warn $(new_issue_msg $app $bucket "download via aria2 failed")
|
||||
|
||||
Write-Host "Fallback to default downloader ..."
|
||||
|
||||
try {
|
||||
foreach ($url in $urls) {
|
||||
Invoke-CachedDownload $app $version $url "$($data.$url.target)" $cookies $use_cache
|
||||
}
|
||||
} catch {
|
||||
Write-Host $_ -ForegroundColor DarkRed
|
||||
abort "URL $url is not valid"
|
||||
}
|
||||
}
|
||||
|
||||
# remove aria2 input file when done
|
||||
if (Test-Path $urlstxt, "$($data.$url.source).aria2*") {
|
||||
Remove-Item $urlstxt -Force -ErrorAction SilentlyContinue
|
||||
Remove-Item "$($data.$url.source).aria2*" -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
# Revert console encoding
|
||||
[Console]::OutputEncoding = $oriConsoleEncoding
|
||||
}
|
||||
|
||||
foreach ($url in $urls) {
|
||||
|
||||
$metalink_filename = get_filename_from_metalink $data.$url.source
|
||||
if ($metalink_filename) {
|
||||
Remove-Item $data.$url.source -Force
|
||||
Rename-Item -Force (Join-Path -Path $cachedir -ChildPath $metalink_filename) $data.$url.source
|
||||
}
|
||||
|
||||
# run hash checks
|
||||
if ($check_hash) {
|
||||
$manifest_hash = hash_for_url $manifest $url $architecture
|
||||
$ok, $err = check_hash $data.$url.source $manifest_hash $(show_app $app $bucket)
|
||||
if (!$ok) {
|
||||
error $err
|
||||
if (Test-Path $data.$url.source) {
|
||||
# rm cached file
|
||||
Remove-Item $data.$url.source -Force -ErrorAction SilentlyContinue
|
||||
Remove-Item "$($data.$url.source).aria2*" -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
if ($url.Contains('sourceforge.net')) {
|
||||
Write-Host -f yellow 'SourceForge.net is known for causing hash validation fails. Please try again before opening a ticket.'
|
||||
}
|
||||
abort $(new_issue_msg $app $bucket 'hash check failed')
|
||||
}
|
||||
}
|
||||
|
||||
# copy or move file to target location
|
||||
if (!(Test-Path $data.$url.source) ) {
|
||||
abort $(new_issue_msg $app $bucket 'cached file not found')
|
||||
}
|
||||
|
||||
if (!($dir -eq $cachedir)) {
|
||||
if ($use_cache) {
|
||||
Copy-Item $data.$url.source $data.$url.target
|
||||
} else {
|
||||
Move-Item $data.$url.source $data.$url.target -Force
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
## Helper functions
|
||||
|
||||
### Downloader parameters
|
||||
|
||||
function cookie_header($cookies) {
|
||||
if (!$cookies) { return }
|
||||
|
||||
$vals = $cookies.psobject.properties | ForEach-Object {
|
||||
"$($_.name)=$($_.value)"
|
||||
}
|
||||
|
||||
[string]::join(';', $vals)
|
||||
}
|
||||
|
||||
function Get-Encoding($wc) {
|
||||
if ($null -ne $wc.ResponseHeaders -and $wc.ResponseHeaders['Content-Type'] -match 'charset=([^;]*)') {
|
||||
return [System.Text.Encoding]::GetEncoding($Matches[1])
|
||||
} else {
|
||||
return [System.Text.Encoding]::GetEncoding('utf-8')
|
||||
}
|
||||
}
|
||||
|
||||
function Get-UserAgent() {
|
||||
return "Scoop/1.0 (+http://scoop.sh/) PowerShell/$($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor) (Windows NT $([System.Environment]::OSVersion.Version.Major).$([System.Environment]::OSVersion.Version.Minor); $(if(${env:ProgramFiles(Arm)}){'ARM64; '}elseif($env:PROCESSOR_ARCHITECTURE -eq 'AMD64'){'Win64; x64; '})$(if($env:PROCESSOR_ARCHITEW6432 -in 'AMD64','ARM64'){'WOW64; '})$PSEdition)"
|
||||
}
|
||||
|
||||
function setup_proxy() {
|
||||
# note: '@' and ':' in password must be escaped, e.g. 'p@ssword' -> p\@ssword'
|
||||
$proxy = get_config PROXY
|
||||
if (!$proxy) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
$credentials, $address = $proxy -split '(?<!\\)@'
|
||||
if (!$address) {
|
||||
$address, $credentials = $credentials, $null # no credentials supplied
|
||||
}
|
||||
|
||||
if ($address -eq 'none') {
|
||||
[net.webrequest]::defaultwebproxy = $null
|
||||
} elseif ($address -ne 'default') {
|
||||
[net.webrequest]::defaultwebproxy = New-Object net.webproxy "http://$address"
|
||||
}
|
||||
|
||||
if ($credentials -eq 'currentuser') {
|
||||
[net.webrequest]::defaultwebproxy.credentials = [net.credentialcache]::defaultcredentials
|
||||
} elseif ($credentials) {
|
||||
$username, $password = $credentials -split '(?<!\\):' | ForEach-Object { $_ -replace '\\([@:])', '$1' }
|
||||
[net.webrequest]::defaultwebproxy.credentials = New-Object net.networkcredential($username, $password)
|
||||
}
|
||||
} catch {
|
||||
warn "Failed to use proxy '$proxy': $($_.exception.message)"
|
||||
}
|
||||
}
|
||||
|
||||
function Get-GitHubToken {
|
||||
return $env:SCOOP_GH_TOKEN, (get_config GH_TOKEN) | Where-Object -Property Length -Value 0 -GT | Select-Object -First 1
|
||||
}
|
||||
|
||||
function github_ratelimit_reached {
|
||||
$api_link = 'https://api.github.com/rate_limit'
|
||||
$ret = (download_json $api_link).rate.remaining -eq 0
|
||||
if ($ret) {
|
||||
Write-Host "GitHub API rate limit reached.`r`nPlease try again later or configure your API token using 'scoop config gh_token <your token>'."
|
||||
}
|
||||
$ret
|
||||
}
|
||||
|
||||
### URL handling
|
||||
|
||||
function handle_special_urls($url) {
|
||||
# FossHub.com
|
||||
if ($url -match '^(?:.*fosshub.com\/)(?<name>.*)(?:\/|\?dwl=)(?<filename>.*)$') {
|
||||
$Body = @{
|
||||
projectUri = $Matches.name
|
||||
fileName = $Matches.filename
|
||||
source = 'CF'
|
||||
isLatestVersion = $true
|
||||
}
|
||||
if ((Invoke-RestMethod -Uri $url) -match '"p":"(?<pid>[a-f0-9]{24}).*?"r":"(?<rid>[a-f0-9]{24})') {
|
||||
$Body.Add('projectId', $Matches.pid)
|
||||
$Body.Add('releaseId', $Matches.rid)
|
||||
}
|
||||
$url = Invoke-RestMethod -Method Post -Uri 'https://api.fosshub.com/download/' -ContentType 'application/json' -Body (ConvertTo-Json $Body -Compress)
|
||||
if ($null -eq $url.error) {
|
||||
$url = $url.data.url
|
||||
}
|
||||
}
|
||||
|
||||
# Sourceforge.net
|
||||
if ($url -match '(?:downloads\.)?sourceforge.net\/projects?\/(?<project>[^\/]+)\/(?:files\/)?(?<file>.*?)(?:$|\/download|\?)') {
|
||||
# Reshapes the URL to avoid redirections
|
||||
$url = "https://downloads.sourceforge.net/project/$($matches['project'])/$($matches['file'])"
|
||||
}
|
||||
|
||||
# Github.com
|
||||
if ($url -match 'github.com/(?<owner>[^/]+)/(?<repo>[^/]+)/releases/download/(?<tag>[^/]+)/(?<file>[^/#]+)(?<filename>.*)' -and ($token = Get-GitHubToken)) {
|
||||
$headers = @{ 'Authorization' = "token $token" }
|
||||
$privateUrl = "https://api.github.com/repos/$($Matches.owner)/$($Matches.repo)"
|
||||
$assetUrl = "https://api.github.com/repos/$($Matches.owner)/$($Matches.repo)/releases/tags/$($Matches.tag)"
|
||||
|
||||
if ((Invoke-RestMethod -Uri $privateUrl -Headers $headers).Private) {
|
||||
$url = ((Invoke-RestMethod -Uri $assetUrl -Headers $headers).Assets | Where-Object -Property Name -EQ -Value $Matches.file).Url, $Matches.filename -join ''
|
||||
}
|
||||
}
|
||||
|
||||
return $url
|
||||
}
|
||||
|
||||
### Remote file information
|
||||
|
||||
function download_json($url) {
|
||||
$githubtoken = Get-GitHubToken
|
||||
$authheader = @{}
|
||||
if ($githubtoken) {
|
||||
$authheader = @{'Authorization' = "token $githubtoken" }
|
||||
}
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
$result = Invoke-WebRequest $url -UseBasicParsing -Headers $authheader | Select-Object -ExpandProperty content | ConvertFrom-Json
|
||||
$ProgressPreference = 'Continue'
|
||||
$result
|
||||
}
|
||||
|
||||
function get_magic_bytes($file) {
|
||||
if (!(Test-Path $file)) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if ((Get-Command Get-Content).parameters.ContainsKey('AsByteStream')) {
|
||||
# PowerShell Core (6.0+) '-Encoding byte' is replaced by '-AsByteStream'
|
||||
return Get-Content $file -AsByteStream -TotalCount 8
|
||||
} else {
|
||||
return Get-Content $file -Encoding byte -TotalCount 8
|
||||
}
|
||||
}
|
||||
|
||||
function get_magic_bytes_pretty($file, $glue = ' ') {
|
||||
if (!(Test-Path $file)) {
|
||||
return ''
|
||||
}
|
||||
|
||||
return (get_magic_bytes $file | ForEach-Object { $_.ToString('x2') }) -join $glue
|
||||
}
|
||||
|
||||
Function Get-RemoteFileSize ($Uri) {
|
||||
$response = Invoke-WebRequest -Uri $Uri -Method HEAD -UseBasicParsing
|
||||
if (!$response.Headers.StatusCode) {
|
||||
$response.Headers.'Content-Length' | ForEach-Object { [int]$_ }
|
||||
}
|
||||
}
|
||||
|
||||
function ftp_file_size($url) {
|
||||
$request = [net.ftpwebrequest]::create($url)
|
||||
$request.method = [net.webrequestmethods+ftp]::getfilesize
|
||||
$request.getresponse().contentlength
|
||||
}
|
||||
|
||||
function url_filename($url) {
|
||||
(Split-Path $url -Leaf).split('?') | Select-Object -First 1
|
||||
}
|
||||
|
||||
function url_remote_filename($url) {
|
||||
# Unlike url_filename which can be tricked by appending a
|
||||
# URL fragment (e.g. #/dl.7z, useful for coercing a local filename),
|
||||
# this function extracts the original filename from the URL.
|
||||
$uri = (New-Object URI $url)
|
||||
$basename = Split-Path $uri.PathAndQuery -Leaf
|
||||
If ($basename -match '.*[?=]+([\w._-]+)') {
|
||||
$basename = $matches[1]
|
||||
}
|
||||
If (($basename -notlike '*.*') -or ($basename -match '^[v.\d]+$')) {
|
||||
$basename = Split-Path $uri.AbsolutePath -Leaf
|
||||
}
|
||||
If (($basename -notlike '*.*') -and ($uri.Fragment -ne '')) {
|
||||
$basename = $uri.Fragment.Trim('/', '#')
|
||||
}
|
||||
return $basename
|
||||
}
|
||||
|
||||
### Hash-related functions
|
||||
|
||||
function hash_for_url($manifest, $url, $arch) {
|
||||
$hashes = @(hash $manifest $arch) | Where-Object { $_ -ne $null }
|
||||
|
||||
if ($hashes.length -eq 0) { return $null }
|
||||
|
||||
$urls = @(script:url $manifest $arch)
|
||||
|
||||
$index = [array]::IndexOf($urls, $url)
|
||||
if ($index -eq -1) { abort "Couldn't find hash in manifest for '$url'." }
|
||||
|
||||
@($hashes)[$index]
|
||||
}
|
||||
|
||||
function check_hash($file, $hash, $app_name) {
|
||||
# returns (ok, err)
|
||||
if (!$hash) {
|
||||
warn "Warning: No hash in manifest. SHA256 for '$(fname $file)' is:`n $((Get-FileHash -Path $file -Algorithm SHA256).Hash.ToLower())"
|
||||
return $true, $null
|
||||
}
|
||||
|
||||
Write-Host 'Checking hash of ' -NoNewline
|
||||
Write-Host $(url_remote_filename $url) -ForegroundColor Cyan -NoNewline
|
||||
Write-Host ' ... ' -NoNewline
|
||||
$algorithm, $expected = get_hash $hash
|
||||
if ($null -eq $algorithm) {
|
||||
return $false, "Hash type '$algorithm' isn't supported."
|
||||
}
|
||||
|
||||
$actual = (Get-FileHash -Path $file -Algorithm $algorithm).Hash.ToLower()
|
||||
$expected = $expected.ToLower()
|
||||
|
||||
if ($actual -ne $expected) {
|
||||
$msg = "Hash check failed!`n"
|
||||
$msg += "App: $app_name`n"
|
||||
$msg += "URL: $url`n"
|
||||
if (Test-Path $file) {
|
||||
$msg += "First bytes: $((get_magic_bytes_pretty $file ' ').ToUpper())`n"
|
||||
}
|
||||
if ($expected -or $actual) {
|
||||
$msg += "Expected: $expected`n"
|
||||
$msg += "Actual: $actual"
|
||||
}
|
||||
return $false, $msg
|
||||
}
|
||||
Write-Host 'ok.' -f Green
|
||||
return $true, $null
|
||||
}
|
||||
|
||||
function get_hash([String] $multihash) {
|
||||
$type, $hash = $multihash -split ':'
|
||||
if (!$hash) {
|
||||
# no type specified, assume sha256
|
||||
$type, $hash = 'sha256', $multihash
|
||||
}
|
||||
|
||||
if (@('md5', 'sha1', 'sha256', 'sha512') -notcontains $type) {
|
||||
return $null, "Hash type '$type' isn't supported."
|
||||
}
|
||||
|
||||
return $type, $hash.ToLower()
|
||||
}
|
||||
|
||||
# Setup proxy globally
|
||||
setup_proxy
|
||||
@@ -8,7 +8,12 @@
|
||||
# array of strings that are long-form options. options that take
|
||||
# a parameter should end with '='
|
||||
# returns @(opts hash, remaining_args array, error string)
|
||||
function getopt($argv, $shortopts, $longopts) {
|
||||
# NOTES:
|
||||
# The first "--" in $argv, if any, will terminate all options; any
|
||||
# following arguments are treated as non-option arguments, even if
|
||||
# they begin with a hyphen. The "--" itself will not be included in
|
||||
# the returned $opts. (POSIX-compatible)
|
||||
function getopt([String[]]$argv, [String]$shortopts, [String[]]$longopts) {
|
||||
$opts = @{}; $rem = @()
|
||||
|
||||
function err($msg) {
|
||||
@@ -16,29 +21,31 @@ function getopt($argv, $shortopts, $longopts) {
|
||||
}
|
||||
|
||||
function regex_escape($str) {
|
||||
return [regex]::escape($str)
|
||||
return [Regex]::Escape($str)
|
||||
}
|
||||
|
||||
# ensure these are arrays
|
||||
$argv = @($argv)
|
||||
$longopts = @($longopts)
|
||||
|
||||
for($i = 0; $i -lt $argv.length; $i++) {
|
||||
for ($i = 0; $i -lt $argv.Length; $i++) {
|
||||
$arg = $argv[$i]
|
||||
if($null -eq $arg) { continue }
|
||||
if ($null -eq $arg) { continue }
|
||||
# don't try to parse array arguments
|
||||
if($arg -is [array]) { $rem += ,$arg; continue }
|
||||
if($arg -is [int]) { $rem += $arg; continue }
|
||||
if($arg -is [decimal]) { $rem += $arg; continue }
|
||||
if ($arg -is [Array]) { $rem += , $arg; continue }
|
||||
if ($arg -is [Int]) { $rem += $arg; continue }
|
||||
if ($arg -is [Decimal]) { $rem += $arg; continue }
|
||||
|
||||
if($arg.startswith('--')) {
|
||||
$name = $arg.substring(2)
|
||||
if ($arg -eq '--') {
|
||||
if ($i -lt $argv.Length - 1) {
|
||||
$rem += $argv[($i + 1)..($argv.Length - 1)]
|
||||
}
|
||||
break
|
||||
} elseif ($arg.StartsWith('--')) {
|
||||
$name = $arg.Substring(2)
|
||||
|
||||
$longopt = $longopts | Where-Object { $_ -match "^$name=?$" }
|
||||
|
||||
if($longopt) {
|
||||
if($longopt.endswith('=')) { # requires arg
|
||||
if($i -eq $argv.length - 1) {
|
||||
if ($longopt) {
|
||||
if ($longopt.EndsWith('=')) {
|
||||
# requires arg
|
||||
if ($i -eq $argv.Length - 1) {
|
||||
return err "Option --$name requires an argument."
|
||||
}
|
||||
$opts.$name = $argv[++$i]
|
||||
@@ -48,14 +55,14 @@ function getopt($argv, $shortopts, $longopts) {
|
||||
} else {
|
||||
return err "Option --$name not recognized."
|
||||
}
|
||||
} elseif($arg.startswith('-') -and $arg -ne '-') {
|
||||
for($j = 1; $j -lt $arg.length; $j++) {
|
||||
$letter = $arg[$j].tostring()
|
||||
} elseif ($arg.StartsWith('-') -and $arg -ne '-') {
|
||||
for ($j = 1; $j -lt $arg.Length; $j++) {
|
||||
$letter = $arg[$j].ToString()
|
||||
|
||||
if($shortopts -match "$(regex_escape $letter)`:?") {
|
||||
$shortopt = $matches[0]
|
||||
if($shortopt[1] -eq ':') {
|
||||
if($j -ne $arg.length -1 -or $i -eq $argv.length - 1) {
|
||||
if ($shortopts -match "$(regex_escape $letter)`:?") {
|
||||
$shortopt = $Matches[0]
|
||||
if ($shortopt[1] -eq ':') {
|
||||
if ($j -ne $arg.Length - 1 -or $i -eq $argv.Length - 1) {
|
||||
return err "Option -$letter requires an argument."
|
||||
}
|
||||
$opts.$letter = $argv[++$i]
|
||||
@@ -70,6 +77,5 @@ function getopt($argv, $shortopts, $longopts) {
|
||||
$rem += $arg
|
||||
}
|
||||
}
|
||||
|
||||
$opts, $rem
|
||||
}
|
||||
|
||||
1034
lib/install.ps1
1034
lib/install.ps1
File diff suppressed because it is too large
Load Diff
@@ -98,7 +98,9 @@ function json_path([String] $json, [String] $jsonpath, [Hashtable] $substitution
|
||||
$jsonpath = substitute $jsonpath $substitutions ($jsonpath -like "*=~*")
|
||||
}
|
||||
try {
|
||||
$obj = [Newtonsoft.Json.Linq.JValue]::Parse($json)
|
||||
$settings = New-Object -Type Newtonsoft.Json.JsonSerializerSettings
|
||||
$settings.DateParseHandling = [Newtonsoft.Json.DateParseHandling]::None
|
||||
$obj = [Newtonsoft.Json.JsonConvert]::DeserializeObject($json, $settings)
|
||||
} catch [Newtonsoft.Json.JsonReaderException] {
|
||||
return $null
|
||||
}
|
||||
@@ -108,13 +110,13 @@ function json_path([String] $json, [String] $jsonpath, [Hashtable] $substitution
|
||||
# Return versions in reverse order
|
||||
$result = [System.Linq.Enumerable]::Reverse($result)
|
||||
}
|
||||
if ($single) {
|
||||
if ([System.Linq.Enumerable]::Count($result) -eq 1 -or $single) {
|
||||
# Extract First value
|
||||
$result = [System.Linq.Enumerable]::First($result)
|
||||
# Convert first value to string
|
||||
$result = $result.ToString()
|
||||
} else {
|
||||
$result = "$([String]::Join('\n', $result))"
|
||||
$result = [Newtonsoft.Json.JsonConvert]::SerializeObject($result)
|
||||
}
|
||||
return $result
|
||||
} catch [Exception] {
|
||||
|
||||
149
lib/manifest.ps1
149
lib/manifest.ps1
@@ -1,13 +1,13 @@
|
||||
function manifest_path($app, $bucket) {
|
||||
fullpath "$(Find-BucketDirectory $bucket)\$(sanitary_path $app).json"
|
||||
(Get-ChildItem (Find-BucketDirectory $bucket) -Filter "$(sanitary_path $app).json" -Recurse).FullName
|
||||
}
|
||||
|
||||
function parse_json($path) {
|
||||
if (!(Test-Path $path)) { return $null }
|
||||
if ($null -eq $path -or !(Test-Path $path)) { return $null }
|
||||
try {
|
||||
Get-Content $path -Raw -Encoding UTF8 | ConvertFrom-Json -ErrorAction Stop
|
||||
} catch {
|
||||
warn "Error parsing JSON at $path."
|
||||
warn "Error parsing JSON at '$path'."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,11 +23,11 @@ function url_manifest($url) {
|
||||
} catch {
|
||||
throw
|
||||
}
|
||||
if(!$str) { return $null }
|
||||
if (!$str) { return $null }
|
||||
try {
|
||||
$str | ConvertFrom-Json -ErrorAction Stop
|
||||
} catch {
|
||||
warn "Error parsing JSON at $url."
|
||||
warn "Error parsing JSON at '$url'."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,31 +40,74 @@ function Get-Manifest($app) {
|
||||
$app = appname_from_url $url
|
||||
$manifest = url_manifest $url
|
||||
} else {
|
||||
$app, $bucket, $version = parse_app $app
|
||||
if ($bucket) {
|
||||
$manifest = manifest $app $bucket
|
||||
# Check if the manifest is already installed
|
||||
if (installed $app) {
|
||||
$global = installed $app $true
|
||||
$ver = Select-CurrentVersion -AppName $app -Global:$global
|
||||
if (!$ver) {
|
||||
$app, $bucket, $ver = parse_app $app
|
||||
$ver = Select-CurrentVersion -AppName $app -Global:$global
|
||||
}
|
||||
$install_info_path = "$(versiondir $app $ver $global)\install.json"
|
||||
if (Test-Path $install_info_path) {
|
||||
$install_info = parse_json $install_info_path
|
||||
$bucket = $install_info.bucket
|
||||
if (!$bucket) {
|
||||
$url = $install_info.url
|
||||
if ($url -match '^(ht|f)tps?://|\\\\') {
|
||||
$manifest = url_manifest $url
|
||||
}
|
||||
if (!$manifest) {
|
||||
if (Test-Path $url) {
|
||||
$manifest = parse_json $url
|
||||
} else {
|
||||
# Fallback to installed manifest
|
||||
$manifest = installed_manifest $app $ver $global
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$manifest = manifest $app $bucket
|
||||
if (!$manifest) {
|
||||
$deprecated_dir = (Find-BucketDirectory -Name $bucket -Root) + '\deprecated'
|
||||
$manifest = parse_json (Get-ChildItem $deprecated_dir -Filter "$(sanitary_path $app).json" -Recurse).FullName
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
foreach ($bucket in Get-LocalBucket) {
|
||||
$app, $bucket, $version = parse_app $app
|
||||
if ($bucket) {
|
||||
$manifest = manifest $app $bucket
|
||||
if ($manifest) {
|
||||
break
|
||||
} else {
|
||||
$matched_buckets = @()
|
||||
foreach ($tekcub in Get-LocalBucket) {
|
||||
$current_manifest = manifest $app $tekcub
|
||||
if (!$manifest -and $current_manifest) {
|
||||
$manifest = $current_manifest
|
||||
$bucket = $tekcub
|
||||
}
|
||||
if ($current_manifest) {
|
||||
$matched_buckets += $tekcub
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$manifest) {
|
||||
# couldn't find app in buckets: check if it's a local path
|
||||
if (Test-Path $app) {
|
||||
$url = Convert-Path $app
|
||||
$app = appname_from_url $url
|
||||
$manifest = parse_json $url
|
||||
} else {
|
||||
if (($app -match '\\/') -or $app.EndsWith('.json')) { $url = $app }
|
||||
$app = appname_from_url $app
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$manifest) {
|
||||
# couldn't find app in buckets: check if it's a local path
|
||||
$appPath = $app
|
||||
$bucket = $null
|
||||
if (!$appPath.EndsWith('.json')) {
|
||||
$appPath += '.json'
|
||||
}
|
||||
if (Test-Path $appPath) {
|
||||
$url = Convert-Path $appPath
|
||||
$app = appname_from_url $url
|
||||
$manifest = url_manifest $url
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($matched_buckets.Length -gt 1) {
|
||||
warn "Multiple buckets contain manifest '$app', the current selection is '$bucket/$app'."
|
||||
}
|
||||
|
||||
return $app, $manifest, $bucket, $url
|
||||
}
|
||||
|
||||
@@ -102,23 +145,6 @@ function install_info($app, $version, $global) {
|
||||
parse_json $path
|
||||
}
|
||||
|
||||
function default_architecture {
|
||||
$arch = get_config 'default_architecture'
|
||||
$system = if ([Environment]::Is64BitOperatingSystem) { '64bit' } else { '32bit' }
|
||||
if ($null -eq $arch) {
|
||||
$arch = $system
|
||||
} else {
|
||||
try {
|
||||
$arch = ensure_architecture $arch
|
||||
} catch {
|
||||
warn 'Invalid default architecture configured. Determining default system architecture'
|
||||
$arch = $system
|
||||
}
|
||||
}
|
||||
|
||||
return $arch
|
||||
}
|
||||
|
||||
function arch_specific($prop, $manifest, $architecture) {
|
||||
if ($manifest.architecture) {
|
||||
$val = $manifest.architecture.$architecture.$prop
|
||||
@@ -128,8 +154,22 @@ function arch_specific($prop, $manifest, $architecture) {
|
||||
if ($manifest.$prop) { return $manifest.$prop }
|
||||
}
|
||||
|
||||
function supports_architecture($manifest, $architecture) {
|
||||
return -not [String]::IsNullOrEmpty((arch_specific 'url' $manifest $architecture))
|
||||
function Get-SupportedArchitecture($manifest, $architecture) {
|
||||
if ($architecture -eq 'arm64' -and ($manifest | ConvertToPrettyJson) -notmatch '[''"]arm64["'']') {
|
||||
# Windows 10 enables existing unmodified x86 apps to run on Arm devices.
|
||||
# Windows 11 adds the ability to run unmodified x64 Windows apps on Arm devices!
|
||||
# Ref: https://learn.microsoft.com/en-us/windows/arm/overview
|
||||
if ($WindowsBuild -ge 22000) {
|
||||
# Windows 11
|
||||
$architecture = '64bit'
|
||||
} else {
|
||||
# Windows 10
|
||||
$architecture = '32bit'
|
||||
}
|
||||
}
|
||||
if (![String]::IsNullOrEmpty((arch_specific 'url' $manifest $architecture))) {
|
||||
return $architecture
|
||||
}
|
||||
}
|
||||
|
||||
function generate_user_manifest($app, $bucket, $version) {
|
||||
@@ -141,16 +181,26 @@ function generate_user_manifest($app, $bucket, $version) {
|
||||
warn "Given version ($version) does not match manifest ($($manifest.version))"
|
||||
warn "Attempting to generate manifest for '$app' ($version)"
|
||||
|
||||
ensure (usermanifestsdir) | Out-Null
|
||||
$manifest_path = "$(usermanifestsdir)\$app.json"
|
||||
|
||||
if (get_config USE_SQLITE_CACHE) {
|
||||
$cached_manifest = (Get-ScoopDBItem -Name $app -Bucket $bucket -Version $version).manifest
|
||||
if ($cached_manifest) {
|
||||
$cached_manifest | Out-UTF8File $manifest_path
|
||||
return $manifest_path
|
||||
}
|
||||
}
|
||||
|
||||
if (!($manifest.autoupdate)) {
|
||||
abort "'$app' does not have autoupdate capability`r`ncouldn't find manifest for '$app@$version'"
|
||||
}
|
||||
|
||||
ensure $(usermanifestsdir) | out-null
|
||||
try {
|
||||
Invoke-AutoUpdate $app "$(resolve-path $(usermanifestsdir))" $manifest $version $(@{ })
|
||||
return "$(resolve-path $(usermanifest $app))"
|
||||
Invoke-AutoUpdate $app $manifest_path $manifest $version $(@{ })
|
||||
return $manifest_path
|
||||
} catch {
|
||||
write-host -f darkred "Could not install $app@$version"
|
||||
Write-Host -ForegroundColor DarkRed "Could not install $app@$version"
|
||||
}
|
||||
|
||||
return $null
|
||||
@@ -159,7 +209,6 @@ function generate_user_manifest($app, $bucket, $version) {
|
||||
function url($manifest, $arch) { arch_specific 'url' $manifest $arch }
|
||||
function installer($manifest, $arch) { arch_specific 'installer' $manifest $arch }
|
||||
function uninstaller($manifest, $arch) { arch_specific 'uninstaller' $manifest $arch }
|
||||
function msi($manifest, $arch) { arch_specific 'msi' $manifest $arch }
|
||||
function hash($manifest, $arch) { arch_specific 'hash' $manifest $arch }
|
||||
function extract_dir($manifest, $arch) { arch_specific 'extract_dir' $manifest $arch}
|
||||
function extract_to($manifest, $arch) { arch_specific 'extract_to' $manifest $arch}
|
||||
function extract_dir($manifest, $arch) { arch_specific 'extract_dir' $manifest $arch }
|
||||
function extract_to($manifest, $arch) { arch_specific 'extract_to' $manifest $arch }
|
||||
|
||||
@@ -1,29 +1,24 @@
|
||||
$modulesdir = "$scoopdir\modules"
|
||||
|
||||
function install_psmodule($manifest, $dir, $global) {
|
||||
$psmodule = $manifest.psmodule
|
||||
if (!$psmodule) { return }
|
||||
|
||||
if ($global) {
|
||||
abort 'Installing PowerShell modules globally is not implemented!'
|
||||
}
|
||||
$targetdir = ensure (modulesdir $global)
|
||||
|
||||
$modulesdir = ensure $modulesdir
|
||||
ensure_in_psmodulepath $modulesdir $global
|
||||
ensure_in_psmodulepath $targetdir $global
|
||||
|
||||
$module_name = $psmodule.name
|
||||
if (!$module_name) {
|
||||
abort "Invalid manifest: The 'name' property is missing from 'psmodule'."
|
||||
}
|
||||
|
||||
$linkfrom = "$modulesdir\$module_name"
|
||||
$linkfrom = "$targetdir\$module_name"
|
||||
Write-Host "Installing PowerShell module '$module_name'"
|
||||
|
||||
Write-Host "Linking $(friendly_path $linkfrom) => $(friendly_path $dir)"
|
||||
|
||||
if (Test-Path $linkfrom) {
|
||||
warn "$(friendly_path $linkfrom) already exists. It will be replaced."
|
||||
Remove-Item -Path $linkfrom -Force -ErrorAction SilentlyContinue
|
||||
Remove-Item -Path $linkfrom -Force -Recurse -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
New-DirectoryJunction $linkfrom $dir | Out-Null
|
||||
@@ -36,24 +31,24 @@ function uninstall_psmodule($manifest, $dir, $global) {
|
||||
$module_name = $psmodule.name
|
||||
Write-Host "Uninstalling PowerShell module '$module_name'."
|
||||
|
||||
$linkfrom = "$modulesdir\$module_name"
|
||||
$targetdir = modulesdir $global
|
||||
|
||||
$linkfrom = "$targetdir\$module_name"
|
||||
if (Test-Path $linkfrom) {
|
||||
Write-Host "Removing $(friendly_path $linkfrom)"
|
||||
$linkfrom = Resolve-Path $linkfrom
|
||||
Remove-Item -Path $linkfrom -Force -ErrorAction SilentlyContinue
|
||||
$linkfrom = Convert-Path $linkfrom
|
||||
Remove-Item -Path $linkfrom -Force -Recurse -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
function ensure_in_psmodulepath($dir, $global) {
|
||||
$path = env 'psmodulepath' $global
|
||||
$path = Get-EnvVar -Name 'PSModulePath' -Global:$global
|
||||
if (!$global -and $null -eq $path) {
|
||||
$path = "$env:USERPROFILE\Documents\WindowsPowerShell\Modules"
|
||||
}
|
||||
$dir = fullpath $dir
|
||||
if ($path -notmatch [Regex]::Escape($dir)) {
|
||||
Write-Output "Adding $(friendly_path $dir) to $(if($global){'global'}else{'your'}) PowerShell module path."
|
||||
|
||||
env 'psmodulepath' $global "$dir;$path" # for future sessions...
|
||||
$env:psmodulepath = "$dir;$env:psmodulepath" # for this session
|
||||
Set-EnvVar -Name 'PSModulePath' -Value "$dir;$path" -Global:$global
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,16 +5,16 @@ function create_startmenu_shortcuts($manifest, $dir, $global, $arch) {
|
||||
$target = [System.IO.Path]::Combine($dir, $_.item(0))
|
||||
$target = New-Object System.IO.FileInfo($target)
|
||||
$name = $_.item(1)
|
||||
$arguments = ""
|
||||
$arguments = ''
|
||||
$icon = $null
|
||||
if($_.length -ge 3) {
|
||||
if ($_.length -ge 3) {
|
||||
$arguments = $_.item(2)
|
||||
}
|
||||
if($_.length -ge 4) {
|
||||
if ($_.length -ge 4) {
|
||||
$icon = [System.IO.Path]::Combine($dir, $_.item(3))
|
||||
$icon = New-Object System.IO.FileInfo($icon)
|
||||
}
|
||||
$arguments = (substitute $arguments @{ '$dir' = $dir; '$original_dir' = $original_dir; '$persist_dir' = $persist_dir})
|
||||
$arguments = (substitute $arguments @{ '$dir' = $dir; '$original_dir' = $original_dir; '$persist_dir' = $persist_dir })
|
||||
startmenu_shortcut $target $name $arguments $icon $global
|
||||
}
|
||||
}
|
||||
@@ -29,11 +29,11 @@ function shortcut_folder($global) {
|
||||
}
|
||||
|
||||
function startmenu_shortcut([System.IO.FileInfo] $target, $shortcutName, $arguments, [System.IO.FileInfo]$icon, $global) {
|
||||
if(!$target.Exists) {
|
||||
if (!$target.Exists) {
|
||||
Write-Host -f DarkRed "Creating shortcut for $shortcutName ($(fname $target)) failed: Couldn't find $target"
|
||||
return
|
||||
}
|
||||
if($icon -and !$icon.Exists) {
|
||||
if ($icon -and !$icon.Exists) {
|
||||
Write-Host -f DarkRed "Creating shortcut for $shortcutName ($(fname $target)) failed: Couldn't find icon $icon"
|
||||
return
|
||||
}
|
||||
@@ -51,11 +51,11 @@ function startmenu_shortcut([System.IO.FileInfo] $target, $shortcutName, $argume
|
||||
if ($arguments) {
|
||||
$wsShell.Arguments = $arguments
|
||||
}
|
||||
if($icon -and $icon.Exists) {
|
||||
if ($icon -and $icon.Exists) {
|
||||
$wsShell.IconLocation = $icon.FullName
|
||||
}
|
||||
$wsShell.Save()
|
||||
write-host "Creating shortcut for $shortcutName ($(fname $target))"
|
||||
Write-Host "Creating shortcut for $shortcutName ($(fname $target))"
|
||||
}
|
||||
|
||||
# Removes the Startmenu shortcut if it exists
|
||||
@@ -63,10 +63,10 @@ function rm_startmenu_shortcuts($manifest, $global, $arch) {
|
||||
$shortcuts = @(arch_specific 'shortcuts' $manifest $arch)
|
||||
$shortcuts | Where-Object { $_ -ne $null } | ForEach-Object {
|
||||
$name = $_.item(1)
|
||||
$shortcut = "$(shortcut_folder $global)\$name.lnk"
|
||||
write-host "Removing shortcut $(friendly_path $shortcut)"
|
||||
if(Test-Path -Path $shortcut) {
|
||||
Remove-Item $shortcut
|
||||
$shortcut = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath("$(shortcut_folder $global)\$name.lnk")
|
||||
Write-Host "Removing shortcut $(friendly_path $shortcut)"
|
||||
if (Test-Path -Path $shortcut) {
|
||||
Remove-Item $shortcut
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
176
lib/system.ps1
Normal file
176
lib/system.ps1
Normal file
@@ -0,0 +1,176 @@
|
||||
# System-related functions
|
||||
|
||||
## Environment Variables
|
||||
|
||||
function Publish-EnvVar {
|
||||
if (-not ('Win32.NativeMethods' -as [Type])) {
|
||||
Add-Type -Namespace Win32 -Name NativeMethods -MemberDefinition @'
|
||||
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
|
||||
public static extern IntPtr SendMessageTimeout(
|
||||
IntPtr hWnd, uint Msg, UIntPtr wParam, string lParam,
|
||||
uint fuFlags, uint uTimeout, out UIntPtr lpdwResult
|
||||
);
|
||||
'@
|
||||
}
|
||||
|
||||
$HWND_BROADCAST = [IntPtr] 0xffff
|
||||
$WM_SETTINGCHANGE = 0x1a
|
||||
$result = [UIntPtr]::Zero
|
||||
|
||||
[Win32.NativeMethods]::SendMessageTimeout($HWND_BROADCAST,
|
||||
$WM_SETTINGCHANGE,
|
||||
[UIntPtr]::Zero,
|
||||
'Environment',
|
||||
2,
|
||||
5000,
|
||||
[ref] $result
|
||||
) | Out-Null
|
||||
}
|
||||
|
||||
function Get-EnvVar {
|
||||
param(
|
||||
[string]$Name,
|
||||
[switch]$Global
|
||||
)
|
||||
|
||||
$registerKey = if ($Global) {
|
||||
Get-Item -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager'
|
||||
} else {
|
||||
Get-Item -Path 'HKCU:'
|
||||
}
|
||||
$envRegisterKey = $registerKey.OpenSubKey('Environment')
|
||||
$registryValueOption = [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames
|
||||
$envRegisterKey.GetValue($Name, $null, $registryValueOption)
|
||||
}
|
||||
|
||||
function Set-EnvVar {
|
||||
param(
|
||||
[string]$Name,
|
||||
[string]$Value,
|
||||
[switch]$Global
|
||||
)
|
||||
|
||||
$registerKey = if ($Global) {
|
||||
Get-Item -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager'
|
||||
} else {
|
||||
Get-Item -Path 'HKCU:'
|
||||
}
|
||||
$envRegisterKey = $registerKey.OpenSubKey('Environment', $true)
|
||||
if ($null -eq $Value -or $Value -eq '') {
|
||||
if ($envRegisterKey.GetValue($Name)) {
|
||||
$envRegisterKey.DeleteValue($Name)
|
||||
}
|
||||
} else {
|
||||
$registryValueKind = if ($Value.Contains('%')) {
|
||||
[Microsoft.Win32.RegistryValueKind]::ExpandString
|
||||
} elseif ($envRegisterKey.GetValue($Name)) {
|
||||
$envRegisterKey.GetValueKind($Name)
|
||||
} else {
|
||||
[Microsoft.Win32.RegistryValueKind]::String
|
||||
}
|
||||
$envRegisterKey.SetValue($Name, $Value, $registryValueKind)
|
||||
}
|
||||
Publish-EnvVar
|
||||
}
|
||||
|
||||
function Split-PathLikeEnvVar {
|
||||
param(
|
||||
[string[]]$Pattern,
|
||||
[string]$Path
|
||||
)
|
||||
|
||||
if ($null -eq $Path -and $Path -eq '') {
|
||||
return $null, $null
|
||||
} else {
|
||||
$splitPattern = $Pattern.Split(';', [System.StringSplitOptions]::RemoveEmptyEntries)
|
||||
$splitPath = $Path.Split(';', [System.StringSplitOptions]::RemoveEmptyEntries)
|
||||
$inPath = @()
|
||||
foreach ($p in $splitPattern) {
|
||||
$inPath += $splitPath.Where({ $_ -like $p })
|
||||
$splitPath = $splitPath.Where({ $_ -notlike $p })
|
||||
}
|
||||
return ($inPath -join ';'), ($splitPath -join ';')
|
||||
}
|
||||
}
|
||||
|
||||
function Add-Path {
|
||||
param(
|
||||
[string[]]$Path,
|
||||
[string]$TargetEnvVar = 'PATH',
|
||||
[switch]$Global,
|
||||
[switch]$Force,
|
||||
[switch]$Quiet
|
||||
)
|
||||
|
||||
# future sessions
|
||||
$inPath, $strippedPath = Split-PathLikeEnvVar $Path (Get-EnvVar -Name $TargetEnvVar -Global:$Global)
|
||||
if (!$inPath -or $Force) {
|
||||
if (!$Quiet) {
|
||||
$Path | ForEach-Object {
|
||||
Write-Host "Adding $(friendly_path $_) to $(if ($Global) {'global'} else {'your'}) path."
|
||||
}
|
||||
}
|
||||
Set-EnvVar -Name $TargetEnvVar -Value ((@($Path) + $strippedPath) -join ';') -Global:$Global
|
||||
}
|
||||
# current session
|
||||
$inPath, $strippedPath = Split-PathLikeEnvVar $Path $env:PATH
|
||||
if (!$inPath -or $Force) {
|
||||
$env:PATH = (@($Path) + $strippedPath) -join ';'
|
||||
}
|
||||
}
|
||||
|
||||
function Remove-Path {
|
||||
param(
|
||||
[string[]]$Path,
|
||||
[string]$TargetEnvVar = 'PATH',
|
||||
[switch]$Global,
|
||||
[switch]$Quiet,
|
||||
[switch]$PassThru
|
||||
)
|
||||
|
||||
# future sessions
|
||||
$inPath, $strippedPath = Split-PathLikeEnvVar $Path (Get-EnvVar -Name $TargetEnvVar -Global:$Global)
|
||||
if ($inPath) {
|
||||
if (!$Quiet) {
|
||||
$Path | ForEach-Object {
|
||||
Write-Host "Removing $(friendly_path $_) from $(if ($Global) {'global'} else {'your'}) path."
|
||||
}
|
||||
}
|
||||
Set-EnvVar -Name $TargetEnvVar -Value $strippedPath -Global:$Global
|
||||
}
|
||||
# current session
|
||||
$inSessionPath, $strippedPath = Split-PathLikeEnvVar $Path $env:PATH
|
||||
if ($inSessionPath) {
|
||||
$env:PATH = $strippedPath
|
||||
}
|
||||
if ($PassThru) {
|
||||
return $inPath
|
||||
}
|
||||
}
|
||||
|
||||
## Deprecated functions
|
||||
|
||||
function env($name, $global, $val) {
|
||||
if ($PSBoundParameters.ContainsKey('val')) {
|
||||
Show-DeprecatedWarning $MyInvocation 'Set-EnvVar'
|
||||
Set-EnvVar -Name $name -Value $val -Global:$global
|
||||
} else {
|
||||
Show-DeprecatedWarning $MyInvocation 'Get-EnvVar'
|
||||
Get-EnvVar -Name $name -Global:$global
|
||||
}
|
||||
}
|
||||
|
||||
function strip_path($orig_path, $dir) {
|
||||
Show-DeprecatedWarning $MyInvocation 'Split-PathLikeEnvVar'
|
||||
Split-PathLikeEnvVar -Pattern @($dir) -Path $orig_path
|
||||
}
|
||||
|
||||
function add_first_in_path($dir, $global) {
|
||||
Show-DeprecatedWarning $MyInvocation 'Add-Path'
|
||||
Add-Path -Path $dir -Global:$global -Force
|
||||
}
|
||||
|
||||
function remove_from_path($dir, $global) {
|
||||
Show-DeprecatedWarning $MyInvocation 'Remove-Path'
|
||||
Remove-Path -Path $dir -Global:$global
|
||||
}
|
||||
45
lib/unix.ps1
45
lib/unix.ps1
@@ -1,45 +0,0 @@
|
||||
# Note: This file is for overwriting global variables and functions to make
|
||||
# them unix compatible. It has to be imported after everything else!
|
||||
|
||||
function is_unix() { $PSVersionTable.Platform -eq 'Unix' }
|
||||
function is_mac() { $PSVersionTable.OS.ToLower().StartsWith('darwin') }
|
||||
function is_linux() { $PSVersionTable.OS.ToLower().StartsWith('linux') }
|
||||
|
||||
if(!(is_unix)) {
|
||||
return # get the hell outta here
|
||||
}
|
||||
|
||||
# core.ps1
|
||||
$scoopdir = $env:SCOOP, (get_config 'rootPath'), (Join-Path $env:HOME "scoop") | Select-Object -first 1
|
||||
$globaldir = $env:SCOOP_GLOBAL, (get_config 'globalPath'), "/usr/local/scoop" | Select-Object -first 1
|
||||
$cachedir = $env:SCOOP_CACHE, (get_config 'cachePath'), (Join-Path $scoopdir "cache") | Select-Object -first 1
|
||||
|
||||
# core.ps1
|
||||
function ensure($dir) {
|
||||
mkdir -p $dir > $null
|
||||
return resolve-path $dir
|
||||
}
|
||||
|
||||
# install.ps1
|
||||
function compute_hash($file, $algname) {
|
||||
if(is_mac) {
|
||||
switch ($algname)
|
||||
{
|
||||
"md5" { $result = (md5 -q $file) }
|
||||
"sha1" { $result = (shasum -ba 1 $file) }
|
||||
"sha256" { $result = (shasum -ba 256 $file) }
|
||||
"sha512" { $result = (shasum -ba 512 $file) }
|
||||
default { $result = (shasum -ba 256 $file) }
|
||||
}
|
||||
} else {
|
||||
switch ($algname)
|
||||
{
|
||||
"md5" { $result = (md5sum -b $file) }
|
||||
"sha1" { $result = (sha1sum -b $file) }
|
||||
"sha256" { $result = (sha256sum -b $file) }
|
||||
"sha512" { $result = (sha512sum -b $file) }
|
||||
default { $result = (sha256sum -b $file) }
|
||||
}
|
||||
}
|
||||
return $result.split(' ') | Select-Object -first 1
|
||||
}
|
||||
@@ -50,7 +50,7 @@ function Select-CurrentVersion { # 'manifest.ps1'
|
||||
)
|
||||
process {
|
||||
$currentPath = "$(appdir $AppName $Global)\current"
|
||||
if (!(get_config NO_JUNCTIONS)) {
|
||||
if (!(get_config NO_JUNCTION)) {
|
||||
$currentVersion = (parse_json "$currentPath\manifest.json").version
|
||||
if ($currentVersion -eq 'nightly') {
|
||||
$currentVersion = (Get-Item $currentPath).Target | Split-Path -Leaf
|
||||
@@ -147,9 +147,20 @@ function Compare-Version {
|
||||
$splitReferenceVersion = @(SplitVersion -Version $ReferenceVersion -Delimiter $Delimiter)
|
||||
$splitDifferenceVersion = @(SplitVersion -Version $DifferenceVersion -Delimiter $Delimiter)
|
||||
|
||||
# Nightly versions are always equal
|
||||
# Nightly versions are always equal unless UPDATE_NIGHTLY is $true
|
||||
if ($splitReferenceVersion[0] -eq 'nightly' -and $splitDifferenceVersion[0] -eq 'nightly') {
|
||||
return 0
|
||||
if (get_config UPDATE_NIGHTLY) {
|
||||
# nightly versions will be compared by date if UPDATE_NIGHTLY is $true
|
||||
if ($null -eq $splitReferenceVersion[1]) {
|
||||
$splitReferenceVersion += Get-Date -Format 'yyyyMMdd'
|
||||
}
|
||||
if ($null -eq $splitDifferenceVersion[1]) {
|
||||
$splitDifferenceVersion += Get-Date -Format 'yyyyMMdd'
|
||||
}
|
||||
return [Math]::Sign($splitDifferenceVersion[1] - $splitReferenceVersion[1])
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
for ($i = 0; $i -lt [Math]::Max($splitReferenceVersion.Length, $splitDifferenceVersion.Length); $i++) {
|
||||
|
||||
@@ -1,114 +1,68 @@
|
||||
# Usage: scoop alias add|list|rm [<args>]
|
||||
# Usage: scoop alias <subcommand> [options] [<args>]
|
||||
# Summary: Manage scoop aliases
|
||||
# Help: Add, remove or list Scoop aliases
|
||||
# Help: Available subcommands: add, rm, list.
|
||||
#
|
||||
# Aliases are custom Scoop subcommands that can be created to make common tasks
|
||||
# easier.
|
||||
# Aliases are custom Scoop subcommands that can be created to make common tasks easier.
|
||||
#
|
||||
# To add an Alias:
|
||||
# scoop alias add <name> <command> <description>
|
||||
# To add an alias:
|
||||
#
|
||||
# e.g.:
|
||||
# scoop alias add rm 'scoop uninstall $args[0]' 'Uninstalls an app'
|
||||
# scoop alias add upgrade 'scoop update *' 'Updates all apps, just like brew or apt'
|
||||
# scoop alias add <name> <command> [<description>]
|
||||
#
|
||||
# e.g.,
|
||||
#
|
||||
# scoop alias add rm 'scoop uninstall $args[0]' 'Uninstall an app'
|
||||
# scoop alias add upgrade 'scoop update *' 'Update all apps, just like "brew" or "apt"'
|
||||
#
|
||||
# To remove an alias:
|
||||
#
|
||||
# scoop alias rm <name>
|
||||
#
|
||||
# To list all aliases:
|
||||
#
|
||||
# scoop alias list [-v|--verbose]
|
||||
#
|
||||
# Options:
|
||||
# -v, --verbose Show alias description and table headers (works only for 'list')
|
||||
# -v, --verbose Show alias description and table headers (works only for "list")
|
||||
|
||||
param(
|
||||
[String]$opt,
|
||||
[String]$name,
|
||||
[String]$command,
|
||||
[String]$description,
|
||||
[Switch]$verbose = $false
|
||||
)
|
||||
param($SubCommand)
|
||||
|
||||
. "$PSScriptRoot\..\lib\install.ps1" # shim related
|
||||
. "$PSScriptRoot\..\lib\getopt.ps1"
|
||||
|
||||
$script:config_alias = 'alias'
|
||||
|
||||
function init_alias_config {
|
||||
$aliases = get_config $script:config_alias
|
||||
if ($aliases) {
|
||||
$aliases
|
||||
$SubCommands = @('add', 'rm', 'list')
|
||||
if ($SubCommand -notin $SubCommands) {
|
||||
if (!$SubCommand) {
|
||||
error '<subcommand> missing'
|
||||
} else {
|
||||
New-Object -TypeName PSObject
|
||||
error "'$SubCommand' is not one of available subcommands: $($SubCommands -join ', ')"
|
||||
}
|
||||
my_usage
|
||||
exit 1
|
||||
}
|
||||
|
||||
function add_alias($name, $command) {
|
||||
if (!$command) {
|
||||
abort "Can't create an empty alias."
|
||||
$opt, $other, $err = getopt $Args 'v' 'verbose'
|
||||
if ($err) { "scoop alias: $err"; exit 1 }
|
||||
|
||||
$name, $command, $description = $other
|
||||
$verbose = $opt.v -or $opt.verbose
|
||||
|
||||
switch ($SubCommand) {
|
||||
'add' {
|
||||
if (!$name -or !$command) {
|
||||
error "<name> and <command> must be specified for subcommand 'add'"
|
||||
exit 1
|
||||
}
|
||||
add_alias $name $command $description
|
||||
}
|
||||
|
||||
# get current aliases from config
|
||||
$aliases = init_alias_config
|
||||
if ($aliases.$name) {
|
||||
abort "Alias $name already exists."
|
||||
'rm' {
|
||||
if (!$name) {
|
||||
error "<name> must be specified for subcommand 'rm'"
|
||||
exit 1
|
||||
}
|
||||
rm_alias $name
|
||||
}
|
||||
|
||||
$alias_file = "scoop-$name"
|
||||
|
||||
# generate script
|
||||
$shimdir = shimdir $false
|
||||
$script =
|
||||
@(
|
||||
"# Summary: $description",
|
||||
"$command"
|
||||
) -join "`r`n"
|
||||
$script | Out-UTF8File "$shimdir\$alias_file.ps1"
|
||||
|
||||
# add alias to config
|
||||
$aliases | Add-Member -MemberType NoteProperty -Name $name -Value $alias_file
|
||||
|
||||
set_config $script:config_alias $aliases | Out-Null
|
||||
}
|
||||
|
||||
function rm_alias($name) {
|
||||
$aliases = init_alias_config
|
||||
if (!$name) {
|
||||
abort 'Which alias should be removed?'
|
||||
'list' {
|
||||
list_aliases $verbose
|
||||
}
|
||||
|
||||
if ($aliases.$name) {
|
||||
"Removing alias $name..."
|
||||
|
||||
rm_shim $aliases.$name (shimdir $false)
|
||||
|
||||
$aliases.PSObject.Properties.Remove($name)
|
||||
set_config $script:config_alias $aliases | Out-Null
|
||||
} else {
|
||||
abort "Alias $name doesn't exist."
|
||||
}
|
||||
}
|
||||
|
||||
function list_aliases {
|
||||
$aliases = @()
|
||||
|
||||
(init_alias_config).PSObject.Properties.GetEnumerator() | ForEach-Object {
|
||||
$content = Get-Content (command_path $_.Name)
|
||||
$command = ($content | Select-Object -Skip 1).Trim()
|
||||
$summary = (summary $content).Trim()
|
||||
|
||||
$aliases += New-Object psobject -Property @{Name = $_.name; Summary = $summary; Command = $command }
|
||||
}
|
||||
|
||||
if (!$aliases.count) {
|
||||
info "No alias found."
|
||||
}
|
||||
$aliases = $aliases.GetEnumerator() | Sort-Object Name
|
||||
if ($verbose) {
|
||||
return $aliases | Select-Object Name, Command, Summary
|
||||
} else {
|
||||
return $aliases | Select-Object Name, Command
|
||||
}
|
||||
}
|
||||
|
||||
switch ($opt) {
|
||||
'add' { add_alias $name $command }
|
||||
'rm' { rm_alias $name }
|
||||
'list' { list_aliases }
|
||||
default { my_usage; exit 1 }
|
||||
}
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -19,6 +19,15 @@
|
||||
# scoop bucket known
|
||||
param($cmd, $name, $repo)
|
||||
|
||||
if (get_config NO_JUNCTION) {
|
||||
. "$PSScriptRoot\..\lib\versions.ps1"
|
||||
}
|
||||
|
||||
if (get_config USE_SQLITE_CACHE) {
|
||||
. "$PSScriptRoot\..\lib\manifest.ps1"
|
||||
. "$PSScriptRoot\..\lib\database.ps1"
|
||||
}
|
||||
|
||||
$usage_add = 'usage: scoop bucket add <name> [<repo>]'
|
||||
$usage_rm = 'usage: scoop bucket rm <name>'
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ param($cmd)
|
||||
|
||||
function cacheinfo($file) {
|
||||
$app, $version, $url = $file.Name -split '#'
|
||||
New-Object PSObject -Property @{ Name = $app; Version = $version; Length = $file.Length; URL = $url }
|
||||
New-Object PSObject -Property @{ Name = $app; Version = $version; Length = $file.Length }
|
||||
}
|
||||
|
||||
function cacheshow($app) {
|
||||
@@ -28,7 +28,7 @@ function cacheshow($app) {
|
||||
$files = @(Get-ChildItem $cachedir | Where-Object -Property Name -Value "^$app#" -Match)
|
||||
$totalLength = ($files | Measure-Object -Property Length -Sum).Sum
|
||||
|
||||
$files | ForEach-Object { cacheinfo $_ } | Select-Object Name, Version, Length, URL
|
||||
$files | ForEach-Object { cacheinfo $_ } | Select-Object Name, Version, Length
|
||||
|
||||
Write-Host "Total: $($files.Length) $(pluralize $files.Length 'file' 'files'), $(filesize $totalLength)" -ForegroundColor Yellow
|
||||
}
|
||||
@@ -48,7 +48,7 @@ function cacheremove($app) {
|
||||
|
||||
$files | ForEach-Object {
|
||||
$curr = cacheinfo $_
|
||||
Write-Host "Removing $($curr.URL)..."
|
||||
Write-Host "Removing $($_.Name)..."
|
||||
Remove-Item $_.FullName
|
||||
if(Test-Path "$cachedir\$($curr.Name).txt") {
|
||||
Remove-Item "$cachedir\$($curr.Name).txt"
|
||||
|
||||
@@ -1,24 +1,29 @@
|
||||
# Usage: scoop cat <app>
|
||||
# Summary: Show content of specified manifest. If available, `bat` will be used to pretty-print the JSON.
|
||||
# Summary: Show content of specified manifest.
|
||||
# Help: Show content of specified manifest.
|
||||
# If configured, `bat` will be used to pretty-print the JSON.
|
||||
# See `cat_style` in `scoop help config` for further information.
|
||||
|
||||
param($app)
|
||||
|
||||
. "$PSScriptRoot\..\lib\json.ps1" # 'ConvertToPrettyJson'
|
||||
. "$PSScriptRoot\..\lib\versions.ps1" # 'Select-CurrentVersion'
|
||||
. "$PSScriptRoot\..\lib\manifest.ps1" # 'Get-Manifest'
|
||||
. "$PSScriptRoot\..\lib\download.ps1" # 'Get-UserAgent'
|
||||
|
||||
if (!$app) { error '<app> missing'; my_usage; exit 1 }
|
||||
|
||||
$null, $manifest, $bucket, $url = Get-Manifest $app
|
||||
|
||||
if ($manifest) {
|
||||
$style = get_config cat_style
|
||||
if ($style) {
|
||||
$manifest | ConvertToPrettyJson | bat --no-paging --style $style --language json
|
||||
} else {
|
||||
$manifest | ConvertToPrettyJson
|
||||
}
|
||||
$style = get_config CAT_STYLE
|
||||
if ($style) {
|
||||
$manifest | ConvertToPrettyJson | bat --no-paging --style $style --language json
|
||||
} else {
|
||||
$manifest | ConvertToPrettyJson
|
||||
}
|
||||
} else {
|
||||
abort "Couldn't find manifest for '$app'$(if($url) { " at the URL $url" })."
|
||||
abort "Couldn't find manifest for '$app'$(if($bucket) { " from '$bucket' bucket" } elseif($url) { " at '$url'" })."
|
||||
}
|
||||
|
||||
exit $exitCode
|
||||
|
||||
@@ -10,38 +10,39 @@ $defenderIssues = 0
|
||||
|
||||
$adminPrivileges = ([System.Security.Principal.WindowsPrincipal] [System.Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)
|
||||
|
||||
if ($adminPrivileges) {
|
||||
if ($adminPrivileges -and $env:USERNAME -ne 'WDAGUtilityAccount') {
|
||||
$defenderIssues += !(check_windows_defender $false)
|
||||
$defenderIssues += !(check_windows_defender $true)
|
||||
}
|
||||
|
||||
$issues += !(check_main_bucket)
|
||||
$issues += !(check_long_paths)
|
||||
$issues += !(Get-WindowsDeveloperModeStatus)
|
||||
|
||||
if (!(Test-HelperInstalled -Helper 7zip)) {
|
||||
error "'7-Zip' is not installed! It's required for unpacking most programs. Please Run 'scoop install 7zip' or 'scoop install 7zip-zstd'."
|
||||
if (!(Test-HelperInstalled -Helper 7zip) -and !(get_config USE_EXTERNAL_7ZIP)) {
|
||||
warn "'7-Zip' is not installed! It's required for unpacking most programs. Please Run 'scoop install 7zip'."
|
||||
$issues++
|
||||
}
|
||||
|
||||
if (!(Test-HelperInstalled -Helper Innounp)) {
|
||||
error "'Inno Setup Unpacker' is not installed! It's required for unpacking InnoSetup files. Please run 'scoop install innounp'."
|
||||
warn "'Inno Setup Unpacker' is not installed! It's required for unpacking InnoSetup files. Please run 'scoop install innounp'."
|
||||
$issues++
|
||||
}
|
||||
|
||||
if (!(Test-HelperInstalled -Helper Dark)) {
|
||||
error "'dark' is not installed! It's required for unpacking installers created with the WiX Toolset. Please run 'scoop install dark' or 'scoop install wixtoolset'."
|
||||
warn "'dark' is not installed! It's required for unpacking installers created with the WiX Toolset. Please run 'scoop install dark' or 'scoop install wixtoolset'."
|
||||
$issues++
|
||||
}
|
||||
|
||||
$globaldir = New-Object System.IO.DriveInfo($globaldir)
|
||||
if ($globaldir.DriveFormat -ne 'NTFS') {
|
||||
error "Scoop requires an NTFS volume to work! Please point `$env:SCOOP_GLOBAL or 'globalPath' variable in '~/.config/scoop/config.json' to another Drive."
|
||||
error "Scoop requires an NTFS volume to work! Please point `$env:SCOOP_GLOBAL or 'global_path' variable in '~/.config/scoop/config.json' to another Drive."
|
||||
$issues++
|
||||
}
|
||||
|
||||
$scoopdir = New-Object System.IO.DriveInfo($scoopdir)
|
||||
if ($scoopdir.DriveFormat -ne 'NTFS') {
|
||||
error "Scoop requires an NTFS volume to work! Please point `$env:SCOOP or 'rootPath' variable in '~/.config/scoop/config.json' to another Drive."
|
||||
error "Scoop requires an NTFS volume to work! Please point `$env:SCOOP or 'root_path' variable in '~/.config/scoop/config.json' to another Drive."
|
||||
$issues++
|
||||
}
|
||||
|
||||
|
||||
@@ -21,20 +21,23 @@
|
||||
# Settings
|
||||
# --------
|
||||
#
|
||||
# 7ZIPEXTRACT_USE_EXTERNAL: $true|$false
|
||||
# use_external_7zip: $true|$false
|
||||
# External 7zip (from path) will be used for archives extraction.
|
||||
#
|
||||
# MSIEXTRACT_USE_LESSMSI: $true|$false
|
||||
# use_lessmsi: $true|$false
|
||||
# Prefer lessmsi utility over native msiexec.
|
||||
#
|
||||
# NO_JUNCTIONS: $true|$false
|
||||
# use_sqlite_cache: $true|$false
|
||||
# Use SQLite database for caching. This is useful for speeding up 'scoop search' and 'scoop shim' commands.
|
||||
#
|
||||
# no_junction: $true|$false
|
||||
# The 'current' version alias will not be used. Shims and shortcuts will point to specific version instead.
|
||||
#
|
||||
# SCOOP_REPO: http://github.com/ScoopInstaller/Scoop
|
||||
# scoop_repo: http://github.com/ScoopInstaller/Scoop
|
||||
# Git repository containining scoop source code.
|
||||
# This configuration is useful for custom forks.
|
||||
#
|
||||
# SCOOP_BRANCH: master|develop
|
||||
# scoop_branch: master|develop
|
||||
# Allow to use different branch than master.
|
||||
# Could be used for testing specific functionalities before released into all users.
|
||||
# If you want to receive updates earlier to test new functionalities use develop (see: 'https://github.com/ScoopInstaller/Scoop/issues/2939')
|
||||
@@ -47,9 +50,13 @@
|
||||
# * An empty or unset value for proxy is equivalent to 'default' (with no username or password)
|
||||
# * To bypass the system proxy and connect directly, use 'none' (with no username or password)
|
||||
#
|
||||
# default_architecture: 64bit|32bit
|
||||
# autostash_on_conflict: $true|$false
|
||||
# When a conflict is detected during updating, Scoop will auto-stash the uncommitted changes.
|
||||
# (Default is $false, which will abort the update)
|
||||
#
|
||||
# default_architecture: 64bit|32bit|arm64
|
||||
# Allow to configure preferred architecture for application installation.
|
||||
# If not specified, architecture is determined be system.
|
||||
# If not specified, architecture is determined by system.
|
||||
#
|
||||
# debug: $true|$false
|
||||
# Additional and detailed output will be shown.
|
||||
@@ -60,20 +67,20 @@
|
||||
# show_update_log: $true|$false
|
||||
# Do not show changed commits on 'scoop update'
|
||||
#
|
||||
# manifest_review: $true|$false
|
||||
# show_manifest: $true|$false
|
||||
# Displays the manifest of every app that's about to
|
||||
# be installed, then asks user if they wish to proceed.
|
||||
#
|
||||
# shim: kiennq|scoopcs|71
|
||||
# Choose scoop shim build.
|
||||
#
|
||||
# rootPath: $Env:UserProfile\scoop
|
||||
# root_path: $Env:UserProfile\scoop
|
||||
# Path to Scoop root directory.
|
||||
#
|
||||
# globalPath: $Env:ProgramData\scoop
|
||||
# global_path: $Env:ProgramData\scoop
|
||||
# Path to Scoop root directory for global apps.
|
||||
#
|
||||
# cachePath:
|
||||
# cache_path:
|
||||
# For downloads, defaults to 'cache' folder under Scoop root directory.
|
||||
#
|
||||
# gh_token:
|
||||
@@ -101,6 +108,21 @@
|
||||
# For example, if you want to access a private GitHub repository,
|
||||
# you need to add the host to this list with 'match' and 'headers' strings.
|
||||
#
|
||||
# hold_update_until:
|
||||
# Disable/Hold Scoop self-updates, until the specified date.
|
||||
# `scoop hold scoop` will set the value to one day later.
|
||||
# Should be in the format 'YYYY-MM-DD', 'YYYY/MM/DD' or any other forms that accepted by '[System.DateTime]::Parse()'.
|
||||
# Ref: https://docs.microsoft.com/dotnet/api/system.datetime.parse?view=netframework-4.5#StringToParse
|
||||
#
|
||||
# update_nightly: $true|$false
|
||||
# Nightly version is formatted as 'nightly-yyyyMMdd' and will be updated after one day if this is set to $true.
|
||||
# Otherwise, nightly version will not be updated unless `--force` is used.
|
||||
#
|
||||
# use_isolated_path: $true|$false|[string]
|
||||
# When set to $true, Scoop will use `SCOOP_PATH` environment variable to store apps' `PATH`s.
|
||||
# When set to arbitrary non-empty string, Scoop will use that string as the environment variable name instead.
|
||||
# This is useful when you want to isolate Scoop from the system `PATH`.
|
||||
#
|
||||
# ARIA2 configuration
|
||||
# -------------------
|
||||
#
|
||||
@@ -147,7 +169,11 @@ if (!$name) {
|
||||
if($null -eq $value) {
|
||||
Write-Host "'$name' is not set"
|
||||
} else {
|
||||
$value
|
||||
if ($value -is [System.DateTime]) {
|
||||
$value.ToString('o')
|
||||
} else {
|
||||
$value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ function choose_item($list, $query) {
|
||||
}
|
||||
|
||||
if (!$url) {
|
||||
scoop help create
|
||||
& "$PSScriptRoot\scoop-help.ps1" create
|
||||
} else {
|
||||
create_manifest $url
|
||||
}
|
||||
|
||||
@@ -3,16 +3,18 @@
|
||||
|
||||
. "$PSScriptRoot\..\lib\getopt.ps1"
|
||||
. "$PSScriptRoot\..\lib\depends.ps1" # 'Get-Dependency'
|
||||
. "$PSScriptRoot\..\lib\manifest.ps1" # 'default_architecture'
|
||||
. "$PSScriptRoot\..\lib\versions.ps1" # 'Select-CurrentVersion'
|
||||
. "$PSScriptRoot\..\lib\manifest.ps1" # 'Get-Manifest' (indirectly)
|
||||
. "$PSScriptRoot\..\lib\download.ps1" # 'Get-UserAgent'
|
||||
|
||||
$opt, $apps, $err = getopt $args 'a:' 'arch='
|
||||
$app = $apps[0]
|
||||
|
||||
if(!$app) { error '<app> missing'; my_usage; exit 1 }
|
||||
|
||||
$architecture = default_architecture
|
||||
$architecture = Get-DefaultArchitecture
|
||||
try {
|
||||
$architecture = ensure_architecture ($opt.a + $opt.arch)
|
||||
$architecture = Format-ArchitectureString ($opt.a + $opt.arch)
|
||||
} catch {
|
||||
abort "ERROR: $_"
|
||||
}
|
||||
@@ -20,7 +22,14 @@ try {
|
||||
$deps = @()
|
||||
Get-Dependency $app $architecture | ForEach-Object {
|
||||
$dep = [ordered]@{}
|
||||
$dep.Source, $dep.Name = $_ -split '/'
|
||||
|
||||
$app, $null, $bucket, $url = Get-Manifest $_
|
||||
if (!$url) {
|
||||
$bucket, $app = $_ -split '/'
|
||||
}
|
||||
$dep.Source = if ($url) { $url } else { $bucket }
|
||||
$dep.Name = $app
|
||||
|
||||
$deps += [PSCustomObject]$dep
|
||||
}
|
||||
$deps
|
||||
|
||||
@@ -14,25 +14,29 @@
|
||||
# scoop download path\to\app.json
|
||||
#
|
||||
# Options:
|
||||
# -f, --force Force download (overwrite cache)
|
||||
# -h, --no-hash-check Skip hash verification (use with caution!)
|
||||
# -u, --no-update-scoop Don't update Scoop before downloading if it's outdated
|
||||
# -a, --arch <32bit|64bit> Use the specified architecture, if the app supports it
|
||||
# -f, --force Force download (overwrite cache)
|
||||
# -s, --skip-hash-check Skip hash verification (use with caution!)
|
||||
# -u, --no-update-scoop Don't update Scoop before downloading if it's outdated
|
||||
# -a, --arch <32bit|64bit|arm64> Use the specified architecture, if the app supports it
|
||||
|
||||
. "$PSScriptRoot\..\lib\getopt.ps1"
|
||||
. "$PSScriptRoot\..\lib\json.ps1" # 'autoupdate.ps1' (indirectly)
|
||||
. "$PSScriptRoot\..\lib\autoupdate.ps1" # 'generate_user_manifest' (indirectly)
|
||||
. "$PSScriptRoot\..\lib\manifest.ps1" # 'default_architecture' 'generate_user_manifest' 'Get-Manifest'
|
||||
. "$PSScriptRoot\..\lib\install.ps1"
|
||||
. "$PSScriptRoot\..\lib\versions.ps1" # 'Select-CurrentVersion'
|
||||
. "$PSScriptRoot\..\lib\manifest.ps1" # 'generate_user_manifest' 'Get-Manifest'
|
||||
. "$PSScriptRoot\..\lib\download.ps1"
|
||||
if (get_config USE_SQLITE_CACHE) {
|
||||
. "$PSScriptRoot\..\lib\database.ps1"
|
||||
}
|
||||
|
||||
$opt, $apps, $err = getopt $args 'fhua:' 'force', 'no-hash-check', 'no-update-scoop', 'arch='
|
||||
$opt, $apps, $err = getopt $args 'fsua:' 'force', 'skip-hash-check', 'no-update-scoop', 'arch='
|
||||
if ($err) { error "scoop download: $err"; exit 1 }
|
||||
|
||||
$check_hash = !($opt.h -or $opt.'no-hash-check')
|
||||
$check_hash = !($opt.s -or $opt.'skip-hash-check')
|
||||
$use_cache = !($opt.f -or $opt.force)
|
||||
$architecture = default_architecture
|
||||
$architecture = Get-DefaultArchitecture
|
||||
try {
|
||||
$architecture = ensure_architecture ($opt.a + $opt.arch)
|
||||
$architecture = Format-ArchitectureString ($opt.a + $opt.arch)
|
||||
} catch {
|
||||
abort "ERROR: $_"
|
||||
}
|
||||
@@ -43,7 +47,7 @@ if (is_scoop_outdated) {
|
||||
if ($opt.u -or $opt.'no-update-scoop') {
|
||||
warn "Scoop is out of date."
|
||||
} else {
|
||||
scoop update
|
||||
& "$PSScriptRoot\scoop-update.ps1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +61,7 @@ foreach ($curr_app in $apps) {
|
||||
$app, $bucket, $version = parse_app $curr_app
|
||||
$app, $manifest, $bucket, $url = Get-Manifest "$bucket/$app"
|
||||
|
||||
info "Starting download for $app..."
|
||||
info "Downloading '$app'$(if ($version) { " ($version)" }) [$architecture]$(if ($bucket) { " from $bucket bucket" })"
|
||||
|
||||
# Generate manifest if there is different version in manifest
|
||||
if (($null -ne $version) -and ($manifest.version -ne $version)) {
|
||||
@@ -70,7 +74,7 @@ foreach ($curr_app in $apps) {
|
||||
}
|
||||
|
||||
if(!$manifest) {
|
||||
error "Couldn't find manifest for '$app'$(if($url) { " at the URL $url" })."
|
||||
error "Couldn't find manifest for '$app'$(if($bucket) { " from '$bucket' bucket" } elseif($url) { " at '$url'" })."
|
||||
continue
|
||||
}
|
||||
$version = $manifest.version
|
||||
@@ -85,21 +89,22 @@ foreach ($curr_app in $apps) {
|
||||
|
||||
$curr_check_hash = $check_hash
|
||||
if ($version -eq 'nightly') {
|
||||
$version = nightly_version $(get-date)
|
||||
$version = nightly_version
|
||||
$curr_check_hash = $false
|
||||
}
|
||||
|
||||
if(!(supports_architecture $manifest $architecture)) {
|
||||
error "'$app' doesn't support $architecture architecture!"
|
||||
$architecture = Get-SupportedArchitecture $manifest $architecture
|
||||
if ($null -eq $architecture) {
|
||||
error "'$app' doesn't support current architecture!"
|
||||
continue
|
||||
}
|
||||
|
||||
if(Test-Aria2Enabled) {
|
||||
dl_with_cache_aria2 $app $version $manifest $architecture $cachedir $manifest.cookie $use_cache $curr_check_hash
|
||||
Invoke-CachedAria2Download $app $version $manifest $architecture $cachedir $manifest.cookie $use_cache $curr_check_hash
|
||||
} else {
|
||||
foreach($url in script:url $manifest $architecture) {
|
||||
try {
|
||||
dl_with_cache $app $version $url $null $manifest.cookie $use_cache
|
||||
Invoke-CachedDownload $app $version $url $null $manifest.cookie $use_cache
|
||||
} catch {
|
||||
write-host -f darkred $_
|
||||
error "URL $url is not valid"
|
||||
|
||||
@@ -10,7 +10,7 @@ $export = @{}
|
||||
if ($args[0] -eq '-c' -or $args[0] -eq '--config') {
|
||||
$export.config = $scoopConfig
|
||||
# Remove machine-specific properties
|
||||
foreach ($prop in 'lastUpdate', 'rootPath', 'globalPath', 'cachePath', 'alias') {
|
||||
foreach ($prop in 'last_update', 'root_path', 'global_path', 'cache_path', 'alias') {
|
||||
$export.config.PSObject.Properties.Remove($prop)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,27 +29,40 @@ if ($global -and !(is_admin)) {
|
||||
exit 1
|
||||
}
|
||||
|
||||
$apps | ForEach-Object {
|
||||
$app = $_
|
||||
foreach ($app in $apps) {
|
||||
|
||||
if ($app -eq 'scoop') {
|
||||
$hold_update_until = [System.DateTime]::Now.AddDays(1)
|
||||
set_config HOLD_UPDATE_UNTIL $hold_update_until.ToString('o') | Out-Null
|
||||
success "$app is now held and might not be updated until $($hold_update_until.ToLocalTime())."
|
||||
continue
|
||||
}
|
||||
if (!(installed $app $global)) {
|
||||
if ($global) {
|
||||
error "'$app' is not installed globally."
|
||||
} else {
|
||||
error "'$app' is not installed."
|
||||
}
|
||||
return
|
||||
continue
|
||||
}
|
||||
|
||||
if (get_config NO_JUNCTIONS) {
|
||||
if (get_config NO_JUNCTION) {
|
||||
$version = Select-CurrentVersion -App $app -Global:$global
|
||||
} else {
|
||||
$version = 'current'
|
||||
}
|
||||
$dir = versiondir $app $version $global
|
||||
$json = install_info $app $version $global
|
||||
if (!$json) {
|
||||
error "Failed to hold '$app'."
|
||||
continue
|
||||
}
|
||||
$install = @{}
|
||||
$json | Get-Member -MemberType Properties | ForEach-Object { $install.Add($_.Name, $json.($_.Name)) }
|
||||
if ($install.hold) {
|
||||
info "'$app' is already held."
|
||||
continue
|
||||
}
|
||||
$install.hold = $true
|
||||
save_install_info $install $dir
|
||||
success "$app is now held and can not be updated anymore."
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
# Summary: Opens the app homepage
|
||||
param($app)
|
||||
|
||||
. "$PSScriptRoot\..\lib\versions.ps1" # 'Select-CurrentVersion'
|
||||
. "$PSScriptRoot\..\lib\manifest.ps1" # 'Get-Manifest'
|
||||
. "$PSScriptRoot\..\lib\download.ps1" # 'Get-UserAgent'
|
||||
|
||||
if ($app) {
|
||||
$null, $manifest, $bucket, $null = Get-Manifest $app
|
||||
|
||||
@@ -13,7 +13,7 @@ param(
|
||||
|
||||
$import = $null
|
||||
$bucket_names = @()
|
||||
$def_arch = default_architecture
|
||||
$def_arch = Get-DefaultArchitecture
|
||||
|
||||
if (Test-Path $scoopfile) {
|
||||
$import = parse_json $scoopfile
|
||||
@@ -34,18 +34,19 @@ foreach ($item in $import.buckets) {
|
||||
}
|
||||
|
||||
foreach ($item in $import.apps) {
|
||||
$instArgs = @()
|
||||
$holdArgs = @()
|
||||
$info = $item.Info -Split ', '
|
||||
$global = if ('Global install' -in $info) {
|
||||
' --global'
|
||||
} else {
|
||||
''
|
||||
if ('Global install' -in $info) {
|
||||
$instArgs += '--global'
|
||||
$holdArgs += '--global'
|
||||
}
|
||||
$arch = if ('64bit' -in $info -and '32bit' -eq $def_arch) {
|
||||
' --arch 64bit'
|
||||
} elseif ('32bit' -in $info -and '64bit' -eq $def_arch) {
|
||||
' --arch 32bit'
|
||||
} else {
|
||||
''
|
||||
if ('64bit' -in $info -and '64bit' -ne $def_arch) {
|
||||
$instArgs += '--arch', '64bit'
|
||||
} elseif ('32bit' -in $info -and '32bit' -ne $def_arch) {
|
||||
$instArgs += '--arch', '32bit'
|
||||
} elseif ('arm64' -in $info -and 'arm64' -ne $def_arch) {
|
||||
$instArgs += '--arch', 'arm64'
|
||||
}
|
||||
|
||||
$app = if ($item.Source -in $bucket_names) {
|
||||
@@ -56,9 +57,9 @@ foreach ($item in $import.apps) {
|
||||
$item.Source
|
||||
}
|
||||
|
||||
& "$PSScriptRoot\scoop-install.ps1" $app$global$arch
|
||||
& "$PSScriptRoot\scoop-install.ps1" $app @instArgs
|
||||
|
||||
if ('Held package' -in $info) {
|
||||
& "$PSScriptRoot\scoop-hold.ps1" $($item.Name)$global
|
||||
& "$PSScriptRoot\scoop-hold.ps1" $item.Name @holdArgs
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
# Usage: scoop info <app> [--verbose]
|
||||
# Usage: scoop info <app> [options]
|
||||
# Summary: Display information about an app
|
||||
# Options:
|
||||
# -v, --verbose Show full paths and URLs
|
||||
# Help: Options:
|
||||
# -v, --verbose Show full paths and URLs
|
||||
|
||||
. "$PSScriptRoot\..\lib\getopt.ps1"
|
||||
. "$PSScriptRoot\..\lib\manifest.ps1" # 'Get-Manifest'
|
||||
. "$PSScriptRoot\..\lib\versions.ps1" # 'Get-InstalledVersion'
|
||||
. "$PSScriptRoot\..\lib\versions.ps1" # 'Get-InstalledVersion', 'Select-CurrentVersion'
|
||||
. "$PSScriptRoot\..\lib\download.ps1" # 'Get-RemoteFileSize'
|
||||
|
||||
$opt, $app, $err = getopt $args 'v' 'verbose'
|
||||
$original_app = $app
|
||||
if ($err) { error "scoop info: $err"; exit 1 }
|
||||
$verbose = $opt.v -or $opt.verbose
|
||||
|
||||
@@ -22,7 +24,7 @@ if (!$manifest) {
|
||||
$global = installed $app $true
|
||||
$status = app_status $app $global
|
||||
$install = install_info $app $status.version $global
|
||||
$status.installed = $bucket -and $install.bucket -eq $bucket
|
||||
$status.installed = ($bucket -and $install.bucket -eq $bucket) -or (installed $app)
|
||||
$version_output = $manifest.version
|
||||
$manifest_file = if ($bucket) {
|
||||
manifest_path $app $bucket
|
||||
@@ -30,12 +32,30 @@ $manifest_file = if ($bucket) {
|
||||
$url
|
||||
}
|
||||
|
||||
# Standalone and Source detection
|
||||
if ((Test-Path $original_app) -or ($original_app -match '^(ht|f)tps?://|\\\\')) {
|
||||
$standalone = $true
|
||||
if (Test-Path $original_app) {
|
||||
$original_app = (Get-AbsolutePath "$original_app")
|
||||
}
|
||||
if ($install.url) {
|
||||
if (Test-Path $install.url) {
|
||||
$install_url = (Get-AbsolutePath $install.url)
|
||||
} else {
|
||||
$install_url = $install.url
|
||||
}
|
||||
}
|
||||
if ($original_app -eq $install_url) {
|
||||
$same_source = $true
|
||||
}
|
||||
}
|
||||
|
||||
if ($verbose) {
|
||||
$dir = currentdir $app $global
|
||||
$original_dir = versiondir $app $manifest.version $global
|
||||
$persist_dir = persistdir $app $global
|
||||
} else {
|
||||
$dir, $original_dir, $persist_dir = "<root>", "<root>", "<root>"
|
||||
$dir, $original_dir, $persist_dir = '<root>', '<root>', '<root>'
|
||||
}
|
||||
|
||||
if ($status.installed) {
|
||||
@@ -43,21 +63,39 @@ if ($status.installed) {
|
||||
if ($install.url) {
|
||||
$manifest_file = $install.url
|
||||
}
|
||||
if ($status.version -eq $manifest.version) {
|
||||
if ($status.deprecated) {
|
||||
$manifest_file = $status.deprecated
|
||||
} elseif ($standalone -and !$same_source) {
|
||||
$version_output = $manifest.version
|
||||
} elseif ($status.version -eq $manifest.version) {
|
||||
$version_output = $status.version
|
||||
} else {
|
||||
$version_output = "$($status.version) (Update to $($manifest.version) available)"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$item = [ordered]@{ Name = $app }
|
||||
if ($status.deprecated) {
|
||||
$item.Name += ' (DEPRECATED)'
|
||||
}
|
||||
if ($manifest.description) {
|
||||
$item.Description = $manifest.description
|
||||
}
|
||||
$item.Version = $version_output
|
||||
if ($bucket) {
|
||||
$item.Bucket = $bucket
|
||||
|
||||
$item.Source = if ($standalone) {
|
||||
$original_app
|
||||
} else {
|
||||
if ($install.bucket) {
|
||||
$install.bucket
|
||||
} elseif ($install.url) {
|
||||
$install.url
|
||||
} else {
|
||||
$bucket
|
||||
}
|
||||
}
|
||||
|
||||
if ($manifest.homepage) {
|
||||
$item.Website = $manifest.homepage.TrimEnd('/')
|
||||
}
|
||||
@@ -69,7 +107,7 @@ if ($manifest.license) {
|
||||
$manifest.license
|
||||
} elseif ($manifest.license -match '[|,]') {
|
||||
if ($verbose) {
|
||||
"$($manifest.license) ($(($manifest.license -Split "\||," | ForEach-Object { "https://spdx.org/licenses/$_.html" }) -join ', '))"
|
||||
"$($manifest.license) ($(($manifest.license -Split '\||,' | ForEach-Object { "https://spdx.org/licenses/$_.html" }) -join ', '))"
|
||||
} else {
|
||||
$manifest.license
|
||||
}
|
||||
@@ -84,7 +122,7 @@ if ($manifest.depends) {
|
||||
|
||||
if (Test-Path $manifest_file) {
|
||||
if (Get-Command git -ErrorAction Ignore) {
|
||||
$gitinfo = (git -C (Split-Path $manifest_file) log -1 -s --format='%aD#%an' $manifest_file 2> $null) -Split '#'
|
||||
$gitinfo = (Invoke-Git -Path (Split-Path $manifest_file) -ArgumentList @('log', '-1', '-s', '--format=%aD#%an', $manifest_file) 2> $null) -Split '#'
|
||||
}
|
||||
if ($gitinfo) {
|
||||
$item.'Updated at' = $gitinfo[0] | Get-Date
|
||||
@@ -100,11 +138,13 @@ if ($verbose) { $item.Manifest = $manifest_file }
|
||||
|
||||
if ($status.installed) {
|
||||
# Show installed versions
|
||||
$installed_output = @()
|
||||
Get-InstalledVersion -AppName $app -Global:$global | ForEach-Object {
|
||||
$installed_output += if ($verbose) { versiondir $app $_ $global } else { "$_$(if ($global) { " *global*" })" }
|
||||
if (!$standalone -or $same_source) {
|
||||
$installed_output = @()
|
||||
Get-InstalledVersion -AppName $app -Global:$global | ForEach-Object {
|
||||
$installed_output += if ($verbose) { versiondir $app $_ $global } else { "$_$(if ($global) { ' *global*' })" }
|
||||
}
|
||||
$item.Installed = $installed_output -join "`n"
|
||||
}
|
||||
$item.Installed = $installed_output -join "`n"
|
||||
|
||||
if ($verbose) {
|
||||
# Show size of installation
|
||||
@@ -112,7 +152,7 @@ if ($status.installed) {
|
||||
|
||||
# Collect file list from each location
|
||||
$appFiles = Get-ChildItem $appsdir -Filter $app
|
||||
$currentFiles = Get-ChildItem $appFiles -Filter (Select-CurrentVersion $app $global)
|
||||
$currentFiles = Get-ChildItem $appFiles.FullName -Filter (Select-CurrentVersion $app $global)
|
||||
$persistFiles = Get-ChildItem $persist_dir -ErrorAction Ignore # Will fail if app does not persist data
|
||||
$cacheFiles = Get-ChildItem $cachedir -Filter "$app#*"
|
||||
|
||||
@@ -120,7 +160,7 @@ if ($status.installed) {
|
||||
$fileTotals = @()
|
||||
foreach ($fileType in ($appFiles, $currentFiles, $persistFiles, $cacheFiles)) {
|
||||
if ($null -ne $fileType) {
|
||||
$fileSum = (Get-ChildItem $fileType -Recurse | Measure-Object -Property Length -Sum).Sum
|
||||
$fileSum = (Get-ChildItem $fileType.FullName -Recurse -File | Measure-Object -Property Length -Sum).Sum
|
||||
$fileTotals += coalesce $fileSum 0
|
||||
} else {
|
||||
$fileTotals += 0
|
||||
@@ -158,15 +198,15 @@ if ($status.installed) {
|
||||
if ($verbose) {
|
||||
# Get download size if app not installed
|
||||
$totalPackage = 0
|
||||
foreach ($url in @(url $manifest (default_architecture))) {
|
||||
foreach ($url in @(url $manifest (Get-DefaultArchitecture))) {
|
||||
try {
|
||||
if (Test-Path (fullpath (cache_path $app $manifest.version $url))) {
|
||||
$cached = " (latest version is cached)"
|
||||
if (Test-Path (cache_path $app $manifest.version $url)) {
|
||||
$cached = ' (latest version is cached)'
|
||||
} else {
|
||||
$cached = $null
|
||||
}
|
||||
|
||||
[int]$urlLength = (Invoke-WebRequest $url -Method Head).Headers.'Content-Length'[0]
|
||||
$urlLength = Get-RemoteFileSize $url
|
||||
$totalPackage += $urlLength
|
||||
} catch [System.Management.Automation.RuntimeException] {
|
||||
$totalPackage = 0
|
||||
@@ -196,7 +236,7 @@ if ($binaries) {
|
||||
$binary_output += $_
|
||||
}
|
||||
}
|
||||
$item.Binaries = $binary_output -join " | "
|
||||
$item.Binaries = $binary_output -join ' | '
|
||||
}
|
||||
$shortcuts = @(arch_specific 'shortcuts' $manifest $install.architecture)
|
||||
if ($shortcuts) {
|
||||
@@ -204,13 +244,13 @@ if ($shortcuts) {
|
||||
$shortcuts | ForEach-Object {
|
||||
$shortcut_output += $_[1]
|
||||
}
|
||||
$item.Shortcuts = $shortcut_output -join " | "
|
||||
$item.Shortcuts = $shortcut_output -join ' | '
|
||||
}
|
||||
$env_set = arch_specific 'env_set' $manifest $install.architecture
|
||||
if ($env_set) {
|
||||
$env_vars = @()
|
||||
$env_set | Get-Member -member noteproperty | ForEach-Object {
|
||||
$env_vars += "$($_.name) = $(format $env_set.$($_.name) @{ "dir" = $dir })"
|
||||
$env_vars += "$($_.name) = $(substitute $env_set.$($_.name) @{ '$dir' = $dir })"
|
||||
}
|
||||
$item.Environment = $env_vars -join "`n"
|
||||
}
|
||||
|
||||
@@ -10,38 +10,49 @@
|
||||
# To install an app from a manifest at a URL:
|
||||
# scoop install https://raw.githubusercontent.com/ScoopInstaller/Main/master/bucket/runat.json
|
||||
#
|
||||
# To install a different version of the app from a URL:
|
||||
# scoop install https://raw.githubusercontent.com/ScoopInstaller/Main/master/bucket/neovim.json@0.9.0
|
||||
#
|
||||
# To install an app from a manifest on your computer
|
||||
# scoop install \path\to\app.json
|
||||
#
|
||||
# To install an app from a manifest on your computer
|
||||
# scoop install \path\to\app.json@version
|
||||
#
|
||||
# Options:
|
||||
# -g, --global Install the app globally
|
||||
# -i, --independent Don't install dependencies automatically
|
||||
# -k, --no-cache Don't use the download cache
|
||||
# -u, --no-update-scoop Don't update Scoop before installing if it's outdated
|
||||
# -s, --skip Skip hash validation (use with caution!)
|
||||
# -a, --arch <32bit|64bit> Use the specified architecture, if the app supports it
|
||||
# -g, --global Install the app globally
|
||||
# -i, --independent Don't install dependencies automatically
|
||||
# -k, --no-cache Don't use the download cache
|
||||
# -s, --skip-hash-check Skip hash validation (use with caution!)
|
||||
# -u, --no-update-scoop Don't update Scoop before installing if it's outdated
|
||||
# -a, --arch <32bit|64bit|arm64> Use the specified architecture, if the app supports it
|
||||
|
||||
. "$PSScriptRoot\..\lib\getopt.ps1"
|
||||
. "$PSScriptRoot\..\lib\json.ps1" # 'autoupdate.ps1' 'manifest.ps1' (indirectly)
|
||||
. "$PSScriptRoot\..\lib\autoupdate.ps1" # 'generate_user_manifest' (indirectly)
|
||||
. "$PSScriptRoot\..\lib\manifest.ps1" # 'default_architecture' 'generate_user_manifest' 'Get-Manifest' 'Select-CurrentVersion' (indirectly)
|
||||
. "$PSScriptRoot\..\lib\manifest.ps1" # 'generate_user_manifest' 'Get-Manifest' 'Select-CurrentVersion' (indirectly)
|
||||
. "$PSScriptRoot\..\lib\system.ps1"
|
||||
. "$PSScriptRoot\..\lib\install.ps1"
|
||||
. "$PSScriptRoot\..\lib\download.ps1"
|
||||
. "$PSScriptRoot\..\lib\decompress.ps1"
|
||||
. "$PSScriptRoot\..\lib\shortcuts.ps1"
|
||||
. "$PSScriptRoot\..\lib\psmodules.ps1"
|
||||
. "$PSScriptRoot\..\lib\versions.ps1"
|
||||
. "$PSScriptRoot\..\lib\depends.ps1"
|
||||
if (get_config USE_SQLITE_CACHE) {
|
||||
. "$PSScriptRoot\..\lib\database.ps1"
|
||||
}
|
||||
|
||||
$opt, $apps, $err = getopt $args 'gikusa:' 'global', 'independent', 'no-cache', 'no-update-scoop', 'skip', 'arch='
|
||||
$opt, $apps, $err = getopt $args 'giksua:' 'global', 'independent', 'no-cache', 'skip-hash-check', 'no-update-scoop', 'arch='
|
||||
if ($err) { "scoop install: $err"; exit 1 }
|
||||
|
||||
$global = $opt.g -or $opt.global
|
||||
$check_hash = !($opt.s -or $opt.skip)
|
||||
$check_hash = !($opt.s -or $opt.'skip-hash-check')
|
||||
$independent = $opt.i -or $opt.independent
|
||||
$use_cache = !($opt.k -or $opt.'no-cache')
|
||||
$architecture = default_architecture
|
||||
$architecture = Get-DefaultArchitecture
|
||||
try {
|
||||
$architecture = ensure_architecture ($opt.a + $opt.arch)
|
||||
$architecture = Format-ArchitectureString ($opt.a + $opt.arch)
|
||||
} catch {
|
||||
abort "ERROR: $_"
|
||||
}
|
||||
@@ -56,7 +67,7 @@ if (is_scoop_outdated) {
|
||||
if ($opt.u -or $opt.'no-update-scoop') {
|
||||
warn "Scoop is out of date."
|
||||
} else {
|
||||
scoop update
|
||||
& "$PSScriptRoot\scoop-update.ps1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +92,7 @@ $specific_versions = $apps | Where-Object {
|
||||
}
|
||||
|
||||
# compare object does not like nulls
|
||||
if ($specific_versions.length -gt 0) {
|
||||
if ($specific_versions.Count -gt 0) {
|
||||
$difference = Compare-Object -ReferenceObject $apps -DifferenceObject $specific_versions -PassThru
|
||||
} else {
|
||||
$difference = $apps
|
||||
@@ -90,13 +101,13 @@ if ($specific_versions.length -gt 0) {
|
||||
$specific_versions_paths = $specific_versions | ForEach-Object {
|
||||
$app, $bucket, $version = parse_app $_
|
||||
if (installed_manifest $app $version) {
|
||||
warn "'$app' ($version) is already installed.`nUse 'scoop update $app$(if ($global) { " --global" })' to install a new version."
|
||||
warn "'$app' ($version) is already installed.`nUse 'scoop update $app$(if ($global) { ' --global' })' to install a new version."
|
||||
continue
|
||||
}
|
||||
|
||||
generate_user_manifest $app $bucket $version
|
||||
}
|
||||
$apps = @(($specific_versions_paths + $difference) | Where-Object { $_ } | Sort-Object -Unique)
|
||||
$apps = @((@($specific_versions_paths) + $difference) | Where-Object { $_ } | Select-Object -Unique)
|
||||
|
||||
# remember which were explictly requested so that we can
|
||||
# differentiate after dependencies are added
|
||||
|
||||
@@ -5,8 +5,9 @@ param($query)
|
||||
|
||||
. "$PSScriptRoot\..\lib\versions.ps1" # 'Select-CurrentVersion'
|
||||
. "$PSScriptRoot\..\lib\manifest.ps1" # 'parse_json' 'Select-CurrentVersion' (indirectly)
|
||||
. "$PSScriptRoot\..\lib\download.ps1" # 'Get-UserAgent'
|
||||
|
||||
$def_arch = default_architecture
|
||||
$defaultArchitecture = Get-DefaultArchitecture
|
||||
if (-not (Get-FormatData ScoopApps)) {
|
||||
Update-FormatData "$PSScriptRoot\..\supporting\formats\ScoopTypes.Format.ps1xml"
|
||||
}
|
||||
@@ -47,10 +48,11 @@ $apps | Where-Object { !$query -or ($_.name -match $query) } | ForEach-Object {
|
||||
$item.Updated = $updated
|
||||
|
||||
$info = @()
|
||||
if ((app_status $app $global).deprecated) { $info += 'Deprecated package'}
|
||||
if ($global) { $info += 'Global install' }
|
||||
if (failed $app $global) { $info += 'Install failed' }
|
||||
if ($install_info.hold) { $info += 'Held package' }
|
||||
if ($install_info.architecture -and $def_arch -ne $install_info.architecture) {
|
||||
if ($install_info.architecture -and $defaultArchitecture -ne $install_info.architecture) {
|
||||
$info += $install_info.architecture
|
||||
}
|
||||
$item.Info = $info -join ', '
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
. "$PSScriptRoot\..\lib\getopt.ps1"
|
||||
. "$PSScriptRoot\..\lib\manifest.ps1" # 'Select-CurrentVersion' (indirectly)
|
||||
. "$PSScriptRoot\..\lib\system.ps1" # 'env_add_path' (indirectly)
|
||||
. "$PSScriptRoot\..\lib\install.ps1"
|
||||
. "$PSScriptRoot\..\lib\versions.ps1" # 'Select-CurrentVersion'
|
||||
. "$PSScriptRoot\..\lib\shortcuts.ps1"
|
||||
@@ -63,13 +64,13 @@ $apps | ForEach-Object {
|
||||
|
||||
write-host "Resetting $app ($version)."
|
||||
|
||||
$dir = resolve-path (versiondir $app $version $global)
|
||||
$dir = Convert-Path (versiondir $app $version $global)
|
||||
$original_dir = $dir
|
||||
$persist_dir = persistdir $app $global
|
||||
|
||||
#region Workaround for #2952
|
||||
if (test_running_process $app $global) {
|
||||
continue
|
||||
return
|
||||
}
|
||||
#endregion Workaround for #2952
|
||||
|
||||
@@ -79,8 +80,11 @@ $apps | ForEach-Object {
|
||||
$dir = link_current $dir
|
||||
create_shims $manifest $dir $global $architecture
|
||||
create_startmenu_shortcuts $manifest $dir $global $architecture
|
||||
# unset all potential old env before re-adding
|
||||
env_rm_path $manifest $dir $global $architecture
|
||||
env_rm $manifest $global $architecture
|
||||
env_add_path $manifest $dir $global $architecture
|
||||
env_set $manifest $dir $global $architecture
|
||||
env_set $manifest $global $architecture
|
||||
# unlink all potential old link before re-persisting
|
||||
unlink_persist_data $manifest $original_dir
|
||||
persist_data $manifest $original_dir $persist_dir
|
||||
|
||||
@@ -3,25 +3,16 @@
|
||||
# Help: Searches for apps that are available to install.
|
||||
#
|
||||
# If used with [query], shows app names that match the query.
|
||||
# - With 'use_sqlite_cache' enabled, [query] is partially matched against app names, binaries, and shortcuts.
|
||||
# - Without 'use_sqlite_cache', [query] can be a regular expression to match against app names and binaries.
|
||||
# Without [query], shows all the available apps.
|
||||
param($query)
|
||||
|
||||
. "$PSScriptRoot\..\lib\manifest.ps1" # 'manifest'
|
||||
. "$PSScriptRoot\..\lib\versions.ps1" # 'Get-LatestVersion'
|
||||
. "$PSScriptRoot\..\lib\download.ps1"
|
||||
|
||||
$list = @()
|
||||
|
||||
try {
|
||||
$query = New-Object Regex $query, 'IgnoreCase'
|
||||
} catch {
|
||||
abort "Invalid regular expression: $($_.Exception.InnerException.Message)"
|
||||
}
|
||||
|
||||
$githubtoken = Get-GitHubToken
|
||||
$authheader = @{}
|
||||
if ($githubtoken) {
|
||||
$authheader = @{'Authorization' = "token $githubtoken"}
|
||||
}
|
||||
$list = [System.Collections.Generic.List[PSCustomObject]]::new()
|
||||
|
||||
function bin_match($manifest, $query) {
|
||||
if (!$manifest.bin) { return $false }
|
||||
@@ -32,41 +23,98 @@ function bin_match($manifest, $query) {
|
||||
if ((strip_ext $fname) -match $query) { $fname }
|
||||
elseif ($alias -match $query) { $alias }
|
||||
}
|
||||
|
||||
if ($bins) { return $bins }
|
||||
else { return $false }
|
||||
}
|
||||
|
||||
function bin_match_json($json, $query) {
|
||||
[System.Text.Json.JsonElement]$bin = [System.Text.Json.JsonElement]::new()
|
||||
if (!$json.RootElement.TryGetProperty('bin', [ref] $bin)) { return $false }
|
||||
$bins = @()
|
||||
if ($bin.ValueKind -eq [System.Text.Json.JsonValueKind]::String -and [System.IO.Path]::GetFileNameWithoutExtension($bin) -match $query) {
|
||||
$bins += [System.IO.Path]::GetFileName($bin)
|
||||
} elseif ($bin.ValueKind -eq [System.Text.Json.JsonValueKind]::Array) {
|
||||
foreach ($subbin in $bin.EnumerateArray()) {
|
||||
if ($subbin.ValueKind -eq [System.Text.Json.JsonValueKind]::String -and [System.IO.Path]::GetFileNameWithoutExtension($subbin) -match $query) {
|
||||
$bins += [System.IO.Path]::GetFileName($subbin)
|
||||
} elseif ($subbin.ValueKind -eq [System.Text.Json.JsonValueKind]::Array) {
|
||||
if ([System.IO.Path]::GetFileNameWithoutExtension($subbin[0]) -match $query) {
|
||||
$bins += [System.IO.Path]::GetFileName($subbin[0])
|
||||
} elseif ($subbin.GetArrayLength() -ge 2 -and $subbin[1] -match $query) {
|
||||
$bins += $subbin[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($bins) { return $bins }
|
||||
else { return $false }
|
||||
}
|
||||
|
||||
function search_bucket($bucket, $query) {
|
||||
$apps = apps_in_bucket (Find-BucketDirectory $bucket) | ForEach-Object { @{ name = $_ } }
|
||||
$apps = Get-ChildItem (Find-BucketDirectory $bucket) -Filter '*.json' -Recurse
|
||||
|
||||
if ($query) {
|
||||
$apps = $apps | Where-Object {
|
||||
if ($_.name -match $query) { return $true }
|
||||
$bin = bin_match (manifest $_.name $bucket) $query
|
||||
$apps | ForEach-Object {
|
||||
$filepath = $_.FullName
|
||||
|
||||
$json = try {
|
||||
[System.Text.Json.JsonDocument]::Parse([System.IO.File]::ReadAllText($filepath))
|
||||
} catch {
|
||||
debug "Failed to parse manifest file: $filepath (error: $_)"
|
||||
return
|
||||
}
|
||||
|
||||
$name = $_.BaseName
|
||||
|
||||
if ($name -match $query) {
|
||||
$list.Add([PSCustomObject]@{
|
||||
Name = $name
|
||||
Version = $json.RootElement.GetProperty('version')
|
||||
Source = $bucket
|
||||
Binaries = ''
|
||||
})
|
||||
} else {
|
||||
$bin = bin_match_json $json $query
|
||||
if ($bin) {
|
||||
$_.bin = $bin
|
||||
return $true
|
||||
$list.Add([PSCustomObject]@{
|
||||
Name = $name
|
||||
Version = $json.RootElement.GetProperty('version')
|
||||
Source = $bucket
|
||||
Binaries = $bin -join ' | '
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
$apps | ForEach-Object { $_.version = (Get-LatestVersion -AppName $_.name -Bucket $bucket); $_ }
|
||||
}
|
||||
|
||||
function download_json($url) {
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
$result = Invoke-WebRequest $url -UseBasicParsing -Headers $authheader | Select-Object -ExpandProperty content | ConvertFrom-Json
|
||||
$ProgressPreference = 'Continue'
|
||||
$result
|
||||
}
|
||||
# fallback function for PowerShell 5
|
||||
function search_bucket_legacy($bucket, $query) {
|
||||
$apps = Get-ChildItem (Find-BucketDirectory $bucket) -Filter '*.json' -Recurse
|
||||
|
||||
function github_ratelimit_reached {
|
||||
$api_link = 'https://api.github.com/rate_limit'
|
||||
$ret = (download_json $api_link).rate.remaining -eq 0
|
||||
if ($ret) {
|
||||
Write-Host "GitHub API rate limit reached.
|
||||
Please try again later or configure your API token using 'scoop config gh_token <your token>'."
|
||||
$apps | ForEach-Object {
|
||||
$manifest = [System.IO.File]::ReadAllText($_.FullName) | ConvertFrom-Json -ErrorAction Continue
|
||||
$name = $_.BaseName
|
||||
|
||||
if ($name -match $query) {
|
||||
$list.Add([PSCustomObject]@{
|
||||
Name = $name
|
||||
Version = $manifest.Version
|
||||
Source = $bucket
|
||||
Binaries = ''
|
||||
})
|
||||
} else {
|
||||
$bin = bin_match $manifest $query
|
||||
if ($bin) {
|
||||
$list.Add([PSCustomObject]@{
|
||||
Name = $name
|
||||
Version = $manifest.Version
|
||||
Source = $bucket
|
||||
Binaries = $bin -join ' | '
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
$ret
|
||||
}
|
||||
|
||||
function search_remote($bucket, $query) {
|
||||
@@ -88,7 +136,7 @@ function search_remotes($query) {
|
||||
$names = $buckets | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty name
|
||||
|
||||
$results = $names | Where-Object { !(Test-Path $(Find-BucketDirectory $_)) } | ForEach-Object {
|
||||
@{ "bucket" = $_; "results" = (search_remote $_ $query) }
|
||||
@{ 'bucket' = $_; 'results' = (search_remote $_ $query) }
|
||||
} | Where-Object { $_.results }
|
||||
|
||||
if ($results.count -gt 0) {
|
||||
@@ -96,46 +144,58 @@ function search_remotes($query) {
|
||||
(add them using 'scoop bucket add <bucket name>')"
|
||||
}
|
||||
|
||||
$remote_list = @()
|
||||
$results | ForEach-Object {
|
||||
$name = $_.bucket
|
||||
$bucket = $_.bucket
|
||||
$_.results | ForEach-Object {
|
||||
$item = [ordered]@{}
|
||||
$item.Name = $_
|
||||
$item.Source = $name
|
||||
$list += [PSCustomObject]$item
|
||||
$item.Source = $bucket
|
||||
$remote_list += [PSCustomObject]$item
|
||||
}
|
||||
}
|
||||
|
||||
$list
|
||||
$remote_list
|
||||
}
|
||||
|
||||
Get-LocalBucket | ForEach-Object {
|
||||
$res = search_bucket $_ $query
|
||||
$local_results = $local_results -or $res
|
||||
if ($res) {
|
||||
$name = "$_"
|
||||
if (get_config USE_SQLITE_CACHE) {
|
||||
. "$PSScriptRoot\..\lib\database.ps1"
|
||||
Select-ScoopDBItem $query -From @('name', 'binary', 'shortcut') |
|
||||
Select-Object -Property name, version, bucket, binary |
|
||||
ForEach-Object {
|
||||
$list.Add([PSCustomObject]@{
|
||||
Name = $_.name
|
||||
Version = $_.version
|
||||
Source = $_.bucket
|
||||
Binaries = $_.binary
|
||||
})
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
$query = New-Object Regex $query, 'IgnoreCase'
|
||||
} catch {
|
||||
abort "Invalid regular expression: $($_.Exception.InnerException.Message)"
|
||||
}
|
||||
|
||||
$res | ForEach-Object {
|
||||
$item = [ordered]@{}
|
||||
$item.Name = $_.name
|
||||
$item.Version = $_.version
|
||||
$item.Source = $name
|
||||
$item.Binaries = ""
|
||||
if ($_.bin) { $item.Binaries = $_.bin -join ' | ' }
|
||||
$list += [PSCustomObject]$item
|
||||
$jsonTextAvailable = [System.AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { [System.IO.Path]::GetFileNameWithoutExtension($_.Location) -eq 'System.Text.Json' }
|
||||
|
||||
Get-LocalBucket | ForEach-Object {
|
||||
if ($jsonTextAvailable) {
|
||||
search_bucket $_ $query
|
||||
} else {
|
||||
search_bucket_legacy $_ $query
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($list.Length -gt 0) {
|
||||
Write-Host "Results from local buckets..."
|
||||
if ($list.Count -gt 0) {
|
||||
Write-Host 'Results from local buckets...'
|
||||
$list
|
||||
}
|
||||
|
||||
if (!$local_results -and !(github_ratelimit_reached)) {
|
||||
if ($list.Count -eq 0 -and !(github_ratelimit_reached)) {
|
||||
$remote_results = search_remotes $query
|
||||
if (!$remote_results) {
|
||||
warn "No matches found."
|
||||
warn 'No matches found.'
|
||||
exit 1
|
||||
}
|
||||
$remote_results
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
# Usage: scoop shim <subcommand> [<shim_names>] [<command_path> [<args>...]] [-g(lobal)]
|
||||
# Usage: scoop shim <subcommand> [<shim_name>...] [options] [other_args]
|
||||
# Summary: Manipulate Scoop shims
|
||||
# Help: Manipulate Scoop shims: add, rm, list, info, alter, etc.
|
||||
# Help: Available subcommands: add, rm, list, info, alter.
|
||||
#
|
||||
# To add a custom shim, use the 'add' subcommand:
|
||||
#
|
||||
# scoop shim add <shim_name> <command_path> [<args>...]
|
||||
#
|
||||
# To remove a shim, use the 'rm' subcommand (CAUTION: this could remove shims added by an app manifest):
|
||||
# To remove shims, use the 'rm' subcommand: (CAUTION: this could remove shims added by an app manifest)
|
||||
#
|
||||
# scoop shim rm <shim_names>
|
||||
# scoop shim rm <shim_name> [<shim_name>...]
|
||||
#
|
||||
# To list all shims or matching shims, use the 'list' subcommand:
|
||||
#
|
||||
# scoop shim list [<shim_names>]
|
||||
# scoop shim list [<regex_pattern>...]
|
||||
#
|
||||
# To show a shim's information, use the 'info' subcommand:
|
||||
#
|
||||
@@ -23,58 +23,39 @@
|
||||
# scoop shim alter <shim_name>
|
||||
#
|
||||
# Options:
|
||||
# -g(lobal) Add/Remove/Info/Alter global shim(s)
|
||||
# (NOTICE: USING SINGLE DASH)
|
||||
# (HINT: To pass arguments like '-g' or '-global' to the shim, use quotes)
|
||||
# -g, --global Manipulate global shim(s)
|
||||
#
|
||||
# HINT: The FIRST double-hyphen '--', if any, will be treated as the POSIX-style command option terminator
|
||||
# and will NOT be included in arguments, so if you want to pass arguments like '-g' or '--global' to
|
||||
# the shim, put them after a '--'. Note that in PowerShell, you must use a QUOTED '--', e.g.,
|
||||
#
|
||||
# scoop shim add myapp 'D:\path\myapp.exe' '--' myapp_args --global
|
||||
|
||||
param($SubCommand, $ShimName, [Switch]$global)
|
||||
param($SubCommand)
|
||||
|
||||
. "$PSScriptRoot\..\lib\getopt.ps1"
|
||||
. "$PSScriptRoot\..\lib\install.ps1" # for rm_shim
|
||||
. "$PSScriptRoot\..\lib\system.ps1" # 'Add-Path' (indirectly)
|
||||
|
||||
if ($SubCommand -notin @('add', 'rm', 'list', 'info', 'alter')) {
|
||||
'ERROR: <subcommand> must be one of: add, rm, list, info, alter'
|
||||
my_usage
|
||||
exit 1
|
||||
}
|
||||
|
||||
if ($SubCommand -ne 'list' -and !$ShimName) {
|
||||
"ERROR: <shim_name> must be specified for subcommand '$SubCommand'"
|
||||
my_usage
|
||||
exit 1
|
||||
}
|
||||
|
||||
if ($Args) {
|
||||
switch ($SubCommand) {
|
||||
'add' {
|
||||
if ($Args[0] -like '-*') {
|
||||
"ERROR: <command_path> must be specified for subcommand 'add'"
|
||||
my_usage
|
||||
exit 1
|
||||
} else {
|
||||
if (($Args -join ' ') -match "^'(.*?)'\s*(.*?)$") {
|
||||
$commandPath = $Matches[1]
|
||||
$commandArgs = $Matches[2]
|
||||
} else {
|
||||
$commandPath = $Args[0]
|
||||
if ($Args.Length -gt 1) {
|
||||
$commandArgs = $Args[1..($Args.Length - 1)]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
'rm' {
|
||||
$ShimName = @($ShimName) + $Args
|
||||
}
|
||||
'list' {
|
||||
$ShimName = (@($ShimName) + $Args) -join '|'
|
||||
}
|
||||
default {
|
||||
# For 'info' and 'alter'
|
||||
"ERROR: Option $Args not recognized."
|
||||
my_usage
|
||||
exit 1
|
||||
}
|
||||
if (!$SubCommand) {
|
||||
error '<subcommand> missing'
|
||||
} else {
|
||||
error "'$SubCommand' is not one of available subcommands: add, rm, list, info, alter"
|
||||
}
|
||||
my_usage
|
||||
exit 1
|
||||
}
|
||||
|
||||
$opt, $other, $err = getopt $Args 'g' 'global'
|
||||
if ($err) { "scoop shim: $err"; exit 1 }
|
||||
|
||||
$global = $opt.g -or $opt.global
|
||||
|
||||
if ($SubCommand -ne 'list' -and $other.Length -eq 0) {
|
||||
error "<shim_name> must be specified for subcommand '$SubCommand'"
|
||||
my_usage
|
||||
exit 1
|
||||
}
|
||||
|
||||
if (-not (Get-FormatData ScoopShims)) {
|
||||
@@ -102,7 +83,7 @@ function Get-ShimInfo($ShimPath) {
|
||||
function Get-ShimPath($ShimName, $Global) {
|
||||
'.shim', '.ps1' | ForEach-Object {
|
||||
$shimPath = Join-Path (shimdir $Global) "$ShimName$_"
|
||||
if (Test-Path $shimPath) {
|
||||
if (Test-Path -LiteralPath $shimPath) {
|
||||
return $shimPath
|
||||
}
|
||||
}
|
||||
@@ -110,6 +91,16 @@ function Get-ShimPath($ShimName, $Global) {
|
||||
|
||||
switch ($SubCommand) {
|
||||
'add' {
|
||||
if ($other.Length -lt 2 -or $other[1] -eq '') {
|
||||
error "<command_path> must be specified for subcommand 'add'"
|
||||
my_usage
|
||||
exit 1
|
||||
}
|
||||
$shimName = $other[0]
|
||||
$commandPath = $other[1]
|
||||
if ($other.Length -gt 2) {
|
||||
$commandArgs = $other[2..($other.Length - 1)]
|
||||
}
|
||||
if ($commandPath -notmatch '[\\/]') {
|
||||
$shortPath = $commandPath
|
||||
$commandPath = Get-ShimTarget (Get-ShimPath $shortPath $global)
|
||||
@@ -126,12 +117,14 @@ switch ($SubCommand) {
|
||||
Write-Host '...'
|
||||
shim $commandPath $global $shimName $commandArgs
|
||||
} else {
|
||||
abort "ERROR: '$($Args[0])' does not exist" 3
|
||||
Write-Host "ERROR: Command path does not exist: " -ForegroundColor Red -NoNewline
|
||||
Write-Host $($other[1]) -ForegroundColor Cyan
|
||||
exit 3
|
||||
}
|
||||
}
|
||||
'rm' {
|
||||
$failed = @()
|
||||
$ShimName | ForEach-Object {
|
||||
$other | ForEach-Object {
|
||||
if (Get-ShimPath $_ $global) {
|
||||
rm_shim $_ (shimdir $global)
|
||||
} else {
|
||||
@@ -139,61 +132,82 @@ switch ($SubCommand) {
|
||||
}
|
||||
}
|
||||
if ($failed) {
|
||||
Write-Host 'Shims not found: ' -NoNewline
|
||||
Write-Host $failed -ForegroundColor Cyan
|
||||
$failed | ForEach-Object {
|
||||
Write-Host "ERROR: $(if ($global) { 'Global' } else {'Local' }) shim not found: " -ForegroundColor Red -NoNewline
|
||||
Write-Host $_ -ForegroundColor Cyan
|
||||
}
|
||||
exit 3
|
||||
}
|
||||
}
|
||||
'list' {
|
||||
$shims = Get-ChildItem -Path $localShimDir -Recurse -Include '*.shim', '*.ps1' |
|
||||
Where-Object { !$ShimName -or ($_.BaseName -match $ShimName) } |
|
||||
Select-Object -ExpandProperty FullName
|
||||
$other = @($other) -ne '*'
|
||||
# Validate all given patterns before matching.
|
||||
$other | ForEach-Object {
|
||||
try {
|
||||
$pattern = $_
|
||||
[void][Regex]::New($pattern)
|
||||
} catch {
|
||||
Write-Host "ERROR: Invalid pattern: " -ForegroundColor Red -NoNewline
|
||||
Write-Host $pattern -ForegroundColor Magenta
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
$pattern = $other -join '|'
|
||||
$shims = @()
|
||||
if (!$global) {
|
||||
$shims += Get-ChildItem -Path $localShimDir -Recurse -Include '*.shim', '*.ps1' |
|
||||
Where-Object { !$pattern -or ($_.BaseName -match $pattern) } |
|
||||
Select-Object -ExpandProperty FullName
|
||||
}
|
||||
if (Test-Path $globalShimDir) {
|
||||
$shims += Get-ChildItem -Path $globalShimDir -Recurse -Include '*.shim', '*.ps1' |
|
||||
Where-Object { !$ShimName -or ($_.BaseName -match $ShimName) } |
|
||||
Where-Object { !$pattern -or ($_.BaseName -match $pattern) } |
|
||||
Select-Object -ExpandProperty FullName
|
||||
}
|
||||
$shims.ForEach({ Get-ShimInfo $_ }) | Add-Member -TypeName 'ScoopShims' -PassThru
|
||||
}
|
||||
'info' {
|
||||
$shimPath = Get-ShimPath $ShimName $global
|
||||
$shimName = $other[0]
|
||||
$shimPath = Get-ShimPath $shimName $global
|
||||
if ($shimPath) {
|
||||
Get-ShimInfo $shimPath
|
||||
} else {
|
||||
Write-Host "$(if ($global) { 'Global' } else { 'Local' }) shim not found: " -NoNewline
|
||||
Write-Host $ShimName -ForegroundColor Cyan
|
||||
if (Get-ShimPath $ShimName (!$global)) {
|
||||
Write-Host "ERROR: $(if ($global) { 'Global' } else { 'Local' }) shim not found: " -ForegroundColor Red -NoNewline
|
||||
Write-Host $shimName -ForegroundColor Cyan
|
||||
if (Get-ShimPath $shimName (!$global)) {
|
||||
Write-Host "But a $(if ($global) { 'local' } else {'global' }) shim exists, " -NoNewline
|
||||
Write-Host "run 'scoop shim info $ShimName$(if (!$global) { ' -global' })' to show its info"
|
||||
Write-Host "run 'scoop shim info $shimName$(if (!$global) { ' --global' })' to show its info"
|
||||
exit 2
|
||||
}
|
||||
exit 3
|
||||
}
|
||||
}
|
||||
'alter' {
|
||||
$shimPath = Get-ShimPath $ShimName $global
|
||||
$shimName = $other[0]
|
||||
$shimPath = Get-ShimPath $shimName $global
|
||||
if ($shimPath) {
|
||||
$shimInfo = Get-ShimInfo $shimPath
|
||||
if ($null -eq $shimInfo.Alternatives) {
|
||||
Write-Host 'No alternatives of ' -NoNewline
|
||||
Write-Host $ShimName -ForegroundColor Cyan -NoNewline
|
||||
Write-Host ' found.'
|
||||
Write-Host 'ERROR: No alternatives of ' -ForegroundColor Red -NoNewline
|
||||
Write-Host $shimName -ForegroundColor Cyan -NoNewline
|
||||
Write-Host ' found.' -ForegroundColor Red
|
||||
exit 2
|
||||
}
|
||||
$shimInfo.Alternatives = $shimInfo.Alternatives.Split(' ')
|
||||
[System.Management.Automation.Host.ChoiceDescription[]]$altApps = 1..$shimInfo.Alternatives.Length | ForEach-Object {
|
||||
New-Object System.Management.Automation.Host.ChoiceDescription "&$($_)`b$($shimInfo.Alternatives[$_ - 1])", "Sets '$ShimName' shim from $($shimInfo.Alternatives[$_ - 1])."
|
||||
New-Object System.Management.Automation.Host.ChoiceDescription "&$($_)`b$($shimInfo.Alternatives[$_ - 1])", "Sets '$shimName' shim from $($shimInfo.Alternatives[$_ - 1])."
|
||||
}
|
||||
$selected = $Host.UI.PromptForChoice("Alternatives of '$ShimName' command", "Please choose one that provides '$ShimName' as default:", $altApps, 0)
|
||||
$selected = $Host.UI.PromptForChoice("Alternatives of '$shimName' command", "Please choose one that provides '$shimName' as default:", $altApps, 0)
|
||||
if ($selected -eq 0) {
|
||||
Write-Host $ShimName -ForegroundColor Cyan -NoNewline
|
||||
Write-Host 'INFO: ' -ForegroundColor Blue -NoNewline
|
||||
Write-Host $shimName -ForegroundColor Cyan -NoNewline
|
||||
Write-Host ' is already from ' -NoNewline
|
||||
Write-Host $shimInfo.Source -ForegroundColor DarkYellow -NoNewline
|
||||
Write-Host ', nothing changed.'
|
||||
} else {
|
||||
$newApp = $shimInfo.Alternatives[$selected]
|
||||
Write-Host 'Use ' -NoNewline
|
||||
Write-Host $ShimName -ForegroundColor Cyan -NoNewline
|
||||
Write-Host $shimName -ForegroundColor Cyan -NoNewline
|
||||
Write-Host ' from ' -NoNewline
|
||||
Write-Host $newApp -ForegroundColor DarkYellow -NoNewline
|
||||
Write-Host ' as default...' -NoNewline
|
||||
@@ -211,11 +225,11 @@ switch ($SubCommand) {
|
||||
Write-Host 'done.'
|
||||
}
|
||||
} else {
|
||||
Write-Host "$(if ($global) { 'Global' } else { 'Local' }) shim not found: " -NoNewline
|
||||
Write-Host $ShimName -ForegroundColor Cyan
|
||||
if (Get-ShimPath $ShimName (!$global)) {
|
||||
Write-Host "ERROR: $(if ($global) { 'Global' } else { 'Local' }) shim not found: " -ForegroundColor Red -NoNewline
|
||||
Write-Host $shimName -ForegroundColor Cyan
|
||||
if (Get-ShimPath $shimName (!$global)) {
|
||||
Write-Host "But a $(if ($global) { 'local' } else {'global' }) shim exists, " -NoNewline
|
||||
Write-Host "run 'scoop shim alter $ShimName$(if (!$global) { ' -global' })' to alternate its source"
|
||||
Write-Host "run 'scoop shim alter $shimName$(if (!$global) { ' --global' })' to alternate its source"
|
||||
exit 2
|
||||
}
|
||||
exit 3
|
||||
|
||||
@@ -6,9 +6,10 @@
|
||||
|
||||
. "$PSScriptRoot\..\lib\manifest.ps1" # 'manifest' 'parse_json' "install_info"
|
||||
. "$PSScriptRoot\..\lib\versions.ps1" # 'Select-CurrentVersion'
|
||||
. "$PSScriptRoot\..\lib\download.ps1" # 'Get-UserAgent'
|
||||
|
||||
# check if scoop needs updating
|
||||
$currentdir = fullpath $(versiondir 'scoop' 'current')
|
||||
$currentdir = versiondir 'scoop' 'current'
|
||||
$needs_update = $false
|
||||
$bucket_needs_update = $false
|
||||
$script:network_failure = $false
|
||||
@@ -21,10 +22,10 @@ if (!(Get-FormatData ScoopStatus)) {
|
||||
|
||||
function Test-UpdateStatus($repopath) {
|
||||
if (Test-Path "$repopath\.git") {
|
||||
git_cmd -C "`"$repopath`"" fetch -q origin
|
||||
Invoke-Git -Path $repopath -ArgumentList @('fetch', '-q', 'origin')
|
||||
$script:network_failure = 128 -eq $LASTEXITCODE
|
||||
$branch = git -C $repopath branch --show-current
|
||||
$commits = git -C $repopath log "HEAD..origin/$branch" --oneline
|
||||
$branch = Invoke-Git -Path $repopath -ArgumentList @('branch', '--show-current')
|
||||
$commits = Invoke-Git -Path $repopath -ArgumentList @('log', "HEAD..origin/$branch", '--oneline')
|
||||
if ($commits) { return $true }
|
||||
else { return $false }
|
||||
} else {
|
||||
@@ -58,7 +59,7 @@ $true, $false | ForEach-Object { # local and global apps
|
||||
Get-ChildItem $dir | Where-Object name -NE 'scoop' | ForEach-Object {
|
||||
$app = $_.name
|
||||
$status = app_status $app $global
|
||||
if (!$status.outdated -and !$status.failed -and !$status.removed -and !$status.missing_deps) { return }
|
||||
if (!$status.outdated -and !$status.failed -and !$status.deprecated -and !$status.removed -and !$status.missing_deps) { return }
|
||||
|
||||
$item = [ordered]@{}
|
||||
$item.Name = $app
|
||||
@@ -66,9 +67,10 @@ $true, $false | ForEach-Object { # local and global apps
|
||||
$item.'Latest Version' = if ($status.outdated) { $status.latest_version } else { "" }
|
||||
$item.'Missing Dependencies' = $status.missing_deps -Split ' ' -Join ' | '
|
||||
$info = @()
|
||||
if ($status.failed) { $info += 'Install failed' }
|
||||
if ($status.hold) { $info += 'Held package' }
|
||||
if ($status.removed) { $info += 'Manifest removed' }
|
||||
if ($status.failed) { $info += 'Install failed' }
|
||||
if ($status.hold) { $info += 'Held package' }
|
||||
if ($status.deprecated) { $info += 'Deprecated' }
|
||||
if ($status.removed) { $info += 'Manifest removed' }
|
||||
$item.Info = $info -join ', '
|
||||
$list += [PSCustomObject]$item
|
||||
}
|
||||
|
||||
@@ -32,6 +32,11 @@ if ($global -and !(is_admin)) {
|
||||
$apps | ForEach-Object {
|
||||
$app = $_
|
||||
|
||||
if ($app -eq 'scoop') {
|
||||
set_config HOLD_UPDATE_UNTIL $null | Out-Null
|
||||
success "$app is no longer held and can be updated again."
|
||||
return
|
||||
}
|
||||
if (!(installed $app $global)) {
|
||||
if ($global) {
|
||||
error "'$app' is not installed globally."
|
||||
@@ -41,15 +46,23 @@ $apps | ForEach-Object {
|
||||
return
|
||||
}
|
||||
|
||||
if (get_config NO_JUNCTIONS) {
|
||||
if (get_config NO_JUNCTION){
|
||||
$version = Select-CurrentVersion -App $app -Global:$global
|
||||
} else {
|
||||
$version = 'current'
|
||||
}
|
||||
$dir = versiondir $app $version $global
|
||||
$json = install_info $app $version $global
|
||||
if (!$json) {
|
||||
error "Failed to unhold '$app'"
|
||||
continue
|
||||
}
|
||||
$install = @{}
|
||||
$json | Get-Member -MemberType Properties | ForEach-Object { $install.Add($_.Name, $json.($_.Name)) }
|
||||
if (!$install.hold) {
|
||||
info "'$app' is not held."
|
||||
continue
|
||||
}
|
||||
$install.hold = $null
|
||||
save_install_info $install $dir
|
||||
success "$app is no longer held and can be updated again."
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
. "$PSScriptRoot\..\lib\getopt.ps1"
|
||||
. "$PSScriptRoot\..\lib\manifest.ps1" # 'Get-Manifest' 'Select-CurrentVersion' (indirectly)
|
||||
. "$PSScriptRoot\..\lib\system.ps1"
|
||||
. "$PSScriptRoot\..\lib\install.ps1"
|
||||
. "$PSScriptRoot\..\lib\shortcuts.ps1"
|
||||
. "$PSScriptRoot\..\lib\psmodules.ps1"
|
||||
@@ -73,7 +74,7 @@ if (!$apps) { exit 0 }
|
||||
continue
|
||||
}
|
||||
|
||||
run_uninstaller $manifest $architecture $dir
|
||||
Invoke-Installer -Path $dir -Manifest $manifest -ProcessorArchitecture $architecture -Global $global -Uninstall
|
||||
rm_shims $app $manifest $global $architecture
|
||||
rm_startmenu_shortcuts $manifest $global $architecture
|
||||
|
||||
|
||||
@@ -6,16 +6,17 @@
|
||||
# You can use '*' in place of <app> to update all apps.
|
||||
#
|
||||
# Options:
|
||||
# -f, --force Force update even when there isn't a newer version
|
||||
# -g, --global Update a globally installed app
|
||||
# -i, --independent Don't install dependencies automatically
|
||||
# -k, --no-cache Don't use the download cache
|
||||
# -s, --skip Skip hash validation (use with caution!)
|
||||
# -q, --quiet Hide extraneous messages
|
||||
# -a, --all Update all apps (alternative to '*')
|
||||
# -f, --force Force update even when there isn't a newer version
|
||||
# -g, --global Update a globally installed app
|
||||
# -i, --independent Don't install dependencies automatically
|
||||
# -k, --no-cache Don't use the download cache
|
||||
# -s, --skip-hash-check Skip hash validation (use with caution!)
|
||||
# -q, --quiet Hide extraneous messages
|
||||
# -a, --all Update all apps (alternative to '*')
|
||||
|
||||
. "$PSScriptRoot\..\lib\getopt.ps1"
|
||||
. "$PSScriptRoot\..\lib\json.ps1" # 'save_install_info' in 'manifest.ps1' (indirectly)
|
||||
. "$PSScriptRoot\..\lib\system.ps1"
|
||||
. "$PSScriptRoot\..\lib\shortcuts.ps1"
|
||||
. "$PSScriptRoot\..\lib\psmodules.ps1"
|
||||
. "$PSScriptRoot\..\lib\decompress.ps1"
|
||||
@@ -23,12 +24,16 @@
|
||||
. "$PSScriptRoot\..\lib\versions.ps1"
|
||||
. "$PSScriptRoot\..\lib\depends.ps1"
|
||||
. "$PSScriptRoot\..\lib\install.ps1"
|
||||
. "$PSScriptRoot\..\lib\download.ps1"
|
||||
if (get_config USE_SQLITE_CACHE) {
|
||||
. "$PSScriptRoot\..\lib\database.ps1"
|
||||
}
|
||||
|
||||
$opt, $apps, $err = getopt $args 'gfiksqa' 'global', 'force', 'independent', 'no-cache', 'skip', 'quiet', 'all'
|
||||
$opt, $apps, $err = getopt $args 'gfiksqa' 'global', 'force', 'independent', 'no-cache', 'skip-hash-check', 'quiet', 'all'
|
||||
if ($err) { "scoop update: $err"; exit 1 }
|
||||
$global = $opt.g -or $opt.global
|
||||
$force = $opt.f -or $opt.force
|
||||
$check_hash = !($opt.s -or $opt.skip)
|
||||
$check_hash = !($opt.s -or $opt.'skip-hash-check')
|
||||
$use_cache = !($opt.k -or $opt.'no-cache')
|
||||
$quiet = $opt.q -or $opt.quiet
|
||||
$independent = $opt.i -or $opt.independent
|
||||
@@ -37,40 +42,46 @@ $all = $opt.a -or $opt.all
|
||||
# load config
|
||||
$configRepo = get_config SCOOP_REPO
|
||||
if (!$configRepo) {
|
||||
$configRepo = "https://github.com/ScoopInstaller/Scoop"
|
||||
$configRepo = 'https://github.com/ScoopInstaller/Scoop'
|
||||
set_config SCOOP_REPO $configRepo | Out-Null
|
||||
}
|
||||
|
||||
# Find current update channel from config
|
||||
$configBranch = get_config SCOOP_BRANCH
|
||||
if (!$configBranch) {
|
||||
$configBranch = "master"
|
||||
$configBranch = 'master'
|
||||
set_config SCOOP_BRANCH $configBranch | Out-Null
|
||||
}
|
||||
|
||||
if(($PSVersionTable.PSVersion.Major) -lt 5) {
|
||||
if (($PSVersionTable.PSVersion.Major) -lt 5) {
|
||||
# check powershell version
|
||||
Write-Output "PowerShell 5 or later is required to run Scoop."
|
||||
Write-Output "Upgrade PowerShell: https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell-core-on-windows"
|
||||
Write-Output 'PowerShell 5 or later is required to run Scoop.'
|
||||
Write-Output 'Upgrade PowerShell: https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell-core-on-windows'
|
||||
break
|
||||
}
|
||||
$show_update_log = get_config SHOW_UPDATE_LOG $true
|
||||
|
||||
function Sync-Scoop {
|
||||
[CmdletBinding()]
|
||||
Param (
|
||||
[Switch]$Log
|
||||
)
|
||||
# Test if Scoop Core is hold
|
||||
if (Test-ScoopCoreOnHold) {
|
||||
return
|
||||
}
|
||||
|
||||
function update_scoop() {
|
||||
# check for git
|
||||
if(!(Test-CommandAvailable git)) { abort "Scoop uses Git to update itself. Run 'scoop install git' and try again." }
|
||||
if (!(Test-GitAvailable)) { abort "Scoop uses Git to update itself. Run 'scoop install git' and try again." }
|
||||
|
||||
Write-Host "Updating Scoop..."
|
||||
$last_update = $(last_scoop_update)
|
||||
if ($null -eq $last_update) {$last_update = [System.DateTime]::Now}
|
||||
$last_update = $last_update.ToString('s')
|
||||
$show_update_log = get_config 'show_update_log' $true
|
||||
$currentdir = fullpath $(versiondir 'scoop' 'current')
|
||||
Write-Host 'Updating Scoop...'
|
||||
$currentdir = versiondir 'scoop' 'current'
|
||||
if (!(Test-Path "$currentdir\.git")) {
|
||||
$newdir = "$currentdir\..\new"
|
||||
$olddir = "$currentdir\..\old"
|
||||
|
||||
# get git scoop
|
||||
git_cmd clone -q $configRepo --branch $configBranch --single-branch "`"$newdir`""
|
||||
Invoke-Git -ArgumentList @('clone', '-q', $configRepo, '--branch', $configBranch, '--single-branch', $newdir)
|
||||
|
||||
# check if scoop was successful downloaded
|
||||
if (!(Test-Path "$newdir\bin\scoop.ps1")) {
|
||||
@@ -83,7 +94,7 @@ function update_scoop() {
|
||||
Rename-Item $newdir 'current' -ErrorAction Stop
|
||||
} catch {
|
||||
Write-Warning $_
|
||||
abort "Scoop update failed. Folder in use. Paste $newdir into $currentdir."
|
||||
abort "Scoop update failed. Folder in use. Please rename folders $currentdir to ``old`` and $newdir to ``current``."
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -91,35 +102,46 @@ function update_scoop() {
|
||||
Remove-Item "$currentdir\..\old" -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
$previousCommit = git -C "$currentdir" rev-parse HEAD
|
||||
$currentRepo = git -C "$currentdir" config remote.origin.url
|
||||
$currentBranch = git -C "$currentdir" branch
|
||||
$previousCommit = Invoke-Git -Path $currentdir -ArgumentList @('rev-parse', 'HEAD')
|
||||
$currentRepo = Invoke-Git -Path $currentdir -ArgumentList @('config', 'remote.origin.url')
|
||||
$currentBranch = Invoke-Git -Path $currentdir -ArgumentList @('branch')
|
||||
|
||||
$isRepoChanged = !($currentRepo -match $configRepo)
|
||||
$isBranchChanged = !($currentBranch -match "\*\s+$configBranch")
|
||||
|
||||
# Stash uncommitted changes
|
||||
if (Invoke-Git -Path $currentdir -ArgumentList @('diff', 'HEAD', '--name-only')) {
|
||||
if (get_config AUTOSTASH_ON_CONFLICT) {
|
||||
warn 'Uncommitted changes detected. Stashing...'
|
||||
Invoke-Git -Path $currentdir -ArgumentList @('stash', 'push', '-m', "WIP at $([System.DateTime]::Now.ToString('o'))", '-u', '-q')
|
||||
} else {
|
||||
warn 'Uncommitted changes detected. Update aborted.'
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
# Change remote url if the repo is changed
|
||||
if ($isRepoChanged) {
|
||||
git -C "$currentdir" config remote.origin.url "$configRepo"
|
||||
Invoke-Git -Path $currentdir -ArgumentList @('config', 'remote.origin.url', $configRepo)
|
||||
}
|
||||
|
||||
# Fetch and reset local repo if the repo or the branch is changed
|
||||
if ($isRepoChanged -or $isBranchChanged) {
|
||||
# Reset git fetch refs, so that it can fetch all branches (GH-3368)
|
||||
git -C "$currentdir" config remote.origin.fetch '+refs/heads/*:refs/remotes/origin/*'
|
||||
Invoke-Git -Path $currentdir -ArgumentList @('config', 'remote.origin.fetch', '+refs/heads/*:refs/remotes/origin/*')
|
||||
# fetch remote branch
|
||||
git_cmd -C "`"$currentdir`"" fetch --force origin "refs/heads/`"$configBranch`":refs/remotes/origin/$configBranch" -q
|
||||
Invoke-Git -Path $currentdir -ArgumentList @('fetch', '--force', 'origin', "refs/heads/$configBranch`:refs/remotes/origin/$configBranch", '-q')
|
||||
# checkout and track the branch
|
||||
git_cmd -C "`"$currentdir`"" checkout -B $configBranch -t origin/$configBranch -q
|
||||
Invoke-Git -Path $currentdir -ArgumentList @('checkout', '-B', $configBranch, '-t', "origin/$configBranch", '-q')
|
||||
# reset branch HEAD
|
||||
git -C "$currentdir" reset --hard origin/$configBranch -q
|
||||
Invoke-Git -Path $currentdir -ArgumentList @('reset', '--hard', "origin/$configBranch", '-q')
|
||||
} else {
|
||||
git_cmd -C "`"$currentdir`"" pull -q
|
||||
Invoke-Git -Path $currentdir -ArgumentList @('pull', '-q')
|
||||
}
|
||||
|
||||
$res = $lastexitcode
|
||||
if ($show_update_log) {
|
||||
git -C "$currentdir" --no-pager log --no-decorate --grep='^(chore)' --invert-grep --format='tformat: * %C(yellow)%h%Creset %<|(72,trunc)%s %C(cyan)%cr%Creset' "$previousCommit..HEAD"
|
||||
if ($Log) {
|
||||
Invoke-GitLog -Path $currentdir -CommitHash $previousCommit
|
||||
}
|
||||
|
||||
if ($res -ne 0) {
|
||||
@@ -127,47 +149,113 @@ function update_scoop() {
|
||||
}
|
||||
}
|
||||
|
||||
# This should have been deprecated after 2019-05-12
|
||||
# if ((Get-LocalBucket) -notcontains 'main') {
|
||||
# info "The main bucket of Scoop has been separated to 'https://github.com/ScoopInstaller/Main'"
|
||||
# info "Adding main bucket..."
|
||||
# add_bucket 'main'
|
||||
# }
|
||||
|
||||
shim "$currentdir\bin\scoop.ps1" $false
|
||||
}
|
||||
|
||||
foreach ($bucket in Get-LocalBucket) {
|
||||
Write-Host "Updating '$bucket' bucket..."
|
||||
function Sync-Bucket {
|
||||
Param (
|
||||
[Switch]$Log
|
||||
)
|
||||
Write-Host 'Updating Buckets...'
|
||||
|
||||
$bucketLoc = Find-BucketDirectory $bucket -Root
|
||||
|
||||
if (!(Test-Path (Join-Path $bucketLoc '.git'))) {
|
||||
if ($bucket -eq 'main') {
|
||||
# Make sure main bucket, which was downloaded as zip, will be properly "converted" into git
|
||||
Write-Host " Converting 'main' bucket to git repo..."
|
||||
$status = rm_bucket 'main'
|
||||
if ($status -ne 0) {
|
||||
abort "Failed to remove local 'main' bucket."
|
||||
}
|
||||
$status = add_bucket 'main' (known_bucket_repo 'main')
|
||||
if ($status -ne 0) {
|
||||
abort "Failed to add remote 'main' bucket."
|
||||
}
|
||||
} else {
|
||||
Write-Host "'$bucket' is not a git repository. Skipped."
|
||||
}
|
||||
continue
|
||||
if (!(Test-Path (Join-Path (Find-BucketDirectory 'main' -Root) '.git'))) {
|
||||
info "Converting 'main' bucket to git repo..."
|
||||
$status = rm_bucket 'main'
|
||||
if ($status -ne 0) {
|
||||
abort "Failed to remove local 'main' bucket."
|
||||
}
|
||||
|
||||
$previousCommit = git -C "$bucketLoc" rev-parse HEAD
|
||||
git_cmd -C "`"$bucketLoc`"" pull -q
|
||||
if ($show_update_log) {
|
||||
git -C "$bucketLoc" --no-pager log --no-decorate --grep='^(chore)' --invert-grep --format='tformat: * %C(yellow)%h%Creset %<|(72,trunc)%s %C(cyan)%cr%Creset' "$previousCommit..HEAD"
|
||||
$status = add_bucket 'main' (known_bucket_repo 'main')
|
||||
if ($status -ne 0) {
|
||||
abort "Failed to add remote 'main' bucket."
|
||||
}
|
||||
}
|
||||
|
||||
set_config lastupdate ([System.DateTime]::Now.ToString('o')) | Out-Null
|
||||
success 'Scoop was updated successfully!'
|
||||
|
||||
$buckets = Get-LocalBucket | ForEach-Object {
|
||||
$path = Find-BucketDirectory $_ -Root
|
||||
return @{
|
||||
name = $_
|
||||
valid = Test-Path (Join-Path $path '.git')
|
||||
path = $path
|
||||
}
|
||||
}
|
||||
|
||||
$buckets | Where-Object { !$_.valid } | ForEach-Object { Write-Host "'$($_.name)' is not a git repository. Skipped." }
|
||||
|
||||
$updatedFiles = [System.Collections.ArrayList]::Synchronized([System.Collections.ArrayList]::new())
|
||||
$removedFiles = [System.Collections.ArrayList]::Synchronized([System.Collections.ArrayList]::new())
|
||||
if ($PSVersionTable.PSVersion.Major -ge 7) {
|
||||
# Parallel parameter is available since PowerShell 7
|
||||
$buckets | Where-Object { $_.valid } | ForEach-Object -ThrottleLimit 5 -Parallel {
|
||||
. "$using:PSScriptRoot\..\lib\core.ps1"
|
||||
. "$using:PSScriptRoot\..\lib\buckets.ps1"
|
||||
|
||||
$name = $_.name
|
||||
$bucketLoc = $_.path
|
||||
$innerBucketLoc = Find-BucketDirectory $name
|
||||
|
||||
$previousCommit = Invoke-Git -Path $bucketLoc -ArgumentList @('rev-parse', 'HEAD')
|
||||
Invoke-Git -Path $bucketLoc -ArgumentList @('pull', '-q')
|
||||
if ($using:Log) {
|
||||
Invoke-GitLog -Path $bucketLoc -Name $name -CommitHash $previousCommit
|
||||
}
|
||||
if (get_config USE_SQLITE_CACHE) {
|
||||
Invoke-Git -Path $bucketLoc -ArgumentList @('diff', '--name-status', $previousCommit) | ForEach-Object {
|
||||
$status, $file = $_ -split '\s+', 2
|
||||
$filePath = Join-Path $bucketLoc $file
|
||||
if ($filePath -match "^$([regex]::Escape($innerBucketLoc)).*\.json$") {
|
||||
switch ($status) {
|
||||
{ $_ -in 'A', 'M', 'R' } {
|
||||
[void]($using:updatedFiles).Add($filePath)
|
||||
}
|
||||
'D' {
|
||||
[void]($using:removedFiles).Add([pscustomobject]@{
|
||||
Name = ([System.IO.FileInfo]$file).BaseName
|
||||
Bucket = $name
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$buckets | Where-Object { $_.valid } | ForEach-Object {
|
||||
$name = $_.name
|
||||
$bucketLoc = $_.path
|
||||
$innerBucketLoc = Find-BucketDirectory $name
|
||||
|
||||
$previousCommit = Invoke-Git -Path $bucketLoc -ArgumentList @('rev-parse', 'HEAD')
|
||||
Invoke-Git -Path $bucketLoc -ArgumentList @('pull', '-q')
|
||||
if ($Log) {
|
||||
Invoke-GitLog -Path $bucketLoc -Name $name -CommitHash $previousCommit
|
||||
}
|
||||
if (get_config USE_SQLITE_CACHE) {
|
||||
Invoke-Git -Path $bucketLoc -ArgumentList @('diff', '--name-status', $previousCommit) | ForEach-Object {
|
||||
$status, $file = $_ -split '\s+', 2
|
||||
$filePath = Join-Path $bucketLoc $file
|
||||
if ($filePath -match "^$([regex]::Escape($innerBucketLoc)).*\.json$") {
|
||||
switch ($status) {
|
||||
{ $_ -in 'A', 'M', 'R' } {
|
||||
[void]($updatedFiles).Add($filePath)
|
||||
}
|
||||
'D' {
|
||||
[void]($removedFiles).Add([pscustomobject]@{
|
||||
Name = ([System.IO.FileInfo]$file).BaseName
|
||||
Bucket = $name
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((get_config USE_SQLITE_CACHE) -and ($updatedFiles.Count -gt 0 -or $removedFiles.Count -gt 0)) {
|
||||
info 'Updating cache...'
|
||||
Set-ScoopDB -Path $updatedFiles
|
||||
$removedFiles | Remove-ScoopDBItem
|
||||
}
|
||||
}
|
||||
|
||||
function update($app, $global, $quiet = $false, $independent, $suggested, $use_cache = $true, $check_hash = $true) {
|
||||
@@ -176,7 +264,7 @@ function update($app, $global, $quiet = $false, $independent, $suggested, $use_c
|
||||
$install = install_info $app $old_version $global
|
||||
|
||||
# re-use architecture, bucket and url from first install
|
||||
$architecture = ensure_architecture $install.architecture
|
||||
$architecture = Format-ArchitectureString $install.architecture
|
||||
$bucket = $install.bucket
|
||||
if ($null -eq $bucket) {
|
||||
$bucket = 'main'
|
||||
@@ -187,7 +275,7 @@ function update($app, $global, $quiet = $false, $independent, $suggested, $use_c
|
||||
$version = $manifest.version
|
||||
$is_nightly = $version -eq 'nightly'
|
||||
if ($is_nightly) {
|
||||
$version = nightly_version $(get-date) $quiet
|
||||
$version = nightly_version $quiet
|
||||
$check_hash = $false
|
||||
}
|
||||
|
||||
@@ -205,33 +293,40 @@ function update($app, $global, $quiet = $false, $independent, $suggested, $use_c
|
||||
|
||||
Write-Host "Updating '$app' ($old_version -> $version)"
|
||||
|
||||
#region Workaround for #2952
|
||||
if (test_running_process $app $global) {
|
||||
Write-Host 'Running process detected, skip updating.'
|
||||
return
|
||||
}
|
||||
#endregion Workaround for #2952
|
||||
|
||||
# region Workaround
|
||||
# Workaround for https://github.com/ScoopInstaller/Scoop/issues/2220 until install is refactored
|
||||
# Remove and replace whole region after proper fix
|
||||
Write-Host "Downloading new version"
|
||||
Write-Host 'Downloading new version'
|
||||
if (Test-Aria2Enabled) {
|
||||
dl_with_cache_aria2 $app $version $manifest $architecture $cachedir $manifest.cookie $true $check_hash
|
||||
Invoke-CachedAria2Download $app $version $manifest $architecture $cachedir $manifest.cookie $true $check_hash
|
||||
} else {
|
||||
$urls = script:url $manifest $architecture
|
||||
|
||||
foreach ($url in $urls) {
|
||||
dl_with_cache $app $version $url $null $manifest.cookie $true
|
||||
Invoke-CachedDownload $app $version $url $null $manifest.cookie $true
|
||||
|
||||
if ($check_hash) {
|
||||
$manifest_hash = hash_for_url $manifest $url $architecture
|
||||
$source = fullpath (cache_path $app $version $url)
|
||||
$source = cache_path $app $version $url
|
||||
$ok, $err = check_hash $source $manifest_hash $(show_app $app $bucket)
|
||||
|
||||
if (!$ok) {
|
||||
error $err
|
||||
if (Test-Path $source) {
|
||||
# rm cached file
|
||||
Remove-Item -force $source
|
||||
Remove-Item -Force $source
|
||||
}
|
||||
if ($url.Contains('sourceforge.net')) {
|
||||
Write-Host -f yellow 'SourceForge.net is known for causing hash validation fails. Please try again before opening a ticket.'
|
||||
}
|
||||
abort $(new_issue_msg $app $bucket "hash check failed")
|
||||
abort $(new_issue_msg $app $bucket 'hash check failed')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -245,22 +340,17 @@ function update($app, $global, $quiet = $false, $independent, $suggested, $use_c
|
||||
|
||||
Invoke-HookScript -HookType 'pre_uninstall' -Manifest $old_manifest -Arch $architecture
|
||||
|
||||
#region Workaround for #2952
|
||||
if (test_running_process $app $global) {
|
||||
return
|
||||
}
|
||||
#endregion Workaround for #2952
|
||||
|
||||
Write-Host "Uninstalling '$app' ($old_version)"
|
||||
run_uninstaller $old_manifest $architecture $dir
|
||||
Invoke-Installer -Path $dir -Manifest $old_manifest -ProcessorArchitecture $architecture -Uninstall
|
||||
rm_shims $app $old_manifest $global $architecture
|
||||
env_rm_path $old_manifest $dir $global $architecture
|
||||
env_rm $old_manifest $global $architecture
|
||||
|
||||
# If a junction was used during install, that will have been used
|
||||
# as the reference directory. Otherwise it will just be the version
|
||||
# directory.
|
||||
$refdir = unlink_current $dir
|
||||
uninstall_psmodule $old_manifest $refdir $global
|
||||
env_rm_path $old_manifest $refdir $global $architecture
|
||||
env_rm $old_manifest $global $architecture
|
||||
|
||||
if ($force -and ($old_version -eq $version)) {
|
||||
if (!(Test-Path "$dir/../_$version.old")) {
|
||||
@@ -304,7 +394,10 @@ if (-not ($apps -or $all)) {
|
||||
error 'scoop update: --no-cache is invalid when <app> is not specified.'
|
||||
exit 1
|
||||
}
|
||||
update_scoop
|
||||
Sync-Scoop -Log:$show_update_log
|
||||
Sync-Bucket -Log:$show_update_log
|
||||
set_config LAST_UPDATE ([System.DateTime]::Now.ToString('o')) | Out-Null
|
||||
success 'Scoop was updated successfully!'
|
||||
} else {
|
||||
if ($global -and !(is_admin)) {
|
||||
'ERROR: You need admin rights to update global apps.'; exit 1
|
||||
@@ -316,7 +409,10 @@ if (-not ($apps -or $all)) {
|
||||
$apps_param = $apps
|
||||
|
||||
if ($updateScoop) {
|
||||
update_scoop
|
||||
Sync-Scoop -Log:$show_update_log
|
||||
Sync-Bucket -Log:$show_update_log
|
||||
set_config LAST_UPDATE ([System.DateTime]::Now.ToString('o')) | Out-Null
|
||||
success 'Scoop was updated successfully!'
|
||||
}
|
||||
|
||||
if ($apps_param -eq '*' -or $all) {
|
||||
@@ -360,11 +456,11 @@ if (-not ($apps -or $all)) {
|
||||
} elseif ($outdated.Length -eq 0) {
|
||||
Write-Host -f Green "Latest versions for all apps are installed! For more information try 'scoop status'"
|
||||
} else {
|
||||
Write-Host -f DarkCyan "Updating one outdated app:"
|
||||
Write-Host -f DarkCyan 'Updating one outdated app:'
|
||||
}
|
||||
}
|
||||
|
||||
$suggested = @{};
|
||||
$suggested = @{}
|
||||
# $outdated is a list of ($app, $global) tuples
|
||||
$outdated | ForEach-Object { update @_ $quiet $independent $suggested $use_cache $check_hash }
|
||||
}
|
||||
|
||||
@@ -29,29 +29,28 @@
|
||||
# -p, --passthru Return reports as objects
|
||||
|
||||
. "$PSScriptRoot\..\lib\getopt.ps1"
|
||||
. "$PSScriptRoot\..\lib\versions.ps1" # 'Select-CurrentVersion'
|
||||
. "$PSScriptRoot\..\lib\manifest.ps1" # 'Get-Manifest'
|
||||
. "$PSScriptRoot\..\lib\json.ps1" # 'json_path'
|
||||
. "$PSScriptRoot\..\lib\install.ps1" # 'hash_for_url'
|
||||
. "$PSScriptRoot\..\lib\download.ps1" # 'hash_for_url'
|
||||
. "$PSScriptRoot\..\lib\depends.ps1" # 'Get-Dependency'
|
||||
|
||||
$opt, $apps, $err = getopt $args 'asnup' @('all', 'scan', 'no-depends', 'no-update-scoop', 'passthru')
|
||||
if ($err) { "scoop virustotal: $err"; exit 1 }
|
||||
if (!$apps) { my_usage; exit 1 }
|
||||
$architecture = ensure_architecture
|
||||
$all = $apps -eq '*' -or $opt.a -or $opt.all
|
||||
if (!$apps -and !$all) { my_usage; exit 1 }
|
||||
$architecture = Get-DefaultArchitecture
|
||||
|
||||
if (is_scoop_outdated) {
|
||||
if ($opt.u -or $opt.'no-update-scoop') {
|
||||
warn 'Scoop is out of date.'
|
||||
} else {
|
||||
scoop update
|
||||
& "$PSScriptRoot\scoop-update.ps1"
|
||||
}
|
||||
}
|
||||
|
||||
$apps_param = $apps
|
||||
|
||||
if ($apps_param -eq '*' -or $opt.a -or $opt.all) {
|
||||
$apps = installed_apps $false
|
||||
$apps += installed_apps $true
|
||||
if ($all) {
|
||||
$apps = (installed_apps $false) + (installed_apps $true)
|
||||
}
|
||||
|
||||
if (!$opt.n -and !$opt.'no-depends') {
|
||||
@@ -66,7 +65,7 @@ $_ERR_NO_API_KEY = 16
|
||||
$exit_code = 0
|
||||
|
||||
# Global API key:
|
||||
$api_key = get_config virustotal_api_key
|
||||
$api_key = get_config VIRUSTOTAL_API_KEY
|
||||
if (!$api_key) {
|
||||
abort ("VirusTotal API key is not configured`n" +
|
||||
" You could get one from https://www.virustotal.com/gui/my-apikey and set with`n" +
|
||||
@@ -88,11 +87,6 @@ Function ConvertTo-VirusTotalUrlId ($url) {
|
||||
$url_id
|
||||
}
|
||||
|
||||
Function Get-RemoteFileSize ($url) {
|
||||
$response = Invoke-WebRequest -Uri $url -Method HEAD -UseBasicParsing
|
||||
$response.Headers.'Content-Length' | ForEach-Object { [System.Convert]::ToInt32($_) }
|
||||
}
|
||||
|
||||
Function Get-VirusTotalResultByHash ($hash, $url, $app) {
|
||||
$hash = $hash.ToLower()
|
||||
$api_url = "https://www.virustotal.com/api/v3/files/$hash"
|
||||
@@ -136,7 +130,7 @@ Function Get-VirusTotalResultByHash ($hash, $url, $app) {
|
||||
warn "$app`: $unsafe/$total, see $report_url"
|
||||
}
|
||||
Default {
|
||||
warn "`e[31m$app`: $unsafe/$total, see $report_url`e[0m"
|
||||
warn "$([char]0x1b)[31m$app`: $unsafe/$total, see $report_url$([char]0x1b)[0m"
|
||||
}
|
||||
}
|
||||
$maliciousResults = $vendorResults |
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
param($command)
|
||||
|
||||
if (!$command) {
|
||||
'ERROR: <command> missing'
|
||||
error '<command> missing'
|
||||
my_usage
|
||||
exit 1
|
||||
}
|
||||
@@ -12,7 +12,7 @@ if (!$command) {
|
||||
$path = Get-CommandPath $command
|
||||
|
||||
if ($null -eq $path) {
|
||||
Write-Host "'$command' not found / not a scoop shim."
|
||||
warn "'$command' not found, not a scoop shim, or a broken shim."
|
||||
exit 2
|
||||
} else {
|
||||
friendly_path $path
|
||||
|
||||
176
schema.json
176
schema.json
@@ -127,10 +127,6 @@
|
||||
"installer": {
|
||||
"$ref": "#/definitions/installer"
|
||||
},
|
||||
"msi": {
|
||||
"$ref": "#/definitions/stringOrArrayOfStrings",
|
||||
"description": "Deprecated"
|
||||
},
|
||||
"post_install": {
|
||||
"$ref": "#/definitions/stringOrArrayOfStrings"
|
||||
},
|
||||
@@ -179,19 +175,11 @@
|
||||
"type": "array"
|
||||
},
|
||||
"autoupdateArch": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"url": {
|
||||
"$ref": "#/definitions/autoupdateUriOrArrayOfAutoupdateUris"
|
||||
},
|
||||
"hash": {
|
||||
"$ref": "#/definitions/hashExtractionOrArrayOfHashExtractions"
|
||||
},
|
||||
"extract_dir": {
|
||||
"$ref": "#/definitions/stringOrArrayOfStrings"
|
||||
},
|
||||
"extract_to": {
|
||||
"$ref": "#/definitions/stringOrArrayOfStrings"
|
||||
"bin": {
|
||||
"$ref": "#/definitions/stringOrArrayOfStringsOrAnArrayOfArrayOfStrings"
|
||||
},
|
||||
"env_add_path": {
|
||||
"$ref": "#/definitions/stringOrArrayOfStrings"
|
||||
@@ -199,71 +187,97 @@
|
||||
"env_set": {
|
||||
"type": "object"
|
||||
},
|
||||
"bin": {
|
||||
"$ref": "#/definitions/stringOrArrayOfStringsOrAnArrayOfArrayOfStrings"
|
||||
"extract_dir": {
|
||||
"$ref": "#/definitions/stringOrArrayOfStrings"
|
||||
},
|
||||
"shortcuts": {
|
||||
"$ref": "#/definitions/shortcutsArray"
|
||||
"hash": {
|
||||
"$ref": "#/definitions/hashExtractionOrArrayOfHashExtractions"
|
||||
},
|
||||
"installer": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"file": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"post_install": {
|
||||
"$ref": "#/definitions/stringOrArrayOfStrings"
|
||||
"shortcuts": {
|
||||
"$ref": "#/definitions/shortcutsArray"
|
||||
},
|
||||
"psmodule": {
|
||||
"url": {
|
||||
"$ref": "#/definitions/autoupdateUriOrArrayOfAutoupdateUris"
|
||||
}
|
||||
}
|
||||
},
|
||||
"autoupdate": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"architecture": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
"32bit": {
|
||||
"$ref": "#/definitions/autoupdateArch"
|
||||
},
|
||||
"64bit": {
|
||||
"$ref": "#/definitions/autoupdateArch"
|
||||
},
|
||||
"arm64": {
|
||||
"$ref": "#/definitions/autoupdateArch"
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"bin": {
|
||||
"$ref": "#/definitions/stringOrArrayOfStringsOrAnArrayOfArrayOfStrings"
|
||||
},
|
||||
"env_add_path": {
|
||||
"$ref": "#/definitions/stringOrArrayOfStrings"
|
||||
},
|
||||
"env_set": {
|
||||
"type": "object"
|
||||
},
|
||||
"persist": {
|
||||
"$ref": "#/definitions/stringOrArrayOfStringsOrAnArrayOfArrayOfStrings"
|
||||
"extract_dir": {
|
||||
"$ref": "#/definitions/stringOrArrayOfStrings"
|
||||
},
|
||||
"hash": {
|
||||
"$ref": "#/definitions/hashExtractionOrArrayOfHashExtractions"
|
||||
},
|
||||
"installer": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"file": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"license": {
|
||||
"$ref": "#/definitions/license"
|
||||
},
|
||||
"notes": {
|
||||
"$ref": "#/definitions/stringOrArrayOfStrings"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"autoupdate": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/autoupdateArch"
|
||||
},
|
||||
{
|
||||
"persist": {
|
||||
"$ref": "#/definitions/stringOrArrayOfStringsOrAnArrayOfArrayOfStrings"
|
||||
},
|
||||
"psmodule": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"notes": {
|
||||
"$ref": "#/definitions/stringOrArrayOfStrings"
|
||||
},
|
||||
"architecture": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"32bit": {
|
||||
"$ref": "#/definitions/autoupdateArch"
|
||||
},
|
||||
"64bit": {
|
||||
"$ref": "#/definitions/autoupdateArch"
|
||||
}
|
||||
}
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"shortcuts": {
|
||||
"$ref": "#/definitions/shortcutsArray"
|
||||
},
|
||||
"url": {
|
||||
"$ref": "#/definitions/autoupdateUriOrArrayOfAutoupdateUris"
|
||||
}
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"checkver": {
|
||||
"anyOf": [
|
||||
@@ -315,6 +329,25 @@
|
||||
"script": {
|
||||
"$ref": "#/definitions/stringOrArrayOfStrings",
|
||||
"description": "Custom PowerShell script to retrieve application version using more complex approach."
|
||||
},
|
||||
"sourceforge": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"project": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
@@ -508,6 +541,9 @@
|
||||
},
|
||||
"64bit": {
|
||||
"$ref": "#/definitions/architecture"
|
||||
},
|
||||
"arm64": {
|
||||
"$ref": "#/definitions/architecture"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
@@ -563,10 +599,6 @@
|
||||
"license": {
|
||||
"$ref": "#/definitions/license"
|
||||
},
|
||||
"msi": {
|
||||
"$ref": "#/definitions/stringOrArrayOfStrings",
|
||||
"description": "Deprecated"
|
||||
},
|
||||
"notes": {
|
||||
"$ref": "#/definitions/stringOrArrayOfStrings"
|
||||
},
|
||||
@@ -614,6 +646,34 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"if": {
|
||||
"properties": {
|
||||
"architecture": {
|
||||
"properties": {
|
||||
"64bit": {
|
||||
"properties": {
|
||||
"url": false
|
||||
}
|
||||
},
|
||||
"32bit": {
|
||||
"properties": {
|
||||
"url": false
|
||||
}
|
||||
},
|
||||
"arm64": {
|
||||
"properties": {
|
||||
"url": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"required": [
|
||||
"url"
|
||||
]
|
||||
},
|
||||
"required": [
|
||||
"version",
|
||||
"homepage",
|
||||
|
||||
1
supporting/shimexe/.gitignore
vendored
1
supporting/shimexe/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
packages/
|
||||
@@ -1 +0,0 @@
|
||||
9726c3a429009a5b22bd92cb8ab96724c670e164e7240e83f27b7c8b7bd1ca39 *shim.exe
|
||||
@@ -1 +0,0 @@
|
||||
18a737674afde4d5e7e1647d8d1e98471bb260513c57739651f92fdf1647d76c92f0cd0a9bb458daf4eae4bdab9d31404162acf6d74a041e6415752b75d722e0 *shim.exe
|
||||
Binary file not shown.
@@ -1,22 +0,0 @@
|
||||
Param([Switch]$Fast)
|
||||
Push-Location $PSScriptRoot
|
||||
. "$PSScriptRoot\..\..\lib\core.ps1"
|
||||
. "$PSScriptRoot\..\..\lib\install.ps1"
|
||||
|
||||
if (!$Fast) {
|
||||
Write-Host "Install dependencies ..."
|
||||
& "$PSScriptRoot\install.ps1"
|
||||
}
|
||||
|
||||
$output = "$PSScriptRoot\bin"
|
||||
Write-Output 'Compiling shim.cs ...'
|
||||
& "$PSScriptRoot\packages\Microsoft.Net.Compilers.Toolset\tasks\net472\csc.exe" -deterministic -platform:anycpu -nologo -optimize -target:exe -out:"$output\shim.exe" shim.cs
|
||||
|
||||
Write-Output 'Computing checksums ...'
|
||||
Remove-Item "$PSScriptRoot\bin\checksum.sha256" -ErrorAction Ignore
|
||||
Remove-Item "$PSScriptRoot\bin\checksum.sha512" -ErrorAction Ignore
|
||||
Get-ChildItem "$PSScriptRoot\bin\*" -Include *.exe, *.dll | ForEach-Object {
|
||||
"$(compute_hash $_ 'sha256') *$($_.Name)" | Out-File "$PSScriptRoot\bin\checksum.sha256" -Append -Encoding oem
|
||||
"$(compute_hash $_ 'sha512') *$($_.Name)" | Out-File "$PSScriptRoot\bin\checksum.sha512" -Append -Encoding oem
|
||||
}
|
||||
Pop-Location
|
||||
@@ -1,8 +0,0 @@
|
||||
# https://github.com/edymtt/nugetstandalone
|
||||
$destinationFolder = "$PSScriptRoot\packages"
|
||||
if ((Test-Path -Path $destinationFolder)) {
|
||||
Remove-Item -Path $destinationFolder -Recurse | Out-Null
|
||||
}
|
||||
|
||||
New-Item $destinationFolder -Type Directory | Out-Null
|
||||
nuget install packages.config -o $destinationFolder -ExcludeVersion
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.Net.Compilers.Toolset" version="4.2.0" targetFramework="net45" developmentDependency="true" />
|
||||
</packages>
|
||||
@@ -1,168 +0,0 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Scoop {
|
||||
|
||||
class Program {
|
||||
[DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
|
||||
static extern bool CreateProcess(string lpApplicationName,
|
||||
string lpCommandLine, IntPtr lpProcessAttributes,
|
||||
IntPtr lpThreadAttributes, bool bInheritHandles,
|
||||
uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory,
|
||||
[In] ref STARTUPINFO lpStartupInfo,
|
||||
out PROCESS_INFORMATION lpProcessInformation);
|
||||
const int ERROR_ELEVATION_REQUIRED = 740;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
struct STARTUPINFO {
|
||||
public Int32 cb;
|
||||
public string lpReserved;
|
||||
public string lpDesktop;
|
||||
public string lpTitle;
|
||||
public Int32 dwX;
|
||||
public Int32 dwY;
|
||||
public Int32 dwXSize;
|
||||
public Int32 dwYSize;
|
||||
public Int32 dwXCountChars;
|
||||
public Int32 dwYCountChars;
|
||||
public Int32 dwFillAttribute;
|
||||
public Int32 dwFlags;
|
||||
public Int16 wShowWindow;
|
||||
public Int16 cbReserved2;
|
||||
public IntPtr lpReserved2;
|
||||
public IntPtr hStdInput;
|
||||
public IntPtr hStdOutput;
|
||||
public IntPtr hStdError;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct PROCESS_INFORMATION {
|
||||
public IntPtr hProcess;
|
||||
public IntPtr hThread;
|
||||
public int dwProcessId;
|
||||
public int dwThreadId;
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError=true)]
|
||||
static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds);
|
||||
const UInt32 INFINITE = 0xFFFFFFFF;
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError=true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
static extern bool CloseHandle(IntPtr hObject);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
static extern bool GetExitCodeProcess(IntPtr hProcess, out uint lpExitCode);
|
||||
|
||||
static int Main(string[] args) {
|
||||
var exe = Assembly.GetExecutingAssembly().Location;
|
||||
var dir = Path.GetDirectoryName(exe);
|
||||
var name = Path.GetFileNameWithoutExtension(exe);
|
||||
|
||||
var configPath = Path.Combine(dir, name + ".shim");
|
||||
if(!File.Exists(configPath)) {
|
||||
Console.Error.WriteLine("Couldn't find " + Path.GetFileName(configPath) + " in " + dir);
|
||||
return 1;
|
||||
}
|
||||
|
||||
var config = Config(configPath);
|
||||
var path = Get(config, "path");
|
||||
var add_args = Get(config, "args");
|
||||
|
||||
var si = new STARTUPINFO();
|
||||
var pi = new PROCESS_INFORMATION();
|
||||
|
||||
// create command line
|
||||
var cmd_args = add_args ?? "";
|
||||
var pass_args = GetArgs(Environment.CommandLine);
|
||||
if(!string.IsNullOrEmpty(pass_args)) {
|
||||
if(!string.IsNullOrEmpty(cmd_args)) cmd_args += " ";
|
||||
cmd_args += pass_args;
|
||||
}
|
||||
if(!string.IsNullOrEmpty(cmd_args)) cmd_args = " " + cmd_args;
|
||||
var cmd = "\"" + path + "\"" + cmd_args;
|
||||
|
||||
if(!CreateProcess(null, cmd, IntPtr.Zero, IntPtr.Zero,
|
||||
bInheritHandles: true,
|
||||
dwCreationFlags: 0,
|
||||
lpEnvironment: IntPtr.Zero, // inherit parent
|
||||
lpCurrentDirectory: null, // inherit parent
|
||||
lpStartupInfo: ref si,
|
||||
lpProcessInformation: out pi)) {
|
||||
|
||||
var error = Marshal.GetLastWin32Error();
|
||||
if(error == ERROR_ELEVATION_REQUIRED) {
|
||||
// Unfortunately, ShellExecute() does not allow us to run program without
|
||||
// CREATE_NEW_CONSOLE, so we can not replace CreateProcess() completely.
|
||||
// The good news is we are okay with CREATE_NEW_CONSOLE when we run program with elevation.
|
||||
Process process = new Process();
|
||||
process.StartInfo = new ProcessStartInfo(path, cmd_args);
|
||||
process.StartInfo.UseShellExecute = true;
|
||||
try {
|
||||
process.Start();
|
||||
}
|
||||
catch(Win32Exception exception) {
|
||||
return exception.ErrorCode;
|
||||
}
|
||||
process.WaitForExit();
|
||||
return process.ExitCode;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
WaitForSingleObject(pi.hProcess, INFINITE);
|
||||
|
||||
uint exit_code = 0;
|
||||
GetExitCodeProcess(pi.hProcess, out exit_code);
|
||||
|
||||
// Close process and thread handles.
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
|
||||
return (int)exit_code;
|
||||
}
|
||||
|
||||
// now uses GetArgs instead
|
||||
static string Serialize(string[] args) {
|
||||
return string.Join(" ", args.Select(a => a.Contains(' ') ? '"' + a + '"' : a));
|
||||
}
|
||||
|
||||
// strips the program name from the command line, returns just the arguments
|
||||
static string GetArgs(string cmdLine) {
|
||||
if(cmdLine.StartsWith("\"")) {
|
||||
var endQuote = cmdLine.IndexOf("\" ", 1);
|
||||
if(endQuote < 0) return "";
|
||||
return cmdLine.Substring(endQuote + 1);
|
||||
}
|
||||
var space = cmdLine.IndexOf(' ');
|
||||
if(space < 0 || space == cmdLine.Length - 1) return "";
|
||||
return cmdLine.Substring(space + 1);
|
||||
}
|
||||
|
||||
static string Get(Dictionary<string, string> dic, string key) {
|
||||
string value = null;
|
||||
dic.TryGetValue(key, out value);
|
||||
return value;
|
||||
}
|
||||
|
||||
static Dictionary<string, string> Config(string path) {
|
||||
var config = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach(var line in File.ReadAllLines(path)) {
|
||||
var m = Regex.Match(line, @"([^=]+)=(.*)");
|
||||
if(m.Success) {
|
||||
config[m.Groups[1].Value.Trim()] = m.Groups[2].Value.Trim();
|
||||
}
|
||||
}
|
||||
return config;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="packages\Microsoft.Net.Compilers.Toolset.4.2.0\build\Microsoft.Net.Compilers.Toolset.props" Condition="Exists('packages\Microsoft.Net.Compilers.Toolset.4.2.0\build\Microsoft.Net.Compilers.Toolset.props')" />
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{381F9D2E-2355-4F84-9206-06BB9175F97B}</ProjectGuid>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>Scoop.Shim</RootNamespace>
|
||||
<AssemblyName>Scoop.Shim</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5.0</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="shim.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105.The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('packages\Microsoft.Net.Compilers.Toolset.4.2.0\build\Microsoft.Net.Compilers.Toolset.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\Microsoft.Net.Compilers.Toolset.4.2.0\build\Microsoft.Net.Compilers.Toolset.props'))" />
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -1,10 +0,0 @@
|
||||
# https://github.com/edymtt/nugetstandalone
|
||||
$destinationFolder = "$PSScriptRoot\packages"
|
||||
if (!(Test-Path -Path $destinationFolder)) {
|
||||
Write-Host -f Red "Run .\install.ps1 first!"
|
||||
exit 1
|
||||
}
|
||||
|
||||
nuget update packages.config -r $destinationFolder
|
||||
Remove-Item $destinationFolder -Force -Recurse | Out-Null
|
||||
nuget install packages.config -o $destinationFolder -ExcludeVersion
|
||||
2
supporting/shims/kiennq/.gitignore
vendored
2
supporting/shims/kiennq/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
*.zip
|
||||
*.bak
|
||||
@@ -1,52 +0,0 @@
|
||||
|
||||
VER?=2.2.1
|
||||
ZIP=shimexe.zip
|
||||
URL?=https://github.com/kiennq/scoop-better-shimexe/releases/download/$(VER)/$(ZIP)
|
||||
LATEST_URL?=https://github.com/kiennq/scoop-better-shimexe/releases/latest
|
||||
NEWVER=$(shell cat version.txt)
|
||||
|
||||
all: verify ## make download unzip verify
|
||||
|
||||
version.txt:
|
||||
@curl --max-redirs 0 -s -D - -o /dev/null $(LATEST_URL) | grep -i ^location | sed -E -e "s|.*/([^/]+)$$|\1|" >version.txt
|
||||
@printf "%s " "Latest version is:"
|
||||
@cat version.txt
|
||||
|
||||
check: version.txt ## Check the version number in version.txt and update if needed
|
||||
|
||||
bump: check ## Bump version number in Makefile
|
||||
@rm -f Makefile.bak
|
||||
@sed -i.bak -e 's|=$(VER)|=$(NEWVER)|' Makefile
|
||||
@cmp --quiet Makefile{,.bak} || echo "Makefile bumped from $(VER) to $(NEWVER)"
|
||||
|
||||
$(ZIP): version.txt
|
||||
curl -L -s -o $(ZIP) $(URL)
|
||||
@touch $@
|
||||
|
||||
download: $(ZIP) ## Download shim from https://github.com/kiennq/scoop-better-shimexe
|
||||
|
||||
shim.exe: $(ZIP)
|
||||
unzip -z -j -o $(ZIP)
|
||||
@touch $@
|
||||
|
||||
unzip: shim.exe ## Unzip download
|
||||
|
||||
verify: shim.exe ## Verify SHA256 checksum for shim.exe
|
||||
sed -e "s|bin/||" checksum.sha256 | sha256sum -c
|
||||
|
||||
clean: ## Clean .zip files
|
||||
rm -f *.zip
|
||||
|
||||
help: ## Display help text
|
||||
@printf "%-8s %s\n" Target Description
|
||||
@printf "%-8s %s\n" '--------' '------------------------------------------'
|
||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "%-8s %s\n", $$1, $$2}'
|
||||
|
||||
.PHONY: all
|
||||
.PHONY: bump
|
||||
.PHONY: check
|
||||
.PHONY: clean
|
||||
.PHONY: download
|
||||
.PHONY: help
|
||||
.PHONY: unzip
|
||||
.PHONY: verify
|
||||
@@ -1 +1 @@
|
||||
aa685053f4a5c0e7145f2a27514c8a56ceae25b0824062326f04037937caa558 bin/shim.exe
|
||||
140e3801d8adeda639a21b14e62b93a4c7d26b7a758421f43c82be59753be49b *shim.exe
|
||||
|
||||
@@ -1 +1 @@
|
||||
67c605c8163869d8ef8153c64eb09b82645cbae8228928c0fef944d0259a7b2d3791ecf4b4b01e23566916a878ee7977bfc1a59846bccf3c63bd6a1cf4f521b5 bin/shim.exe
|
||||
59d9da9f9714003b915bcafbe1b41f53b121dde206ecc23984f62273e957766eece8d64ffc53011c328d3a2ad627aa0f4f7c39bbec8e7b64d0d2ee7b7e771423 *shim.exe
|
||||
|
||||
Binary file not shown.
@@ -1 +1 @@
|
||||
2.2.1
|
||||
v3.1.2
|
||||
|
||||
1
supporting/shims/scoopcs/checksum.sha256
Normal file
1
supporting/shims/scoopcs/checksum.sha256
Normal file
@@ -0,0 +1 @@
|
||||
0116068768fc992fc536738396b33db3dafe6b0cf0e6f54f6d1aa8b0331f3cec *shim.exe
|
||||
1
supporting/shims/scoopcs/checksum.sha512
Normal file
1
supporting/shims/scoopcs/checksum.sha512
Normal file
@@ -0,0 +1 @@
|
||||
d734c528e9f20581ed3c7aa71a458f7dff7e2780fa0c319ccb9c813cd8dbf656bd7e550b81d2aa3ee8775bff9a4e507bc0b25f075697405adca0f47d37835848 *shim.exe
|
||||
BIN
supporting/shims/scoopcs/shim.exe
Normal file
BIN
supporting/shims/scoopcs/shim.exe
Normal file
Binary file not shown.
1
supporting/shims/scoopcs/version.txt
Normal file
1
supporting/shims/scoopcs/version.txt
Normal file
@@ -0,0 +1 @@
|
||||
1.1.0
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,4 +1,4 @@
|
||||
b624949df8b0e3a6153fdfb730a7c6f4990b6592ee0d922e1788433d276610f3 *Newtonsoft.Json.dll
|
||||
9abb57d73d82a2d77008321a85aff2b62e5ac68bebb54ece8668c96cc112e36b *Newtonsoft.Json.Schema.dll
|
||||
0318c8221ce4d44806f8def619bcc02886be0902aab80080e6251c50c6ca53a9 *Scoop.Validator.dll
|
||||
40a70bee96d108701f8f2e81392f9b79fd003f1cb4e1653ad2429753153fd7ee *validator.exe
|
||||
e1e27af7b07eeedf5ce71a9255f0422816a6fc5849a483c6714e1b472044fa9d *Newtonsoft.Json.dll
|
||||
7496d5349a123a6e3696085662b2ff17b156ccdb0e30e0c396ac72d2da36ce1c *Newtonsoft.Json.Schema.dll
|
||||
83b1006443e8c340ca4c631614fc2ce0d5cb9a28c851e3b59724299f58b1397f *Scoop.Validator.dll
|
||||
87f8f8db2202a3fbef6f431d0b7e20cec9d32095c441927402041f3c4076c1b6 *validator.exe
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
2fdf035661f349206f58ea1feed8805b7f9517a21f9c113e7301c69de160f184c774350a12a710046e3ff6baa37345d319b6f47fd24fbba4e042d54014bee511 *Newtonsoft.Json.dll
|
||||
855ab2e30c9d523c9f321ae861c5969244185f660fa47e05cec96df8e2970d19843dbd3d89a0fca845544641915d1adf4b4a2145ef568dd99da7791e5064d70e *Newtonsoft.Json.Schema.dll
|
||||
338793e6127330c0b05728291fcf18441127ffb56e1bd5c0f0588cd7436605f4b852f4bb622f655896a7eb7b1262add142b200fd5f37391b47d1401becb6b81c *Scoop.Validator.dll
|
||||
d497c27b48f44f4cff270d3c8801b0cecc74108f8786a4a7c40e57541308ae33a69f5456cfc43ae1ce4214038d20da9fbeac1bcf76cc58d972863b58dab18401 *validator.exe
|
||||
56eb7f070929b239642dab729537dde2c2287bdb852ad9e80b5358c74b14bc2b2dded910d0e3b6304ea27eb587e5f19db0a92e1cbae6a70fb20b4ef05057e4ac *Newtonsoft.Json.dll
|
||||
78b12beb1e67ac4f6efa0fcba57b4b34ea6a31d8b369934d6b6a6617386ef9939ea453ac262916e5857ce0359eb809424ea33c676a87a8fdfd77a59b2ce96db0 *Newtonsoft.Json.Schema.dll
|
||||
e9da4370aee4df47eedcf15d9749712eee513e5a9115b808617ddfcfde5bc47a0410edfb57508fcf51033c0be967611b2fd2c2ba944de7290c020cc67f77ac57 *Scoop.Validator.dll
|
||||
58a0c37e98cac17822c7756bf6686a5fb74e711b8d986d13bd2f689f6b3b1f485fcd908d92cbc6a162a0e5974c2c5a43de57d15f1996be0aa405e41ec2ec8393 *validator.exe
|
||||
|
||||
Binary file not shown.
@@ -4,7 +4,7 @@ Push-Location $PSScriptRoot
|
||||
. "$PSScriptRoot\..\..\lib\install.ps1"
|
||||
|
||||
if (!$Fast) {
|
||||
Write-Host "Install dependencies ..."
|
||||
Write-Host 'Install dependencies ...'
|
||||
& "$PSScriptRoot\install.ps1"
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ Write-Output 'Computing checksums ...'
|
||||
Remove-Item "$PSScriptRoot\bin\checksum.sha256" -ErrorAction Ignore
|
||||
Remove-Item "$PSScriptRoot\bin\checksum.sha512" -ErrorAction Ignore
|
||||
Get-ChildItem "$PSScriptRoot\bin\*" -Include *.exe, *.dll | ForEach-Object {
|
||||
"$(compute_hash $_ 'sha256') *$($_.Name)" | Out-File "$PSScriptRoot\bin\checksum.sha256" -Append -Encoding oem
|
||||
"$(compute_hash $_ 'sha512') *$($_.Name)" | Out-File "$PSScriptRoot\bin\checksum.sha512" -Append -Encoding oem
|
||||
"$((Get-FileHash -Path $_ -Algorithm SHA256).Hash.ToLower()) *$($_.Name)" | Out-File "$PSScriptRoot\bin\checksum.sha256" -Append -Encoding oem
|
||||
"$((Get-FileHash -Path $_ -Algorithm SHA512).Hash.ToLower()) *$($_.Name)" | Out-File "$PSScriptRoot\bin\checksum.sha512" -Append -Encoding oem
|
||||
}
|
||||
Pop-Location
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Newtonsoft.Json" version="13.0.1" targetFramework="net45" />
|
||||
<package id="Newtonsoft.Json.Schema" version="3.0.15-beta2" targetFramework="net45" />
|
||||
<package id="Microsoft.Net.Compilers.Toolset" version="4.2.0" targetFramework="net45" developmentDependency="true" />
|
||||
<package id="Newtonsoft.Json" version="13.0.3" targetFramework="net45" />
|
||||
<package id="Newtonsoft.Json.Schema" version="4.0.1" targetFramework="net45" />
|
||||
<package id="Microsoft.Net.Compilers.Toolset" version="4.10.0" targetFramework="net45"
|
||||
developmentDependency="true" />
|
||||
</packages>
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="packages\Microsoft.Net.Compilers.Toolset.4.2.0\build\Microsoft.Net.Compilers.Toolset.props" Condition="Exists('packages\Microsoft.Net.Compilers.Toolset.4.2.0\build\Microsoft.Net.Compilers.Toolset.props')" />
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<Import
|
||||
Project="packages\Microsoft.Net.Compilers.Toolset.4.10.0\build\Microsoft.Net.Compilers.Toolset.props"
|
||||
Condition="Exists('packages\Microsoft.Net.Compilers.Toolset.4.10.0\build\Microsoft.Net.Compilers.Toolset.props')" />
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props"
|
||||
Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
@@ -14,12 +17,14 @@
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed">
|
||||
<HintPath>packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
<Reference
|
||||
Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed">
|
||||
<HintPath>packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json.Schema, Version=3.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed">
|
||||
<HintPath>packages\Newtonsoft.Json.Schema.3.0.15-beta2\lib\net45\Newtonsoft.Json.Schema.dll</HintPath>
|
||||
<Reference
|
||||
Include="Newtonsoft.Json.Schema, Version=4.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed">
|
||||
<HintPath>packages\Newtonsoft.Json.Schema.4.0.1\lib\net45\Newtonsoft.Json.Schema.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
@@ -41,8 +46,12 @@
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105.The missing file is {0}.</ErrorText>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer.
|
||||
Enable NuGet Package Restore to download them. For more information, see
|
||||
http://go.microsoft.com/fwlink/?LinkID=322105.The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('packages\Microsoft.Net.Compilers.Toolset.4.2.0\build\Microsoft.Net.Compilers.Toolset.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\Microsoft.Net.Compilers.Toolset.4.2.0\build\Microsoft.Net.Compilers.Toolset.props'))" />
|
||||
<Error
|
||||
Condition="!Exists('packages\Microsoft.Net.Compilers.Toolset.4.10.0\build\Microsoft.Net.Compilers.Toolset.props')"
|
||||
Text="$([System.String]::Format('$(ErrorText)', 'packages\Microsoft.Net.Compilers.Toolset.4.10.0\build\Microsoft.Net.Compilers.Toolset.props'))" />
|
||||
</Target>
|
||||
</Project>
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
$repo_dir = (Get-Item $MyInvocation.MyCommand.Path).Directory.Parent.FullName
|
||||
|
||||
$repo_files = @( Get-ChildItem $repo_dir -File -Recurse -Force )
|
||||
|
||||
$project_file_exclusions = @(
|
||||
'[\\/]\.git[\\/]',
|
||||
'\.sublime-workspace$',
|
||||
'\.DS_Store$',
|
||||
'supporting(\\|/)validator(\\|/)packages(\\|/)*',
|
||||
'supporting(\\|/)shimexe(\\|/)packages(\\|/)*'
|
||||
)
|
||||
|
||||
Describe 'Project code' {
|
||||
|
||||
$files = @(
|
||||
$repo_files |
|
||||
Where-Object { $_.fullname -inotmatch $($project_file_exclusions -join '|') } |
|
||||
Where-Object { $_.fullname -imatch '.(ps1|psm1)$' }
|
||||
)
|
||||
|
||||
$files_exist = ($files.Count -gt 0)
|
||||
|
||||
It $('PowerShell code files exist ({0} found)' -f $files.Count) -Skip:$(-not $files_exist) {
|
||||
if (-not ($files.Count -gt 0)) {
|
||||
throw 'No PowerShell code files were found'
|
||||
}
|
||||
}
|
||||
|
||||
function Test-PowerShellSyntax {
|
||||
# ref: http://powershell.org/wp/forums/topic/how-to-check-syntax-of-scripts-automatically @@ https://archive.is/xtSv6
|
||||
# originally created by Alexander Petrovskiy & Dave Wyatt
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
|
||||
[string[]]
|
||||
$Path
|
||||
)
|
||||
|
||||
process {
|
||||
foreach ($scriptPath in $Path) {
|
||||
$contents = Get-Content -Path $scriptPath
|
||||
|
||||
if ($null -eq $contents) {
|
||||
continue
|
||||
}
|
||||
|
||||
$errors = $null
|
||||
$null = [System.Management.Automation.PSParser]::Tokenize($contents, [ref]$errors)
|
||||
|
||||
New-Object psobject -Property @{
|
||||
Path = $scriptPath
|
||||
SyntaxErrorsFound = ($errors.Count -gt 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
It 'PowerShell code files do not contain syntax errors' -Skip:$(-not $files_exist) {
|
||||
$badFiles = @(
|
||||
foreach ($file in $files) {
|
||||
if ( (Test-PowerShellSyntax $file.FullName).SyntaxErrorsFound ) {
|
||||
$file.FullName
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if ($badFiles.Count -gt 0) {
|
||||
throw "The following files have syntax errors: `r`n`r`n$($badFiles -join "`r`n")"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
. "$PSScriptRoot\Import-File-Tests.ps1"
|
||||
@@ -1,127 +1,52 @@
|
||||
#Requires -Version 5.0
|
||||
#Requires -Modules @{ ModuleName = 'Pester'; RequiredVersion = '4.10.1' }
|
||||
#Requires -Version 5.1
|
||||
#Requires -Modules @{ ModuleName = 'BuildHelpers'; ModuleVersion = '2.0.1' }
|
||||
#Requires -Modules @{ ModuleName = 'Pester'; ModuleVersion = '5.2.0' }
|
||||
param(
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[String]
|
||||
$repo_dir = (Split-Path -Path $MyInvocation.PSCommandPath -Parent)
|
||||
[String] $BucketPath = $MyInvocation.PSScriptRoot
|
||||
)
|
||||
|
||||
. "$PSScriptRoot\Scoop-TestLib.ps1"
|
||||
. "$PSScriptRoot\..\lib\manifest.ps1"
|
||||
. "$PSScriptRoot\..\lib\unix.ps1"
|
||||
. "$PSScriptRoot\Scoop-00File.Tests.ps1" -TestPath $BucketPath
|
||||
|
||||
$bucketdir = $repo_dir
|
||||
if (Test-Path("$repo_dir\..\bucket")) {
|
||||
$bucketdir = "$repo_dir\..\bucket"
|
||||
} elseif (Test-Path("$repo_dir\bucket")) {
|
||||
$bucketdir = "$repo_dir\bucket"
|
||||
}
|
||||
|
||||
# Tests for non manifest files
|
||||
$repo_files = @(Get-ChildItem -Path $repo_dir -File -Recurse)
|
||||
$project_file_exclusions = @(
|
||||
'[\\/]\.git[\\/]',
|
||||
'.sublime-workspace$',
|
||||
'.DS_Store$'
|
||||
)
|
||||
. "$PSScriptRoot\Import-File-Tests.ps1"
|
||||
|
||||
# Tests for manifest files
|
||||
Describe 'Manifest Validator' -Tag 'Validator' {
|
||||
BeforeAll {
|
||||
$schema = "$PSScriptRoot\..\schema.json"
|
||||
$working_dir = setup_working 'manifest'
|
||||
Add-Type -Path "$PSScriptRoot\..\supporting\validator\bin\Newtonsoft.Json.dll"
|
||||
Add-Type -Path "$PSScriptRoot\..\supporting\validator\bin\Newtonsoft.Json.Schema.dll"
|
||||
Add-Type -Path "$PSScriptRoot\..\supporting\validator\bin\Scoop.Validator.dll"
|
||||
}
|
||||
|
||||
It 'Scoop.Validator is available' {
|
||||
([System.Management.Automation.PSTypeName]'Scoop.Validator').Type | Should -Be 'Scoop.Validator'
|
||||
}
|
||||
|
||||
Context 'parse_json function' {
|
||||
It 'fails with invalid json' {
|
||||
{ parse_json "$working_dir\broken_wget.json" } | Should -Throw
|
||||
Describe 'Manifest validates against the schema' {
|
||||
BeforeDiscovery {
|
||||
$bucketDir = if (Test-Path "$BucketPath\bucket") {
|
||||
"$BucketPath\bucket"
|
||||
} else {
|
||||
$BucketPath
|
||||
}
|
||||
}
|
||||
|
||||
Context 'schema validation' {
|
||||
It 'fails with broken schema' {
|
||||
$validator = New-Object Scoop.Validator("$working_dir\broken_schema.json", $true)
|
||||
$validator.Validate("$working_dir\wget.json") | Should -BeFalse
|
||||
$validator.Errors.Count | Should -Be 1
|
||||
$validator.Errors | Select-Object -First 1 | Should -Match 'broken_schema.*(line 6).*(position 4)'
|
||||
}
|
||||
It 'fails with broken manifest' {
|
||||
$validator = New-Object Scoop.Validator($schema, $true)
|
||||
$validator.Validate("$working_dir\broken_wget.json") | Should -BeFalse
|
||||
$validator.Errors.Count | Should -Be 1
|
||||
$validator.Errors | Select-Object -First 1 | Should -Match 'broken_wget.*(line 5).*(position 4)'
|
||||
}
|
||||
It 'fails with invalid manifest' {
|
||||
$validator = New-Object Scoop.Validator($schema, $true)
|
||||
$validator.Validate("$working_dir\invalid_wget.json") | Should -BeFalse
|
||||
$validator.Errors.Count | Should -Be 16
|
||||
$validator.Errors | Select-Object -First 1 | Should -Match "Property 'randomproperty' has not been defined and the schema does not allow additional properties\."
|
||||
$validator.Errors | Select-Object -Last 1 | Should -Match 'Required properties are missing from object: version\.'
|
||||
}
|
||||
}
|
||||
}
|
||||
Describe 'manifest validates against the schema' -Tag 'Manifests' {
|
||||
BeforeAll {
|
||||
$schema = "$PSScriptRoot\..\schema.json"
|
||||
$changed_manifests = @()
|
||||
if ($env:CI -eq $true) {
|
||||
# AppVeyor
|
||||
$commit = if ($env:APPVEYOR_PULL_REQUEST_HEAD_COMMIT) { $env:APPVEYOR_PULL_REQUEST_HEAD_COMMIT } else { $env:APPVEYOR_REPO_COMMIT }
|
||||
|
||||
# GitHub Actions
|
||||
if ($env:GITHUB_SHA) {
|
||||
$commit = $env:GITHUB_SHA
|
||||
}
|
||||
$changed_manifests = (Get-GitChangedFile -Path $repo_dir -Include '*.json' -Commit $commit)
|
||||
Set-BuildEnvironment -Force
|
||||
$manifestFiles = @(Get-GitChangedFile -Path $bucketDir -Include '*.json' -Commit $env:BHCommitHash)
|
||||
} else {
|
||||
$manifestFiles = (Get-ChildItem $bucketDir -Filter '*.json' -Recurse).FullName
|
||||
}
|
||||
$manifest_files = Get-ChildItem $bucketdir *.json
|
||||
$validator = New-Object Scoop.Validator($schema, $true)
|
||||
}
|
||||
|
||||
$quota_exceeded = $false
|
||||
|
||||
$manifest_files | ForEach-Object {
|
||||
$skip_manifest = ($changed_manifests -inotcontains $_.FullName)
|
||||
if ($env:CI -ne $true -or $changed_manifests -imatch 'schema.json') {
|
||||
$skip_manifest = $false
|
||||
}
|
||||
It "$_" -Skip:$skip_manifest {
|
||||
BeforeAll {
|
||||
Add-Type -Path "$PSScriptRoot\..\supporting\validator\bin\Scoop.Validator.dll"
|
||||
# Could not use backslash '\' in Linux/macOS for .NET object 'Scoop.Validator'
|
||||
$validator = New-Object Scoop.Validator("$PSScriptRoot/../schema.json", $true)
|
||||
$global:quotaExceeded = $false
|
||||
}
|
||||
It '<_>' -TestCases $manifestFiles {
|
||||
if ($global:quotaExceeded) {
|
||||
Set-ItResult -Skipped -Because 'Schema validation limit exceeded.'
|
||||
} else {
|
||||
$file = $_ # exception handling may overwrite $_
|
||||
|
||||
if (!($quota_exceeded)) {
|
||||
try {
|
||||
$validator.Validate($file.fullname)
|
||||
|
||||
if ($validator.Errors.Count -gt 0) {
|
||||
Write-Host -f red " [-] $_ has $($validator.Errors.Count) Error$(If($validator.Errors.Count -gt 1) { 's' })!"
|
||||
Write-Host -f yellow $validator.ErrorsAsString
|
||||
}
|
||||
$validator.Errors.Count | Should -Be 0
|
||||
} catch {
|
||||
if ($_.exception.message -like '*The free-quota limit of 1000 schema validations per hour has been reached.*') {
|
||||
$quota_exceeded = $true
|
||||
Write-Host -f darkyellow 'Schema validation limit exceeded. Will skip further validations.'
|
||||
} else {
|
||||
throw
|
||||
}
|
||||
try {
|
||||
$validator.Validate($file)
|
||||
if ($validator.Errors.Count -gt 0) {
|
||||
Write-Host " [-] $_ has $($validator.Errors.Count) Error$(If($validator.Errors.Count -gt 1) { 's' })!" -ForegroundColor Red
|
||||
Write-Host $validator.ErrorsAsString -ForegroundColor Yellow
|
||||
}
|
||||
$validator.Errors.Count | Should -Be 0
|
||||
} catch {
|
||||
if ($_.Exception.Message -like '*The free-quota limit of 1000 schema validations per hour has been reached.*') {
|
||||
$global:quotaExceeded = $true
|
||||
Set-ItResult -Skipped -Because 'Schema validation limit exceeded.'
|
||||
} else {
|
||||
throw
|
||||
}
|
||||
}
|
||||
|
||||
$manifest = parse_json $file.fullname
|
||||
$url = arch_specific 'url' $manifest '32bit'
|
||||
$url64 = arch_specific 'url' $manifest '64bit'
|
||||
if (!$url) {
|
||||
$url = $url64
|
||||
}
|
||||
$url | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
if ([String]::IsNullOrEmpty($MyInvocation.PSScriptRoot)) {
|
||||
Write-Error 'This script should not be called directly! It has to be imported from a buckets test file!'
|
||||
exit 1
|
||||
}
|
||||
|
||||
Describe 'Style constraints for non-binary project files' {
|
||||
|
||||
$files = @(
|
||||
# gather all files except '*.exe', '*.zip', or any .git repository files
|
||||
$repo_files |
|
||||
Where-Object { $_.fullname -inotmatch $($project_file_exclusions -join '|') } |
|
||||
Where-Object { $_.fullname -inotmatch '(.exe|.zip|.dll)$' } |
|
||||
Where-Object { $_.fullname -inotmatch '(unformatted)' }
|
||||
)
|
||||
|
||||
$files_exist = ($files.Count -gt 0)
|
||||
|
||||
It $('non-binary project files exist ({0} found)' -f $files.Count) -Skip:$(-not $files_exist) {
|
||||
if (-not ($files.Count -gt 0)) {
|
||||
throw 'No non-binary project were found'
|
||||
}
|
||||
}
|
||||
|
||||
It 'files do not contain leading UTF-8 BOM' -Skip:$(-not $files_exist) {
|
||||
# UTF-8 BOM == 0xEF 0xBB 0xBF
|
||||
# see http://www.powershellmagazine.com/2012/12/17/pscxtip-how-to-determine-the-byte-order-mark-of-a-text-file @@ https://archive.is/RgT42
|
||||
# ref: http://poshcode.org/2153 @@ https://archive.is/sGnnu
|
||||
$badFiles = @(
|
||||
foreach ($file in $files) {
|
||||
if ((Get-Command Get-Content).parameters.ContainsKey('AsByteStream')) {
|
||||
# PowerShell Core (6.0+) '-Encoding byte' is replaced by '-AsByteStream'
|
||||
$content = ([char[]](Get-Content $file.FullName -AsByteStream -TotalCount 3) -join '')
|
||||
} else {
|
||||
$content = ([char[]](Get-Content $file.FullName -Encoding byte -TotalCount 3) -join '')
|
||||
}
|
||||
if ([regex]::match($content, '(?ms)^\xEF\xBB\xBF').success) {
|
||||
$file.FullName
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if ($badFiles.Count -gt 0) {
|
||||
throw "The following files have utf-8 BOM: `r`n`r`n$($badFiles -join "`r`n")"
|
||||
}
|
||||
}
|
||||
|
||||
It 'files end with a newline' -Skip:$(-not $files_exist) {
|
||||
$badFiles = @(
|
||||
foreach ($file in $files) {
|
||||
# Ignore previous TestResults.xml
|
||||
if ($file -match 'TestResults.xml') {
|
||||
continue
|
||||
}
|
||||
$string = [System.IO.File]::ReadAllText($file.FullName)
|
||||
if ($string.Length -gt 0 -and $string[-1] -ne "`n") {
|
||||
$file.FullName
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if ($badFiles.Count -gt 0) {
|
||||
throw "The following files do not end with a newline: `r`n`r`n$($badFiles -join "`r`n")"
|
||||
}
|
||||
}
|
||||
|
||||
It 'file newlines are CRLF' -Skip:$(-not $files_exist) {
|
||||
$badFiles = @(
|
||||
foreach ($file in $files) {
|
||||
$content = Get-Content -Raw $file.FullName
|
||||
if (!$content) {
|
||||
throw "File contents are null: $($file.FullName)"
|
||||
}
|
||||
$lines = [regex]::split($content, '\r\n')
|
||||
$lineCount = $lines.Count
|
||||
|
||||
for ($i = 0; $i -lt $lineCount; $i++) {
|
||||
if ( [regex]::match($lines[$i], '\r|\n').success ) {
|
||||
$file.FullName
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if ($badFiles.Count -gt 0) {
|
||||
throw "The following files have non-CRLF line endings: `r`n`r`n$($badFiles -join "`r`n")"
|
||||
}
|
||||
}
|
||||
|
||||
It 'files have no lines containing trailing whitespace' -Skip:$(-not $files_exist) {
|
||||
$badLines = @(
|
||||
foreach ($file in $files) {
|
||||
# Ignore previous TestResults.xml
|
||||
if ($file -match 'TestResults.xml') {
|
||||
continue
|
||||
}
|
||||
$lines = [System.IO.File]::ReadAllLines($file.FullName)
|
||||
$lineCount = $lines.Count
|
||||
|
||||
for ($i = 0; $i -lt $lineCount; $i++) {
|
||||
if ($lines[$i] -match '\s+$') {
|
||||
'File: {0}, Line: {1}' -f $file.FullName, ($i + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if ($badLines.Count -gt 0) {
|
||||
throw "The following $($badLines.Count) lines contain trailing whitespace: `r`n`r`n$($badLines -join "`r`n")"
|
||||
}
|
||||
}
|
||||
|
||||
It 'any leading whitespace consists only of spaces (excepting makefiles)' -Skip:$(-not $files_exist) {
|
||||
$badLines = @(
|
||||
foreach ($file in $files) {
|
||||
if ($file.fullname -inotmatch '(^|.)makefile$') {
|
||||
$lines = [System.IO.File]::ReadAllLines($file.FullName)
|
||||
$lineCount = $lines.Count
|
||||
|
||||
for ($i = 0; $i -lt $lineCount; $i++) {
|
||||
if ($lines[$i] -notmatch '^[ ]*(\S|$)') {
|
||||
'File: {0}, Line: {1}' -f $file.FullName, ($i + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if ($badLines.Count -gt 0) {
|
||||
throw "The following $($badLines.Count) lines contain TABs within leading whitespace: `r`n`r`n$($badLines -join "`r`n")"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
188
test/Scoop-00File.Tests.ps1
Normal file
188
test/Scoop-00File.Tests.ps1
Normal file
@@ -0,0 +1,188 @@
|
||||
param(
|
||||
[String] $TestPath = "$PSScriptRoot\.."
|
||||
)
|
||||
|
||||
BeforeDiscovery {
|
||||
$project_file_exclusions = @(
|
||||
'[\\/]\.git[\\/]',
|
||||
'\.sublime-workspace$',
|
||||
'\.DS_Store$',
|
||||
'supporting(\\|/)validator(\\|/)packages(\\|/)*'
|
||||
)
|
||||
$repo_files = (Get-ChildItem $TestPath -File -Recurse).FullName |
|
||||
Where-Object { $_ -inotmatch $($project_file_exclusions -join '|') }
|
||||
}
|
||||
|
||||
Describe 'Code Syntax' -ForEach @(, $repo_files) -Tag 'File' {
|
||||
BeforeAll {
|
||||
$files = @(
|
||||
$_ | Where-Object { $_ -imatch '.(ps1|psm1)$' }
|
||||
)
|
||||
function Test-PowerShellSyntax {
|
||||
# ref: http://powershell.org/wp/forums/topic/how-to-check-syntax-of-scripts-automatically @@ https://archive.is/xtSv6
|
||||
# originally created by Alexander Petrovskiy & Dave Wyatt
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
|
||||
[string[]]
|
||||
$Path
|
||||
)
|
||||
|
||||
process {
|
||||
foreach ($scriptPath in $Path) {
|
||||
$contents = Get-Content -Path $scriptPath
|
||||
|
||||
if ($null -eq $contents) {
|
||||
continue
|
||||
}
|
||||
|
||||
$errors = $null
|
||||
$null = [System.Management.Automation.PSParser]::Tokenize($contents, [ref]$errors)
|
||||
|
||||
New-Object psobject -Property @{
|
||||
Path = $scriptPath
|
||||
SyntaxErrorsFound = ($errors.Count -gt 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
It 'PowerShell code files do not contain syntax errors' {
|
||||
$badFiles = @(
|
||||
foreach ($file in $files) {
|
||||
if ( (Test-PowerShellSyntax $file).SyntaxErrorsFound ) {
|
||||
$file
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if ($badFiles.Count -gt 0) {
|
||||
throw "The following files have syntax errors: `r`n`r`n$($badFiles -join "`r`n")"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Describe 'Style constraints for non-binary project files' -ForEach @(, $repo_files) -Tag 'File' {
|
||||
BeforeAll {
|
||||
$files = @(
|
||||
# gather all files except '*.exe', '*.zip', or any .git repository files
|
||||
$_ |
|
||||
Where-Object { $_ -inotmatch '(.exe|.zip|.dll)$' } |
|
||||
Where-Object { $_ -inotmatch '(unformatted)' }
|
||||
)
|
||||
}
|
||||
|
||||
It 'files do not contain leading UTF-8 BOM' {
|
||||
# UTF-8 BOM == 0xEF 0xBB 0xBF
|
||||
# see http://www.powershellmagazine.com/2012/12/17/pscxtip-how-to-determine-the-byte-order-mark-of-a-text-file @@ https://archive.is/RgT42
|
||||
# ref: http://poshcode.org/2153 @@ https://archive.is/sGnnu
|
||||
$badFiles = @(
|
||||
foreach ($file in $files) {
|
||||
if ((Get-Command Get-Content).parameters.ContainsKey('AsByteStream')) {
|
||||
# PowerShell Core (6.0+) '-Encoding byte' is replaced by '-AsByteStream'
|
||||
$content = ([char[]](Get-Content $file -AsByteStream -TotalCount 3) -join '')
|
||||
} else {
|
||||
$content = ([char[]](Get-Content $file -Encoding byte -TotalCount 3) -join '')
|
||||
}
|
||||
if ([regex]::match($content, '(?ms)^\xEF\xBB\xBF').success) {
|
||||
$file
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if ($badFiles.Count -gt 0) {
|
||||
throw "The following files have utf-8 BOM: `r`n`r`n$($badFiles -join "`r`n")"
|
||||
}
|
||||
}
|
||||
|
||||
It 'files end with a newline' {
|
||||
$badFiles = @(
|
||||
foreach ($file in $files) {
|
||||
# Ignore previous TestResults.xml
|
||||
if ($file -match 'TestResults.xml') {
|
||||
continue
|
||||
}
|
||||
$string = [System.IO.File]::ReadAllText($file)
|
||||
if ($string.Length -gt 0 -and $string[-1] -ne "`n") {
|
||||
$file
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if ($badFiles.Count -gt 0) {
|
||||
throw "The following files do not end with a newline: `r`n`r`n$($badFiles -join "`r`n")"
|
||||
}
|
||||
}
|
||||
|
||||
It 'file newlines are CRLF' {
|
||||
$badFiles = @(
|
||||
foreach ($file in $files) {
|
||||
$content = [System.IO.File]::ReadAllText($file)
|
||||
if (!$content) {
|
||||
throw "File contents are null: $($file)"
|
||||
}
|
||||
$lines = [regex]::split($content, '\r\n')
|
||||
$lineCount = $lines.Count
|
||||
|
||||
for ($i = 0; $i -lt $lineCount; $i++) {
|
||||
if ( [regex]::match($lines[$i], '\r|\n').success ) {
|
||||
$file
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if ($badFiles.Count -gt 0) {
|
||||
throw "The following files have non-CRLF line endings: `r`n`r`n$($badFiles -join "`r`n")"
|
||||
}
|
||||
}
|
||||
|
||||
It 'files have no lines containing trailing whitespace' {
|
||||
$badLines = @(
|
||||
foreach ($file in $files) {
|
||||
# Ignore previous TestResults.xml
|
||||
if ($file -match 'TestResults.xml') {
|
||||
continue
|
||||
}
|
||||
$lines = [System.IO.File]::ReadAllLines($file)
|
||||
$lineCount = $lines.Count
|
||||
|
||||
for ($i = 0; $i -lt $lineCount; $i++) {
|
||||
if ($lines[$i] -match '\s+$') {
|
||||
'File: {0}, Line: {1}' -f $file, ($i + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if ($badLines.Count -gt 0) {
|
||||
throw "The following $($badLines.Count) lines contain trailing whitespace: `r`n`r`n$($badLines -join "`r`n")"
|
||||
}
|
||||
}
|
||||
|
||||
It 'any leading whitespace consists only of spaces (excepting makefiles)' {
|
||||
$badLines = @(
|
||||
foreach ($file in $files) {
|
||||
if ($file -inotmatch '(^|.)makefile$') {
|
||||
$lines = [System.IO.File]::ReadAllLines($file)
|
||||
$lineCount = $lines.Count
|
||||
|
||||
for ($i = 0; $i -lt $lineCount; $i++) {
|
||||
if ($lines[$i] -notmatch '^[ ]*(\S|$)') {
|
||||
'File: {0}, Line: {1}' -f $file, ($i + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if ($badLines.Count -gt 0) {
|
||||
throw "The following $($badLines.Count) lines contain TABs within leading whitespace: `r`n`r`n$($badLines -join "`r`n")"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
33
test/Scoop-00Linting.Tests.ps1
Normal file
33
test/Scoop-00Linting.Tests.ps1
Normal file
@@ -0,0 +1,33 @@
|
||||
Describe 'PSScriptAnalyzer' -Tag 'Linter' {
|
||||
BeforeDiscovery {
|
||||
$scriptDir = @('.', 'bin', 'lib', 'libexec', 'test')
|
||||
}
|
||||
|
||||
BeforeAll {
|
||||
$lintSettings = "$PSScriptRoot\..\PSScriptAnalyzerSettings.psd1"
|
||||
}
|
||||
|
||||
It 'PSScriptAnalyzerSettings.ps1 should exist' {
|
||||
$lintSettings | Should -Exist
|
||||
}
|
||||
|
||||
Context 'Linting all *.psd1, *.psm1 and *.ps1 files' {
|
||||
BeforeEach {
|
||||
$analysis = Invoke-ScriptAnalyzer -Path "$PSScriptRoot\..\$_" -Settings $lintSettings
|
||||
}
|
||||
It 'Should pass: <_>' -TestCases $scriptDir {
|
||||
$analysis | Should -HaveCount 0
|
||||
if ($analysis) {
|
||||
foreach ($result in $analysis) {
|
||||
switch -wildCard ($result.ScriptName) {
|
||||
'*.psm1' { $type = 'Module' }
|
||||
'*.ps1' { $type = 'Script' }
|
||||
'*.psd1' { $type = 'Manifest' }
|
||||
}
|
||||
Write-Warning " [*] $($result.Severity): $($result.Message)"
|
||||
Write-Warning " $($result.RuleName) in $type`: $directory\$($result.ScriptName):$($result.Line)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
. "$PSScriptRoot\Scoop-TestLib.ps1"
|
||||
. "$PSScriptRoot\..\lib\core.ps1"
|
||||
. "$PSScriptRoot\..\lib\help.ps1"
|
||||
. "$PSScriptRoot\..\libexec\scoop-alias.ps1" | Out-Null
|
||||
|
||||
Describe 'add_alias' -Tag 'Scoop' {
|
||||
Mock shimdir { "$env:TEMP\shims" }
|
||||
Mock set_config { }
|
||||
Mock get_config { @{} }
|
||||
|
||||
$shimdir = shimdir
|
||||
ensure $shimdir
|
||||
|
||||
Context "alias doesn't exist" {
|
||||
It 'creates a new alias' {
|
||||
$alias_file = "$shimdir\scoop-rm.ps1"
|
||||
$alias_file | Should -Not -Exist
|
||||
|
||||
add_alias 'rm' '"hello, world!"'
|
||||
& $alias_file | Should -Be 'hello, world!'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'alias exists' {
|
||||
It 'does not change existing alias' {
|
||||
$alias_file = "$shimdir\scoop-rm.ps1"
|
||||
New-Item $alias_file -Type File -Force
|
||||
$alias_file | Should -Exist
|
||||
|
||||
add_alias 'rm' 'test'
|
||||
$alias_file | Should -FileContentMatch ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Describe 'rm_alias' -Tag 'Scoop' {
|
||||
Mock shimdir { "$env:TEMP\shims" }
|
||||
Mock set_config { }
|
||||
Mock get_config { @{} }
|
||||
|
||||
$shimdir = shimdir
|
||||
ensure $shimdir
|
||||
|
||||
Context 'alias exists' {
|
||||
It 'removes an existing alias' {
|
||||
$alias_file = "$shimdir\scoop-rm.ps1"
|
||||
add_alias 'rm' '"hello, world!"'
|
||||
|
||||
$alias_file | Should -Exist
|
||||
Mock get_config { @(@{'rm' = 'scoop-rm' }) }
|
||||
|
||||
rm_alias 'rm'
|
||||
$alias_file | Should -Not -Exist
|
||||
}
|
||||
}
|
||||
}
|
||||
45
test/Scoop-Commands.Tests.ps1
Normal file
45
test/Scoop-Commands.Tests.ps1
Normal file
@@ -0,0 +1,45 @@
|
||||
BeforeAll {
|
||||
. "$PSScriptRoot\Scoop-TestLib.ps1"
|
||||
. "$PSScriptRoot\..\lib\core.ps1"
|
||||
. "$PSScriptRoot\..\lib\commands.ps1"
|
||||
}
|
||||
|
||||
Describe 'Manipulate Alias' -Tag 'Scoop' {
|
||||
BeforeAll {
|
||||
Mock shimdir { "$TestDrive\shims" }
|
||||
Mock set_config {}
|
||||
Mock get_config { @{} }
|
||||
|
||||
$shimdir = shimdir
|
||||
ensure $shimdir
|
||||
}
|
||||
|
||||
It 'Creates a new alias if it does not exist' {
|
||||
$alias_script = "$shimdir\scoop-rm.ps1"
|
||||
$alias_script | Should -Not -Exist
|
||||
|
||||
add_alias 'rm' '"hello, world!"'
|
||||
& $alias_script | Should -Be 'hello, world!'
|
||||
}
|
||||
|
||||
It 'Skips an existing alias' {
|
||||
$alias_script = "$shimdir\scoop-rm.ps1"
|
||||
Mock abort {}
|
||||
New-Item $alias_script -Type File -Force
|
||||
$alias_script | Should -Exist
|
||||
|
||||
add_alias 'rm' '"test"'
|
||||
Should -Invoke -CommandName abort -Times 1 -ParameterFilter { $msg -eq "File 'scoop-rm.ps1' already exists in shims directory." }
|
||||
}
|
||||
|
||||
It 'Removes an existing alias' {
|
||||
$alias_script = "$shimdir\scoop-rm.ps1"
|
||||
$alias_script | Should -Exist
|
||||
Mock get_config { @(@{'rm' = 'scoop-rm' }) }
|
||||
Mock info {}
|
||||
|
||||
rm_alias 'rm'
|
||||
$alias_script | Should -Not -Exist
|
||||
Should -Invoke -CommandName info -Times 1 -ParameterFilter { $msg -eq "Removing alias 'rm'..." }
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,16 @@
|
||||
. "$PSScriptRoot\Scoop-TestLib.ps1"
|
||||
. "$PSScriptRoot\..\lib\core.ps1"
|
||||
BeforeAll {
|
||||
. "$PSScriptRoot\Scoop-TestLib.ps1"
|
||||
. "$PSScriptRoot\..\lib\core.ps1"
|
||||
}
|
||||
|
||||
Describe 'config' -Tag 'Scoop' {
|
||||
BeforeAll {
|
||||
$configFile = "$env:TEMP\ScoopTestFixtures\config.json"
|
||||
if (Test-Path $configFile) {
|
||||
Remove-Item -Path $configFile -Force
|
||||
}
|
||||
$configFile = [IO.Path]::GetTempFileName()
|
||||
$unicode = [Regex]::Unescape('\u4f60\u597d\u3053\u3093\u306b\u3061\u306f') # 你好こんにちは
|
||||
}
|
||||
|
||||
BeforeEach {
|
||||
$scoopConfig = $null
|
||||
AfterAll {
|
||||
Remove-Item -Path $configFile -Force
|
||||
}
|
||||
|
||||
It 'load_cfg should return null if config file does not exist' {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user