Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7f5abc95ad | |||
| 3d1a73cf9e | |||
| d049d1a1e9 | |||
| 8b22c004ba | |||
| 1f2937b387 | |||
| 4823eb9082 | |||
| 39eda12a39 | |||
| f56c9bc371 | |||
| 586db93aa3 | |||
| ff10fbd0b5 | |||
| 756c6fd26b | |||
| 16e58bbbc2 | |||
| 8ffdf58d68 | |||
| 2abac29dff | |||
| 293da8859d | |||
| 111ef1fe44 | |||
| 97b285be5c | |||
| cc659a57f7 | |||
| f5cb86b53e | |||
| 207f1e9d51 | |||
| 750b029ef8 | |||
| 710fd5ec2e | |||
| d1315392a1 | |||
| cf894d9e82 | |||
| 1da4e08ed2 | |||
| 66546a8b17 | |||
| 3c3275ece5 | |||
| d43fbe6de6 | |||
| 16fd8be8be | |||
| 297e65ebe5 | |||
| a5c019419b | |||
| 815694dd8b | |||
| 083786795e | |||
| 284f4753e0 | |||
| 335e9160a6 |
@@ -0,0 +1,66 @@
|
||||
name: Default (not tags)
|
||||
|
||||
on:
|
||||
push:
|
||||
tags-ignore:
|
||||
- '**'
|
||||
|
||||
env:
|
||||
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
|
||||
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
|
||||
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
|
||||
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
|
||||
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
|
||||
NPMCI_URL_CLOUDLY: ${{secrets.NPMCI_URL_CLOUDLY}}
|
||||
|
||||
jobs:
|
||||
security:
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
container:
|
||||
image: ${{ env.IMAGE }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install pnpm and npmci
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @shipzone/npmci
|
||||
|
||||
- name: Run npm prepare
|
||||
run: npmci npm prepare
|
||||
|
||||
- name: Audit production dependencies
|
||||
run: |
|
||||
npmci command npm config set registry https://registry.npmjs.org
|
||||
npmci command pnpm audit --audit-level=high --prod
|
||||
continue-on-error: true
|
||||
|
||||
- name: Audit development dependencies
|
||||
run: |
|
||||
npmci command npm config set registry https://registry.npmjs.org
|
||||
npmci command pnpm audit --audit-level=high --dev
|
||||
continue-on-error: true
|
||||
|
||||
test:
|
||||
if: ${{ always() }}
|
||||
needs: security
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ${{ env.IMAGE }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Test stable
|
||||
run: |
|
||||
npmci node install stable
|
||||
npmci npm install
|
||||
npmci npm test
|
||||
|
||||
- name: Test build
|
||||
run: |
|
||||
npmci node install stable
|
||||
npmci npm install
|
||||
npmci npm build
|
||||
@@ -0,0 +1,124 @@
|
||||
name: Default (tags)
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
env:
|
||||
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
|
||||
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
|
||||
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
|
||||
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
|
||||
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
|
||||
NPMCI_URL_CLOUDLY: ${{secrets.NPMCI_URL_CLOUDLY}}
|
||||
|
||||
jobs:
|
||||
security:
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
container:
|
||||
image: ${{ env.IMAGE }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Prepare
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @shipzone/npmci
|
||||
npmci npm prepare
|
||||
|
||||
- name: Audit production dependencies
|
||||
run: |
|
||||
npmci command npm config set registry https://registry.npmjs.org
|
||||
npmci command pnpm audit --audit-level=high --prod
|
||||
continue-on-error: true
|
||||
|
||||
- name: Audit development dependencies
|
||||
run: |
|
||||
npmci command npm config set registry https://registry.npmjs.org
|
||||
npmci command pnpm audit --audit-level=high --dev
|
||||
continue-on-error: true
|
||||
|
||||
test:
|
||||
if: ${{ always() }}
|
||||
needs: security
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ${{ env.IMAGE }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Prepare
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @shipzone/npmci
|
||||
npmci npm prepare
|
||||
|
||||
- name: Test stable
|
||||
run: |
|
||||
npmci node install stable
|
||||
npmci npm install
|
||||
npmci npm test
|
||||
|
||||
- name: Test build
|
||||
run: |
|
||||
npmci node install stable
|
||||
npmci npm install
|
||||
npmci npm build
|
||||
|
||||
release:
|
||||
needs: test
|
||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ${{ env.IMAGE }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Prepare
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @shipzone/npmci
|
||||
npmci npm prepare
|
||||
|
||||
- name: Release
|
||||
run: |
|
||||
npmci node install stable
|
||||
npmci npm publish
|
||||
|
||||
metadata:
|
||||
needs: test
|
||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ${{ env.IMAGE }}
|
||||
continue-on-error: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Prepare
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @shipzone/npmci
|
||||
npmci npm prepare
|
||||
|
||||
- name: Code quality
|
||||
run: |
|
||||
npmci command npm install -g typescript
|
||||
npmci npm install
|
||||
|
||||
- name: Trigger
|
||||
run: npmci trigger
|
||||
|
||||
- name: Build docs and upload artifacts
|
||||
run: |
|
||||
npmci node install stable
|
||||
npmci npm install
|
||||
pnpm install -g @gitzone/tsdoc
|
||||
npmci command tsdoc
|
||||
continue-on-error: true
|
||||
+1
-3
@@ -15,8 +15,6 @@ node_modules/
|
||||
|
||||
# builds
|
||||
dist/
|
||||
dist_web/
|
||||
dist_serve/
|
||||
dist_ts_web/
|
||||
dist_*/
|
||||
|
||||
# custom
|
||||
-119
@@ -1,119 +0,0 @@
|
||||
# gitzone ci_default
|
||||
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
|
||||
|
||||
cache:
|
||||
paths:
|
||||
- .npmci_cache/
|
||||
key: "$CI_BUILD_STAGE"
|
||||
|
||||
stages:
|
||||
- security
|
||||
- test
|
||||
- release
|
||||
- metadata
|
||||
|
||||
# ====================
|
||||
# security stage
|
||||
# ====================
|
||||
mirror:
|
||||
stage: security
|
||||
script:
|
||||
- npmci git mirror
|
||||
tags:
|
||||
- docker
|
||||
- notpriv
|
||||
|
||||
snyk:
|
||||
stage: security
|
||||
script:
|
||||
- npmci npm prepare
|
||||
- npmci command npm install -g snyk
|
||||
- npmci command npm install --ignore-scripts
|
||||
- npmci command snyk test
|
||||
tags:
|
||||
- docker
|
||||
- notpriv
|
||||
|
||||
# ====================
|
||||
# test stage
|
||||
# ====================
|
||||
|
||||
testStable:
|
||||
stage: test
|
||||
script:
|
||||
- npmci npm prepare
|
||||
- npmci node install stable
|
||||
- npmci npm install
|
||||
- npmci npm test
|
||||
coverage: /\d+.?\d+?\%\s*coverage/
|
||||
tags:
|
||||
- docker
|
||||
- priv
|
||||
|
||||
testBuild:
|
||||
stage: test
|
||||
script:
|
||||
- npmci npm prepare
|
||||
- npmci node install lts
|
||||
- npmci npm install
|
||||
- npmci command npm run build
|
||||
coverage: /\d+.?\d+?\%\s*coverage/
|
||||
tags:
|
||||
- docker
|
||||
- notpriv
|
||||
|
||||
release:
|
||||
stage: release
|
||||
script:
|
||||
- npmci node install lts
|
||||
- npmci npm publish
|
||||
only:
|
||||
- tags
|
||||
tags:
|
||||
- docker
|
||||
- notpriv
|
||||
|
||||
# ====================
|
||||
# metadata stage
|
||||
# ====================
|
||||
codequality:
|
||||
stage: metadata
|
||||
allow_failure: true
|
||||
script:
|
||||
- npmci command npm install -g tslint typescript
|
||||
- npmci npm install
|
||||
- npmci command "tslint -c tslint.json ./ts/**/*.ts"
|
||||
tags:
|
||||
- docker
|
||||
- priv
|
||||
|
||||
trigger:
|
||||
stage: metadata
|
||||
script:
|
||||
- npmci trigger
|
||||
only:
|
||||
- tags
|
||||
tags:
|
||||
- docker
|
||||
- notpriv
|
||||
|
||||
pages:
|
||||
image: hosttoday/ht-docker-dbase:npmci
|
||||
services:
|
||||
- docker:stable-dind
|
||||
stage: metadata
|
||||
script:
|
||||
- npmci command npm install -g @gitzone/tsdoc
|
||||
- npmci npm prepare
|
||||
- npmci npm install
|
||||
- npmci command tsdoc
|
||||
tags:
|
||||
- docker
|
||||
- notpriv
|
||||
only:
|
||||
- tags
|
||||
artifacts:
|
||||
expire_in: 1 week
|
||||
paths:
|
||||
- public
|
||||
allow_failure: true
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"@git.zone/cli": {
|
||||
"schemaVersion": 2,
|
||||
"projectType": "npm",
|
||||
"module": {
|
||||
"githost": "code.foss.global",
|
||||
"gitscope": "push.rocks",
|
||||
"gitrepo": "smartupdate",
|
||||
"description": "A library designed to facilitate smarter update notifications and checking for Node.js projects.",
|
||||
"npmPackagename": "@push.rocks/smartupdate",
|
||||
"license": "MIT",
|
||||
"projectDomain": "push.rocks"
|
||||
},
|
||||
"release": {
|
||||
"targets": {
|
||||
"git": {
|
||||
"enabled": true,
|
||||
"remote": "origin"
|
||||
},
|
||||
"npm": {
|
||||
"enabled": false,
|
||||
"registries": [
|
||||
"https://verdaccio.lossless.digital",
|
||||
"https://registry.npmjs.org"
|
||||
],
|
||||
"accessLevel": "public"
|
||||
},
|
||||
"docker": {
|
||||
"enabled": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@git.zone/tsdoc": {
|
||||
"legal": "\n## License and Legal Information\n\nThis repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository. \n\n**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.\n\n### Trademarks\n\nThis project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.\n\n### Company Information\n\nTask Venture Capital GmbH \nRegistered at District court Bremen HRB 35230 HB, Germany\n\nFor any legal inquiries or if you require further information, please contact us via email at hello@task.vc.\n\nBy using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.\n"
|
||||
},
|
||||
"@ship.zone/szci": {
|
||||
"npmGlobalTools": [],
|
||||
"npmRegistryUrl": "registry.npmjs.org"
|
||||
}
|
||||
}
|
||||
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"command": "npm test",
|
||||
"name": "Run npm test",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
+26
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"json.schemas": [
|
||||
{
|
||||
"fileMatch": ["/npmextra.json"],
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"npmci": {
|
||||
"type": "object",
|
||||
"description": "settings for npmci"
|
||||
},
|
||||
"gitzone": {
|
||||
"type": "object",
|
||||
"description": "settings for gitzone",
|
||||
"properties": {
|
||||
"projectType": {
|
||||
"type": "string",
|
||||
"enum": ["website", "element", "service", "npm", "wcc"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
# Changelog
|
||||
|
||||
## Pending
|
||||
|
||||
|
||||
## 2026-05-10 - 2.0.8
|
||||
|
||||
### Fixes
|
||||
|
||||
- improve update check caching, validation, and error handling (smartupdate)
|
||||
- cache both update-available and up-to-date results with package version and registry context to avoid stale or incorrect cache reuse
|
||||
- return cached CLI update results correctly for time-based checks and handle changelog opening failures without unhandled rejections
|
||||
- validate package names and versions before registry access, preserve specific error types, and add deterministic cache-focused tests
|
||||
- add configurable cache store options and remove the unused smarttime dependency
|
||||
|
||||
## 2026-05-01 - 2.0.7 - fix(build)
|
||||
modernize build configuration and tighten TypeScript typings
|
||||
|
||||
- updates build and test tooling dependencies and simplifies package scripts
|
||||
- adds strict TypeScript settings and replaces any-based access with typed APIs in cache manager and notifier
|
||||
- adds project metadata files to the published package and aligns npmextra configuration with namespaced tool settings
|
||||
|
||||
## 2025-11-03 - 2.0.6 - maintenance
|
||||
Summarized maintenance updates across the 2.0.6 release line.
|
||||
|
||||
- Refined project description
|
||||
- Updated TypeScript configuration
|
||||
- Updated npmextra git host metadata
|
||||
- Included additional minor maintenance-only updates with no user-facing impact
|
||||
|
||||
## 2023-07-26 - 2.0.5 - core
|
||||
Delivered a small core fix release.
|
||||
|
||||
- Updated core behavior with a minor fix
|
||||
|
||||
## 2022-06-01 - 2.0.4 - core
|
||||
Delivered a small core fix release covering versions 2.0.3 to 2.0.4.
|
||||
|
||||
- Applied minor core update fixes across the release line
|
||||
|
||||
## 2022-04-13 - 2.0.2 - core
|
||||
Delivered a small core fix release.
|
||||
|
||||
- Updated core behavior with a minor fix
|
||||
|
||||
## 2022-04-13 - 2.0.0 - release line
|
||||
Published the initial 2.x release line with follow-up maintenance updates through 2.0.1.
|
||||
|
||||
- Released version 2.0.0
|
||||
- Included minor non-descriptive maintenance updates in 2.0.1
|
||||
|
||||
## 2022-04-13 - 1.0.31 - core
|
||||
Introduced a breaking change in the core module.
|
||||
|
||||
- BREAKING CHANGE: switched the package to ESM
|
||||
|
||||
## 2020-06-25 - 1.0.28 - core
|
||||
Summarized a sequence of patch releases from 1.0.28 to 1.0.30.
|
||||
|
||||
- Applied repeated minor core fixes across patch releases 1.0.28 through 1.0.30
|
||||
|
||||
## 2019-09-16 - 1.0.22 - core
|
||||
Summarized patch activity from 1.0.22 to 1.0.28.
|
||||
|
||||
- Applied repeated minor core fixes across versions 1.0.22 through 1.0.28
|
||||
- Included an unspecified maintenance update in 1.0.21
|
||||
|
||||
## 2018-09-02 - 1.0.18 - dependencies
|
||||
Delivered maintenance improvements across versions 1.0.18 to 1.0.20.
|
||||
|
||||
- Updated dependencies to their latest compatible versions
|
||||
- Refreshed CI-related configuration
|
||||
- Included a general maintenance update in 1.0.21
|
||||
|
||||
## 2018-05-22 - 1.0.16 - package
|
||||
Updated package naming and scoped dependencies across versions 1.0.15 to 1.0.16.
|
||||
|
||||
- Switched package naming to the `@pushrocks` scope
|
||||
- Updated `smartopen` dependency to `@pushrocks/smartopen`
|
||||
|
||||
## 2018-02-14 - 1.0.13 - resilience
|
||||
Improved runtime resilience and project maintenance through versions 1.0.13 to 1.0.14.
|
||||
|
||||
- Improved resilience against network failures
|
||||
- Updated CI configuration
|
||||
- Removed `typings-global`
|
||||
|
||||
## 2017-10-07 - 1.0.12 - maintenance
|
||||
Applied maintenance updates across versions 1.0.10 to 1.0.12.
|
||||
|
||||
- Renamed npmextra metadata
|
||||
- Improved operation when `npms.io` is unavailable
|
||||
- Updated CI configuration
|
||||
- Improved smartupdate resilience
|
||||
|
||||
## 2017-08-20 - 1.0.6 - features
|
||||
Delivered multiple early improvements across versions 1.0.1 to 1.0.10.
|
||||
|
||||
- Added initial working version and project documentation
|
||||
- Improved console logs and wording
|
||||
- Added `smartopen`
|
||||
- Improved update information output
|
||||
- Fixed a bug that reset the timer
|
||||
- Updated dependencies
|
||||
- Included compile and maintenance-related updates
|
||||
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2026 Task Venture Capital GmbH
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+35
-14
@@ -1,19 +1,40 @@
|
||||
{
|
||||
"npmci": {
|
||||
"npmGlobalTools": [],
|
||||
"npmAccessLevel": "public"
|
||||
},
|
||||
"npmts": {
|
||||
"coverageTreshold": 50
|
||||
},
|
||||
"gitzone": {
|
||||
"@git.zone/cli": {
|
||||
"projectType": "npm",
|
||||
"module": {
|
||||
"githost": "gitlab.com",
|
||||
"gitscope": "pushrocks",
|
||||
"githost": "code.foss.global",
|
||||
"gitscope": "push.rocks",
|
||||
"gitrepo": "smartupdate",
|
||||
"shortDescription": "update your tools in a smart way",
|
||||
"npmPackagename": "@pushrocks/smartupdate",
|
||||
"license": "MIT"
|
||||
"description": "A library designed to facilitate smarter update notifications and checking for Node.js projects.",
|
||||
"npmPackagename": "@push.rocks/smartupdate",
|
||||
"license": "MIT",
|
||||
"projectDomain": "push.rocks",
|
||||
"keywords": [
|
||||
"update notifications",
|
||||
"version checking",
|
||||
"npm package updates",
|
||||
"version comparison",
|
||||
"CLI tool updates",
|
||||
"dependency management",
|
||||
"npm registry",
|
||||
"typescript utilities",
|
||||
"software maintenance",
|
||||
"open-source contribution"
|
||||
]
|
||||
},
|
||||
"release": {
|
||||
"registries": [
|
||||
"https://verdaccio.lossless.digital",
|
||||
"https://registry.npmjs.org"
|
||||
],
|
||||
"accessLevel": "public"
|
||||
}
|
||||
},
|
||||
"@git.zone/tsdoc": {
|
||||
"legal": "\n## License and Legal Information\n\nThis repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository. \n\n**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.\n\n### Trademarks\n\nThis project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.\n\n### Company Information\n\nTask Venture Capital GmbH \nRegistered at District court Bremen HRB 35230 HB, Germany\n\nFor any legal inquiries or if you require further information, please contact us via email at hello@task.vc.\n\nBy using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.\n"
|
||||
},
|
||||
"@ship.zone/szci": {
|
||||
"npmGlobalTools": [],
|
||||
"npmRegistryUrl": "registry.npmjs.org"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Generated
-2016
File diff suppressed because it is too large
Load Diff
+50
-28
@@ -1,43 +1,65 @@
|
||||
{
|
||||
"name": "@pushrocks/smartupdate",
|
||||
"version": "1.0.26",
|
||||
"name": "@push.rocks/smartupdate",
|
||||
"version": "2.0.8",
|
||||
"private": false,
|
||||
"description": "update your tools in a smart way",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/index.d.ts",
|
||||
"description": "A library designed to facilitate smarter update notifications and checking for Node.js projects.",
|
||||
"main": "dist_ts/index.js",
|
||||
"typings": "dist_ts/index.d.ts",
|
||||
"type": "module",
|
||||
"author": "Lossless GmbH",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"test": "(tstest test/)",
|
||||
"build": "(tsbuild)"
|
||||
"test": "tstest test/ --verbose",
|
||||
"build": "tsbuild --web",
|
||||
"buildDocs": "tsdoc"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@gitzone/tsbuild": "^2.1.17",
|
||||
"@gitzone/tsrun": "^1.2.8",
|
||||
"@gitzone/tstest": "^1.0.24",
|
||||
"@pushrocks/tapbundle": "^3.0.13",
|
||||
"@types/node": "^12.7.4",
|
||||
"tslint": "^5.19.0",
|
||||
"tslint-config-prettier": "^1.18.0"
|
||||
"@git.zone/tsbuild": "^4.4.0",
|
||||
"@git.zone/tsrun": "^2.0.3",
|
||||
"@git.zone/tstest": "^3.6.3",
|
||||
"@types/lodash.clonedeep": "^4.5.9",
|
||||
"@types/node": "^25.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@pushrocks/consolecolor": "^2.0.1",
|
||||
"@pushrocks/npmextra": "^3.0.5",
|
||||
"@pushrocks/smartlog": "^2.0.19",
|
||||
"@pushrocks/smartnpm": "^1.0.12",
|
||||
"@pushrocks/smartopen": "^1.0.22",
|
||||
"@pushrocks/smarttime": "^3.0.12",
|
||||
"@pushrocks/smartversion": "^2.0.0"
|
||||
"@push.rocks/consolecolor": "^2.0.3",
|
||||
"@push.rocks/npmextra": "^5.3.3",
|
||||
"@push.rocks/smartnpm": "^2.0.6",
|
||||
"@push.rocks/smartopen": "^2.0.0",
|
||||
"@push.rocks/smartversion": "^3.1.0"
|
||||
},
|
||||
"files": [
|
||||
"ts/*",
|
||||
"ts_web/*",
|
||||
"dist/*",
|
||||
"dist_web/*",
|
||||
"dist_ts_web/*",
|
||||
"assets/*",
|
||||
"ts/**/*",
|
||||
"ts_web/**/*",
|
||||
"dist/**/*",
|
||||
"dist_*/**/*",
|
||||
"dist_ts/**/*",
|
||||
"dist_ts_web/**/*",
|
||||
"assets/**/*",
|
||||
"cli.js",
|
||||
".smartconfig.json",
|
||||
"license",
|
||||
"npmextra.json",
|
||||
"readme.md"
|
||||
]
|
||||
],
|
||||
"browserslist": [
|
||||
"last 1 chrome versions"
|
||||
],
|
||||
"keywords": [
|
||||
"update notifications",
|
||||
"version checking",
|
||||
"npm package updates",
|
||||
"version comparison",
|
||||
"CLI tool updates",
|
||||
"dependency management",
|
||||
"npm registry",
|
||||
"typescript utilities",
|
||||
"software maintenance",
|
||||
"open-source contribution"
|
||||
],
|
||||
"homepage": "https://code.foss.global/push.rocks/smartupdate",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://code.foss.global/push.rocks/smartupdate.git"
|
||||
},
|
||||
"packageManager": "pnpm@10.28.2"
|
||||
}
|
||||
|
||||
Generated
+8033
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
|
||||
@@ -1,49 +1,99 @@
|
||||
# @pushrocks/smartupdate
|
||||
# @push.rocks/smartupdate
|
||||
update your tools in a smart way
|
||||
|
||||
## Availabililty and Links
|
||||
* [npmjs.org (npm package)](https://www.npmjs.com/package/@pushrocks/smartupdate)
|
||||
* [gitlab.com (source)](https://gitlab.com/pushrocks/smartupdate)
|
||||
* [github.com (source mirror)](https://github.com/pushrocks/smartupdate)
|
||||
* [docs (typedoc)](https://pushrocks.gitlab.io/smartupdate/)
|
||||
## Install
|
||||
To get started with `@push.rocks/smartupdate`, you need to install it via npm. Run the following command in your terminal:
|
||||
|
||||
## Status for master
|
||||
[](https://gitlab.com/pushrocks/smartupdate/commits/master)
|
||||
[](https://gitlab.com/pushrocks/smartupdate/commits/master)
|
||||
[](https://www.npmjs.com/package/@pushrocks/smartupdate)
|
||||
[](https://snyk.io/test/npm/@pushrocks/smartupdate)
|
||||
[](https://nodejs.org/dist/latest-v10.x/docs/api/)
|
||||
[](https://nodejs.org/dist/latest-v10.x/docs/api/)
|
||||
[](https://prettier.io/)
|
||||
```bash
|
||||
npm install @push.rocks/smartupdate --save
|
||||
```
|
||||
|
||||
This will add `@push.rocks/smartupdate` as a dependency to your project and download it to your `node_modules` folder.
|
||||
|
||||
## Usage
|
||||
|
||||
Use TypeScript for best in class instellisense.
|
||||
The `@push.rocks/smartupdate` module offers a smart way to notify users of your CLI tools or libraries about new versions available on npm. Let's take a closer look at how you can utilize it in your project using TypeScript.
|
||||
|
||||
smartupdate makes it really easy to notify your tool users about new versions:
|
||||
### Importing and Instantiating `SmartUpdate`
|
||||
|
||||
First, you need to import the `SmartUpdate` class from the `@push.rocks/smartupdate` package and create an instance of it. You can optionally pass settings to the constructor to customize its behavior:
|
||||
|
||||
```typescript
|
||||
import * as smartupdate from 'smartupdate';
|
||||
import { SmartUpdate } from '@push.rocks/smartupdate';
|
||||
|
||||
// the following command will check npm for a version newer than the specified one.
|
||||
// It will open the specified URL if a newer version is actually found.
|
||||
await smartupdate.standardHandler.check(
|
||||
'lodash',
|
||||
'1.0.5',
|
||||
'http://gitzone.gitlab.io/npmts/changelog.html'
|
||||
);
|
||||
const smartUpdate = new SmartUpdate();
|
||||
```
|
||||
|
||||
For further information read the linked docs at the top of this README.
|
||||
### Checking for Updates
|
||||
|
||||
> MIT licensed | **©** [Lossless GmbH](https://lossless.gmbh)
|
||||
> | By using this npm module you agree to our [privacy policy](https://lossless.gmbH/privacy.html)
|
||||
To check for an update for a specific npm package, you can use the `check` method. This method requires the npm package name, the current local version of the package you are checking against, and optionally, a URL to the changelog. If a newer version is found, it will log a message to the console and, if a changelog URL is provided and not running in a CI environment, attempt to open the changelog in the default web browser.
|
||||
|
||||
[](https://push.rocks)
|
||||
Here’s an example:
|
||||
|
||||
For further information read the linked docs at the top of this readme.
|
||||
```typescript
|
||||
// Async function to demonstrate usage
|
||||
async function checkForUpdates() {
|
||||
const npmPackageName = 'some-npm-package';
|
||||
const currentVersion = '1.0.0';
|
||||
const changelogUrl = 'https://example.com/changelog';
|
||||
|
||||
> MIT licensed | **©** [Lossless GmbH](https://lossless.gmbh)
|
||||
| By using this npm module you agree to our [privacy policy](https://lossless.gmbH/privacy)
|
||||
// Check for an update for 'some-npm-package'
|
||||
const hasNewerVersion = await smartUpdate.check(npmPackageName, currentVersion, changelogUrl);
|
||||
|
||||
[](https://maintainedby.lossless.com)
|
||||
if (hasNewerVersion) {
|
||||
console.log('A newer version is available. Please consider updating.');
|
||||
} else {
|
||||
console.log('You are using the latest version.');
|
||||
}
|
||||
}
|
||||
|
||||
// Execute the function
|
||||
checkForUpdates().catch(console.error);
|
||||
```
|
||||
|
||||
### Checking for CLI Updates
|
||||
|
||||
For CLI applications, `@push.rocks/smartupdate` offers a more tailored method called `checkForCli`. This method works similarly to `check` but is optimized for CLI tools, taking into account factors like avoiding frequent checks (e.g., not more than once per hour).
|
||||
|
||||
Example usage:
|
||||
|
||||
```typescript
|
||||
// Async function to demonstrate CLI update checks
|
||||
async function checkCliUpdates() {
|
||||
const cliPackageName = 'your-cli-tool';
|
||||
const currentCliVersion = '0.1.0';
|
||||
const changelogUrl = 'https://example.com/cli-changelog';
|
||||
|
||||
const needsUpdate = await smartUpdate.checkForCli(cliPackageName, currentCliVersion, changelogUrl);
|
||||
|
||||
if (needsUpdate) {
|
||||
console.log(`A newer version of ${cliPackageName} is available. Visit ${changelogUrl} for more information.`);
|
||||
} else {
|
||||
console.log(`You are using the latest version of ${cliPackageName}.`);
|
||||
}
|
||||
}
|
||||
|
||||
// Run the CLI update check
|
||||
checkCliUpdates().catch(console.error);
|
||||
```
|
||||
|
||||
By integrating `@push.rocks/smartupdate` into your project, you ensure users are always informed about the latest updates, encouraging them to keep their installations current and benefiting from new features, performance improvements, and bug fixes.
|
||||
|
||||
## License and Legal Information
|
||||
|
||||
This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository.
|
||||
|
||||
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
|
||||
|
||||
### Trademarks
|
||||
|
||||
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.
|
||||
|
||||
### Company Information
|
||||
|
||||
Task Venture Capital GmbH
|
||||
Registered at District court Bremen HRB 35230 HB, Germany
|
||||
|
||||
For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
|
||||
|
||||
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# smartupdate improvement plan
|
||||
|
||||
Status: implemented.
|
||||
|
||||
## Goal
|
||||
|
||||
Make update checks predictable, cache-safe, and testable without changing the core public API shape.
|
||||
|
||||
## Implementation
|
||||
|
||||
- Fix CLI cache behavior so cached update information still reports an available update and time-based cache entries expire.
|
||||
- Cache both `update-available` and `up-to-date` results so normal repeated CLI runs do not keep hitting the registry.
|
||||
- Store cache context with each entry and only reuse cache entries for the same package, current version, and registry.
|
||||
- Preserve more useful error types for invalid package names, invalid versions, package lookup failures, and registry failures.
|
||||
- Await and catch changelog opening so browser failures do not become unhandled promise rejections.
|
||||
- Replace live npm tests with deterministic registry stubs and ephemeral cache storage.
|
||||
- Remove unused dependency surface.
|
||||
|
||||
## Verification
|
||||
|
||||
- Run `pnpm test`.
|
||||
- Run `pnpm run build`.
|
||||
@@ -0,0 +1,214 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import type * as smartnpm from '@push.rocks/smartnpm';
|
||||
import * as smartupdate from '../ts/index.js';
|
||||
|
||||
const createMockedSmartUpdate = (versionsArg: Record<string, string>) => {
|
||||
const checkedPackages: string[] = [];
|
||||
const testSmartUpdate = new smartupdate.SmartUpdate({
|
||||
logLevel: 'SILENT',
|
||||
noColor: true,
|
||||
cacheDuration: { minutes: 30 },
|
||||
cacheStore: { storeType: 'ephemeral' },
|
||||
});
|
||||
|
||||
testSmartUpdate.npmRegistry.getPackageInfo = async (packageNameArg: string) => {
|
||||
checkedPackages.push(packageNameArg);
|
||||
const version = versionsArg[packageNameArg];
|
||||
if (!version) {
|
||||
throw new Error(`Package not found: ${packageNameArg}`);
|
||||
}
|
||||
return {
|
||||
name: packageNameArg,
|
||||
version,
|
||||
} as unknown as smartnpm.NpmPackage;
|
||||
};
|
||||
|
||||
return { testSmartUpdate, checkedPackages };
|
||||
};
|
||||
|
||||
tap.test('backward compatibility: should create an instance of SmartUpdate', async () => {
|
||||
const { testSmartUpdate } = createMockedSmartUpdate({ lodash: '4.17.21' });
|
||||
expect(testSmartUpdate).toBeInstanceOf(smartupdate.SmartUpdate);
|
||||
});
|
||||
|
||||
tap.test('backward compatibility: should check for a npm module using old API', async () => {
|
||||
const { testSmartUpdate } = createMockedSmartUpdate({ lodash: '4.17.21' });
|
||||
const result = await testSmartUpdate.check('lodash', '1.0.5');
|
||||
expect(result).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('modern API: checkForUpdate should return rich result object', async () => {
|
||||
const { testSmartUpdate } = createMockedSmartUpdate({ lodash: '4.17.21' });
|
||||
const result = await testSmartUpdate.checkForUpdate({
|
||||
packageName: 'lodash',
|
||||
currentVersion: '1.0.0',
|
||||
cacheStrategy: 'never',
|
||||
});
|
||||
|
||||
expect(result).toBeTypeOf('object');
|
||||
expect(result.status).toEqual('update-available');
|
||||
expect(result.packageName).toEqual('lodash');
|
||||
expect(result.currentVersion).toEqual('1.0.0');
|
||||
expect(result.latestVersion).toEqual('4.17.21');
|
||||
expect(result.checkTime).toBeInstanceOf(Date);
|
||||
expect(result.cacheHit).toBeFalse();
|
||||
});
|
||||
|
||||
tap.test('modern API: checkForUpdate with up-to-date version', async () => {
|
||||
const { testSmartUpdate } = createMockedSmartUpdate({ '@push.rocks/smartversion': '3.1.0' });
|
||||
const result = await testSmartUpdate.checkForUpdate({
|
||||
packageName: '@push.rocks/smartversion',
|
||||
currentVersion: '999.999.999',
|
||||
cacheStrategy: 'never',
|
||||
});
|
||||
|
||||
expect(result.status).toEqual('up-to-date');
|
||||
expect(result.latestVersion).toEqual('3.1.0');
|
||||
});
|
||||
|
||||
tap.test('modern API: getLatestVersion utility method', async () => {
|
||||
const { testSmartUpdate } = createMockedSmartUpdate({ lodash: '4.17.21' });
|
||||
const latestVersion = await testSmartUpdate.getLatestVersion('lodash');
|
||||
expect(latestVersion).toEqual('4.17.21');
|
||||
});
|
||||
|
||||
tap.test('modern API: error handling for non-existent package', async () => {
|
||||
const { testSmartUpdate } = createMockedSmartUpdate({});
|
||||
const result = await testSmartUpdate.checkForUpdate({
|
||||
packageName: 'this-package-definitely-does-not-exist-12345',
|
||||
currentVersion: '1.0.0',
|
||||
cacheStrategy: 'never',
|
||||
});
|
||||
|
||||
expect(result.status).toEqual('error');
|
||||
expect(result.error).toBeInstanceOf(smartupdate.PackageNotFoundError);
|
||||
});
|
||||
|
||||
tap.test('modern API: invalid versions are reported before registry access', async () => {
|
||||
const { testSmartUpdate, checkedPackages } = createMockedSmartUpdate({ lodash: '4.17.21' });
|
||||
const result = await testSmartUpdate.checkForUpdate({
|
||||
packageName: 'lodash',
|
||||
currentVersion: 'not-a-version',
|
||||
cacheStrategy: 'never',
|
||||
});
|
||||
|
||||
expect(result.status).toEqual('error');
|
||||
expect(result.error).toBeInstanceOf(smartupdate.InvalidVersionError);
|
||||
expect(checkedPackages).toHaveLength(0);
|
||||
});
|
||||
|
||||
tap.test('modern API: invalid package names are reported before registry access', async () => {
|
||||
const { testSmartUpdate, checkedPackages } = createMockedSmartUpdate({ lodash: '4.17.21' });
|
||||
const result = await testSmartUpdate.checkForUpdate({
|
||||
packageName: 'invalid package name',
|
||||
currentVersion: '1.0.0',
|
||||
cacheStrategy: 'never',
|
||||
});
|
||||
|
||||
expect(result.status).toEqual('error');
|
||||
expect(result.error).toBeInstanceOf(smartupdate.InvalidPackageNameError);
|
||||
expect(checkedPackages).toHaveLength(0);
|
||||
});
|
||||
|
||||
tap.test('modern API: caches update-available results for time-based checks', async () => {
|
||||
const { testSmartUpdate, checkedPackages } = createMockedSmartUpdate({ express: '5.0.0' });
|
||||
|
||||
const result1 = await testSmartUpdate.checkForUpdate({
|
||||
packageName: 'express',
|
||||
currentVersion: '1.0.0',
|
||||
cacheStrategy: 'time-based',
|
||||
});
|
||||
const result2 = await testSmartUpdate.checkForUpdate({
|
||||
packageName: 'express',
|
||||
currentVersion: '1.0.0',
|
||||
cacheStrategy: 'time-based',
|
||||
});
|
||||
|
||||
expect(result1.status).toEqual('update-available');
|
||||
expect(result2.status).toEqual('check-skipped');
|
||||
expect(result2.cacheHit).toBeTrue();
|
||||
expect(result2.latestVersion).toEqual('5.0.0');
|
||||
expect(checkedPackages).toHaveLength(1);
|
||||
});
|
||||
|
||||
tap.test('modern API: caches up-to-date results for time-based checks', async () => {
|
||||
const { testSmartUpdate, checkedPackages } = createMockedSmartUpdate({ express: '5.0.0' });
|
||||
|
||||
const result1 = await testSmartUpdate.checkForUpdate({
|
||||
packageName: 'express',
|
||||
currentVersion: '5.0.0',
|
||||
cacheStrategy: 'time-based',
|
||||
});
|
||||
const result2 = await testSmartUpdate.checkForUpdate({
|
||||
packageName: 'express',
|
||||
currentVersion: '5.0.0',
|
||||
cacheStrategy: 'time-based',
|
||||
});
|
||||
|
||||
expect(result1.status).toEqual('up-to-date');
|
||||
expect(result2.status).toEqual('check-skipped');
|
||||
expect(result2.cacheHit).toBeTrue();
|
||||
expect(result2.latestVersion).toEqual('5.0.0');
|
||||
expect(checkedPackages).toHaveLength(1);
|
||||
});
|
||||
|
||||
tap.test('modern API: cache entries are scoped to the checked current version', async () => {
|
||||
const { testSmartUpdate, checkedPackages } = createMockedSmartUpdate({ express: '5.0.0' });
|
||||
|
||||
const result1 = await testSmartUpdate.checkForUpdate({
|
||||
packageName: 'express',
|
||||
currentVersion: '1.0.0',
|
||||
cacheStrategy: 'time-based',
|
||||
});
|
||||
const result2 = await testSmartUpdate.checkForUpdate({
|
||||
packageName: 'express',
|
||||
currentVersion: '5.0.0',
|
||||
cacheStrategy: 'time-based',
|
||||
});
|
||||
|
||||
expect(result1.status).toEqual('update-available');
|
||||
expect(result2.status).toEqual('up-to-date');
|
||||
expect(result2.cacheHit).toBeFalse();
|
||||
expect(checkedPackages).toHaveLength(2);
|
||||
});
|
||||
|
||||
tap.test('modern API: checkForCli returns true for cached known updates', async () => {
|
||||
const { testSmartUpdate, checkedPackages } = createMockedSmartUpdate({ 'my-cli': '2.0.0' });
|
||||
|
||||
const result1 = await testSmartUpdate.checkForCli('my-cli', '1.0.0');
|
||||
const result2 = await testSmartUpdate.checkForCli('my-cli', '1.0.0');
|
||||
|
||||
expect(result1).toBeTrue();
|
||||
expect(result2).toBeTrue();
|
||||
expect(checkedPackages).toHaveLength(1);
|
||||
});
|
||||
|
||||
tap.test('cache manager: clears package-specific and complete cache data', async () => {
|
||||
const cacheManager = new smartupdate.UpdateCacheManager({
|
||||
durationMs: 60_000,
|
||||
storeType: 'ephemeral',
|
||||
});
|
||||
|
||||
await cacheManager.setCached('one', cacheManager.createCacheStatus('1.0.0'));
|
||||
await cacheManager.setCached('two', cacheManager.createCacheStatus('2.0.0'));
|
||||
|
||||
await cacheManager.clearCache('one');
|
||||
expect(await cacheManager.getCached('one')).toBeNull();
|
||||
expect(await cacheManager.getCached('two')).toBeTypeOf('object');
|
||||
|
||||
await cacheManager.clearCache();
|
||||
expect(await cacheManager.getCached('two')).toBeNull();
|
||||
});
|
||||
|
||||
tap.test('modern API: exports all runtime classes and constants', async () => {
|
||||
expect(smartupdate.SmartUpdate).toBeTypeOf('function');
|
||||
expect(smartupdate.UpdateCacheManager).toBeTypeOf('function');
|
||||
expect(smartupdate.UpdateNotifier).toBeTypeOf('function');
|
||||
expect(smartupdate.RegistryUnavailableError).toBeTypeOf('function');
|
||||
expect(smartupdate.PackageNotFoundError).toBeTypeOf('function');
|
||||
expect(smartupdate.InvalidVersionError).toBeTypeOf('function');
|
||||
expect(smartupdate.InvalidPackageNameError).toBeTypeOf('function');
|
||||
expect(smartupdate.LOG_LEVELS).toBeTypeOf('object');
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -1,8 +0,0 @@
|
||||
import { expect, tap } from '@pushrocks/tapbundle';
|
||||
import * as smartupdate from '../ts/index';
|
||||
|
||||
tap.test('should check for a npm module', async () => {
|
||||
await smartupdate.standardHandler.check('lodash', '1.0.5');
|
||||
});
|
||||
|
||||
tap.start();
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* autocreated commitinfo by @push.rocks/commitinfo
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@push.rocks/smartupdate',
|
||||
version: '2.0.8',
|
||||
description: 'A library designed to facilitate smarter update notifications and checking for Node.js projects.'
|
||||
}
|
||||
+28
-100
@@ -1,104 +1,32 @@
|
||||
import * as plugins from './smartupdate.plugins';
|
||||
// Main class
|
||||
export { SmartUpdate } from './smartupdate.classes.smartupdate.js';
|
||||
|
||||
import { TimeStamp } from '@pushrocks/smarttime';
|
||||
// Supporting classes (for advanced use cases)
|
||||
export { UpdateCacheManager } from './smartupdate.classes.cachemanager.js';
|
||||
export { UpdateNotifier } from './smartupdate.classes.notifier.js';
|
||||
|
||||
interface ICacheStatus {
|
||||
lastCheck: number;
|
||||
latestVersion: string;
|
||||
performedUpgrade: boolean;
|
||||
}
|
||||
// Interfaces and types
|
||||
export type {
|
||||
ISmartUpdateOptions,
|
||||
IUpdateCheckOptions,
|
||||
IUpdateCheckResult,
|
||||
ICacheStatus,
|
||||
ICacheOptions,
|
||||
INotificationOptions,
|
||||
TCacheStrategy,
|
||||
TCacheStoreType,
|
||||
TCachedUpdateStatus,
|
||||
} from './smartupdate.interfaces.js';
|
||||
|
||||
import { KeyValueStore } from '@pushrocks/npmextra';
|
||||
// Error classes
|
||||
export {
|
||||
SmartUpdateError,
|
||||
RegistryUnavailableError,
|
||||
PackageNotFoundError,
|
||||
InvalidVersionError,
|
||||
InvalidPackageNameError,
|
||||
} from './smartupdate.errors.js';
|
||||
|
||||
export class SmartUpdate {
|
||||
public kvStore = new plugins.npmextra.KeyValueStore('custom', 'global_smartupdate');
|
||||
|
||||
public async check(npmnameArg: string, compareVersion: string, changelogUrlArg?: string) {
|
||||
// the newData to write
|
||||
const timeStamp = new TimeStamp();
|
||||
const newData = {
|
||||
lastCheck: timeStamp.milliSeconds,
|
||||
latestVersion: 'x.x.x',
|
||||
performedUpgrade: false
|
||||
};
|
||||
|
||||
// the comparison data from the keyValue store
|
||||
const result: ICacheStatus = await this.kvStore.readKey(npmnameArg);
|
||||
|
||||
if (result) {
|
||||
const lastCheckTimeStamp = TimeStamp.fromMilliSeconds(result.lastCheck);
|
||||
const tresholdTime = plugins.smarttime.getMilliSecondsFromUnits({ hours: 1 });
|
||||
if (!lastCheckTimeStamp.isOlderThan(timeStamp, tresholdTime)) {
|
||||
newData.lastCheck = lastCheckTimeStamp.milliSeconds;
|
||||
const nextCheckInMinutes =
|
||||
(tresholdTime - (timeStamp.milliSeconds - lastCheckTimeStamp.milliSeconds)) / 60000;
|
||||
console.log(
|
||||
`next update check in less than ${Math.floor(nextCheckInMinutes) + 1} minute(s): ` +
|
||||
`${plugins.consolecolor.coloredString(
|
||||
`${npmnameArg} has already been checked within the last hour.`,
|
||||
'pink'
|
||||
)}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
const npmPackage = await this.getNpmPackageFromRegistry(npmnameArg);
|
||||
if (!npmPackage) {
|
||||
plugins.smartlog.defaultLogger.log('warn', 'failed to retrieve package information...');
|
||||
plugins.smartlog.defaultLogger.log('info', 'npms.io might be down');
|
||||
return;
|
||||
}
|
||||
newData.latestVersion = npmPackage.version;
|
||||
const upgradeBool = await this.checkIfUpgrade(npmPackage, compareVersion, changelogUrlArg);
|
||||
if (upgradeBool) {
|
||||
// TODO:
|
||||
}
|
||||
this.kvStore.writeKey(npmnameArg, newData);
|
||||
}
|
||||
|
||||
private async getNpmPackageFromRegistry(npmnameArg): Promise<plugins.smartnpm.NpmPackage> {
|
||||
console.log(
|
||||
`smartupdate: checking for newer version of ${plugins.consolecolor.coloredString(
|
||||
npmnameArg,
|
||||
'pink'
|
||||
)}...`
|
||||
);
|
||||
const npmRegistry = new plugins.smartnpm.NpmRegistry();
|
||||
const npmPackage = npmRegistry.getPackageInfo(npmnameArg);
|
||||
return npmPackage;
|
||||
}
|
||||
|
||||
private async checkIfUpgrade(
|
||||
npmPackage: plugins.smartnpm.NpmPackage,
|
||||
localVersionStringArg: string,
|
||||
changelogUrlArg?: string
|
||||
) {
|
||||
// create Version objects
|
||||
const versionNpm = new plugins.smartversion.SmartVersion(npmPackage.version);
|
||||
const versionLocal = new plugins.smartversion.SmartVersion(localVersionStringArg);
|
||||
if (!versionNpm.greaterThan(versionLocal)) {
|
||||
console.log(
|
||||
`smartupdate: You are running the latest version of ${plugins.consolecolor.coloredString(
|
||||
npmPackage.name,
|
||||
'pink'
|
||||
)}`
|
||||
);
|
||||
return false;
|
||||
} else {
|
||||
plugins.smartlog.defaultLogger.log(
|
||||
'warn',
|
||||
`There is a newer version of ${npmPackage.name} available on npm.`
|
||||
);
|
||||
plugins.smartlog.defaultLogger.log(
|
||||
'warn',
|
||||
`Your version: ${versionLocal.versionString} | version on npm: ${versionNpm.versionString}`
|
||||
);
|
||||
if (!process.env.CI && changelogUrlArg) {
|
||||
console.log('trying to open changelog...');
|
||||
plugins.smartopen.openUrl(changelogUrlArg);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
export let standardHandler = new SmartUpdate();
|
||||
// Constants (for reference)
|
||||
export type { TLogLevel } from './smartupdate.constants.js';
|
||||
export { LOG_LEVELS, DEFAULT_MESSAGE_COLOR } from './smartupdate.constants.js';
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
import * as plugins from './smartupdate.plugins.js';
|
||||
import type { ICacheStatus, ICacheOptions, TCacheStrategy, TCachedUpdateStatus } from './smartupdate.interfaces.js';
|
||||
|
||||
type TCacheStoreData = Record<string, ICacheStatus>;
|
||||
|
||||
/**
|
||||
* Manages caching of update check results
|
||||
*/
|
||||
export class UpdateCacheManager {
|
||||
public readonly kvStore: plugins.npmextra.KeyValueStore<TCacheStoreData>;
|
||||
private cacheDurationMs: number;
|
||||
|
||||
constructor(options: ICacheOptions) {
|
||||
this.cacheDurationMs = options.durationMs;
|
||||
this.kvStore = new plugins.npmextra.KeyValueStore<TCacheStoreData>({
|
||||
typeArg: options.storeType || 'userHomeDir',
|
||||
identityArg: options.storeIdentifier || 'global_smartupdate',
|
||||
customPath: options.customPath,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cache duration in milliseconds
|
||||
*/
|
||||
public getCacheDuration(): number {
|
||||
return this.cacheDurationMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cached status for a package
|
||||
*/
|
||||
public async getCached(packageName: string): Promise<ICacheStatus | null> {
|
||||
return (await this.kvStore.readKey(packageName)) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set cache status for a package
|
||||
*/
|
||||
public async setCached(packageName: string, status: ICacheStatus): Promise<void> {
|
||||
await this.kvStore.writeKey(packageName, status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear cache for a specific package or all packages
|
||||
*/
|
||||
public async clearCache(packageName?: string): Promise<void> {
|
||||
if (packageName) {
|
||||
const cacheData = await this.kvStore.readAll();
|
||||
delete cacheData[packageName];
|
||||
await this.kvStore.wipe();
|
||||
await this.kvStore.writeAll(cacheData);
|
||||
} else {
|
||||
await this.kvStore.wipe();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we should check the registry or use cache
|
||||
* @returns Object with shouldCheck flag and optional nextCheckTime
|
||||
*/
|
||||
public async shouldCheckRegistry(
|
||||
packageName: string,
|
||||
strategy: TCacheStrategy = 'time-based',
|
||||
cacheContext: {
|
||||
currentVersion?: string;
|
||||
registryUrl?: string;
|
||||
} = {}
|
||||
): Promise<{
|
||||
shouldCheck: boolean;
|
||||
cacheStatus?: ICacheStatus;
|
||||
nextCheckTime?: Date;
|
||||
minutesUntilNextCheck?: number;
|
||||
}> {
|
||||
// Never use cache
|
||||
if (strategy === 'never') {
|
||||
return { shouldCheck: true };
|
||||
}
|
||||
|
||||
// Get cached data
|
||||
const cacheStatus = await this.getCached(packageName);
|
||||
|
||||
// No cache exists
|
||||
if (!cacheStatus || !this.cacheMatchesContext(cacheStatus, cacheContext)) {
|
||||
return { shouldCheck: true };
|
||||
}
|
||||
|
||||
// Always use cache if available
|
||||
if (strategy === 'always') {
|
||||
return { shouldCheck: false, cacheStatus };
|
||||
}
|
||||
|
||||
// Time-based strategy: check if cache is still valid
|
||||
const now = Date.now();
|
||||
const lastCheckTime = cacheStatus.lastCheck;
|
||||
const timeSinceLastCheck = now - lastCheckTime;
|
||||
|
||||
// Cache is still valid
|
||||
if (timeSinceLastCheck < this.cacheDurationMs) {
|
||||
const nextCheckTime = new Date(lastCheckTime + this.cacheDurationMs);
|
||||
const minutesUntilNextCheck = (this.cacheDurationMs - timeSinceLastCheck) / 60000;
|
||||
|
||||
return {
|
||||
shouldCheck: false,
|
||||
cacheStatus,
|
||||
nextCheckTime,
|
||||
minutesUntilNextCheck,
|
||||
};
|
||||
}
|
||||
|
||||
// Cache is expired
|
||||
return { shouldCheck: true, cacheStatus };
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new cache status object
|
||||
*/
|
||||
public createCacheStatus(
|
||||
latestVersion: string,
|
||||
performedUpgrade: boolean = false,
|
||||
metadata: {
|
||||
currentVersion?: string;
|
||||
registryUrl?: string;
|
||||
status?: TCachedUpdateStatus;
|
||||
} = {}
|
||||
): ICacheStatus {
|
||||
return {
|
||||
lastCheck: Date.now(),
|
||||
latestVersion,
|
||||
currentVersion: metadata.currentVersion,
|
||||
registryUrl: metadata.registryUrl,
|
||||
status: metadata.status,
|
||||
performedUpgrade,
|
||||
};
|
||||
}
|
||||
|
||||
private cacheMatchesContext(
|
||||
cacheStatus: ICacheStatus,
|
||||
cacheContext: {
|
||||
currentVersion?: string;
|
||||
registryUrl?: string;
|
||||
}
|
||||
): boolean {
|
||||
if (cacheContext.currentVersion && cacheStatus.currentVersion !== cacheContext.currentVersion) {
|
||||
return false;
|
||||
}
|
||||
if (cacheContext.registryUrl && cacheStatus.registryUrl !== cacheContext.registryUrl) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
import * as plugins from './smartupdate.plugins.js';
|
||||
import {
|
||||
LOG_LEVELS,
|
||||
type TLogLevel,
|
||||
DEFAULT_MESSAGE_COLOR,
|
||||
MESSAGE_PREFIXES,
|
||||
} from './smartupdate.constants.js';
|
||||
import type { INotificationOptions, IUpdateCheckResult } from './smartupdate.interfaces.js';
|
||||
|
||||
/**
|
||||
* Handles all user-facing notifications and console output
|
||||
*/
|
||||
export class UpdateNotifier {
|
||||
private logLevel: TLogLevel;
|
||||
private useColors: boolean;
|
||||
private customLogger?: (level: TLogLevel, message: string) => void;
|
||||
|
||||
constructor(options: INotificationOptions) {
|
||||
this.logLevel = options.logLevel;
|
||||
this.useColors = options.useColors && !process.env.NO_COLOR;
|
||||
this.customLogger = options.customLogger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a message at the given level should be logged
|
||||
*/
|
||||
private shouldLog(level: TLogLevel): boolean {
|
||||
return LOG_LEVELS[level] <= LOG_LEVELS[this.logLevel];
|
||||
}
|
||||
|
||||
/**
|
||||
* Colorize a string if colors are enabled
|
||||
*/
|
||||
private colorize(
|
||||
text: string,
|
||||
color: plugins.consolecolor.TColorName = DEFAULT_MESSAGE_COLOR
|
||||
): string {
|
||||
if (!this.useColors) {
|
||||
return text;
|
||||
}
|
||||
return plugins.consolecolor.coloredString(text, color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a message at the specified level
|
||||
*/
|
||||
private log(level: TLogLevel, message: string): void {
|
||||
if (!this.shouldLog(level)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.customLogger) {
|
||||
this.customLogger(level, message);
|
||||
} else {
|
||||
console.log(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a debug message
|
||||
*/
|
||||
public debug(message: string): void {
|
||||
this.log('DEBUG', `${MESSAGE_PREFIXES.INFO} ${message}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log an info message
|
||||
*/
|
||||
public info(message: string): void {
|
||||
this.log('INFO', `${MESSAGE_PREFIXES.SMARTUPDATE} ${message}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a warning message
|
||||
*/
|
||||
public warn(message: string): void {
|
||||
this.log('WARN', `${MESSAGE_PREFIXES.WARN} ${message}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log an error message
|
||||
*/
|
||||
public error(message: string): void {
|
||||
this.log('ERROR', `${MESSAGE_PREFIXES.ERROR} ${message}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify about checking for updates
|
||||
*/
|
||||
public notifyCheckingForUpdate(packageName: string): void {
|
||||
this.info(
|
||||
`checking for newer version of ${this.colorize(packageName)}...`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify that the package is up to date
|
||||
*/
|
||||
public notifyUpToDate(packageName: string): void {
|
||||
this.info(
|
||||
`You are running the latest version of ${this.colorize(packageName)}`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify that an update is available
|
||||
*/
|
||||
public notifyUpdateAvailable(packageName: string, currentVersion: string, latestVersion: string): void {
|
||||
this.warn(`There is a newer version of ${packageName} available on npm.`);
|
||||
this.warn(`Your version: ${currentVersion} | version on npm: ${latestVersion}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify that a check was skipped due to rate limiting
|
||||
*/
|
||||
public notifyCheckSkipped(packageName: string, nextCheckMinutes: number): void {
|
||||
const minutes = Math.floor(nextCheckMinutes) + 1;
|
||||
this.info(
|
||||
`Already checked recently. Next check available in ${minutes} minute${minutes !== 1 ? 's' : ''}: ` +
|
||||
this.colorize(packageName)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify that the changelog is being opened
|
||||
*/
|
||||
public notifyOpeningChangelog(): void {
|
||||
this.info('Opening changelog in browser...');
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify about a registry error
|
||||
*/
|
||||
public notifyRegistryError(): void {
|
||||
this.warn('Failed to retrieve package information.');
|
||||
this.info('Is the registry down?');
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify with a complete update check result
|
||||
*/
|
||||
public notifyUpdateCheckResult(result: IUpdateCheckResult): void {
|
||||
switch (result.status) {
|
||||
case 'up-to-date':
|
||||
this.notifyUpToDate(result.packageName);
|
||||
break;
|
||||
|
||||
case 'update-available':
|
||||
if (result.latestVersion) {
|
||||
this.notifyUpdateAvailable(
|
||||
result.packageName,
|
||||
result.currentVersion,
|
||||
result.latestVersion
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'check-skipped':
|
||||
if (result.nextCheckTime && result.reason) {
|
||||
const minutesUntilNext = (result.nextCheckTime.getTime() - result.checkTime.getTime()) / 60000;
|
||||
this.notifyCheckSkipped(result.packageName, minutesUntilNext);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'error':
|
||||
if (result.error) {
|
||||
this.error(result.error.message);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,351 @@
|
||||
import * as plugins from './smartupdate.plugins.js';
|
||||
import { UpdateCacheManager } from './smartupdate.classes.cachemanager.js';
|
||||
import { UpdateNotifier } from './smartupdate.classes.notifier.js';
|
||||
import { DEFAULT_CACHE_DURATION_HOURS, MILLISECONDS_PER_MINUTE, MINUTES_PER_HOUR } from './smartupdate.constants.js';
|
||||
import type {
|
||||
ICacheStatus,
|
||||
ISmartUpdateOptions,
|
||||
IUpdateCheckOptions,
|
||||
IUpdateCheckResult,
|
||||
TCachedUpdateStatus,
|
||||
} from './smartupdate.interfaces.js';
|
||||
import {
|
||||
SmartUpdateError,
|
||||
RegistryUnavailableError,
|
||||
PackageNotFoundError,
|
||||
InvalidPackageNameError,
|
||||
InvalidVersionError,
|
||||
} from './smartupdate.errors.js';
|
||||
|
||||
/**
|
||||
* SmartUpdate - Elegant update checking for Node.js packages
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const smartUpdate = new SmartUpdate();
|
||||
* const result = await smartUpdate.checkForUpdate({
|
||||
* packageName: 'lodash',
|
||||
* currentVersion: '1.0.0',
|
||||
* changelogUrl: 'https://example.com/changelog'
|
||||
* });
|
||||
*
|
||||
* if (result.status === 'update-available') {
|
||||
* console.log(`Update available: ${result.latestVersion}`);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export class SmartUpdate {
|
||||
public readonly npmRegistry: plugins.smartnpm.NpmRegistry;
|
||||
private cacheManager: UpdateCacheManager;
|
||||
private notifier: UpdateNotifier;
|
||||
private options: ISmartUpdateOptions;
|
||||
|
||||
/**
|
||||
* @deprecated Use the options parameter instead of kvStore property
|
||||
*/
|
||||
public kvStore: plugins.npmextra.KeyValueStore<Record<string, ICacheStatus>>;
|
||||
|
||||
constructor(options: ISmartUpdateOptions = {}) {
|
||||
this.options = options;
|
||||
|
||||
// Initialize npm registry
|
||||
this.npmRegistry = new plugins.smartnpm.NpmRegistry(options.npmRegistryOptions || {});
|
||||
|
||||
// Calculate cache duration in milliseconds
|
||||
const cacheDuration = options.cacheDuration || { hours: DEFAULT_CACHE_DURATION_HOURS };
|
||||
const cacheDurationMs =
|
||||
(cacheDuration.hours || 0) * MINUTES_PER_HOUR * MILLISECONDS_PER_MINUTE +
|
||||
(cacheDuration.minutes || 0) * MILLISECONDS_PER_MINUTE +
|
||||
(cacheDuration.seconds || 0) * 1000;
|
||||
|
||||
// Initialize cache manager
|
||||
this.cacheManager = new UpdateCacheManager({
|
||||
durationMs: cacheDurationMs || DEFAULT_CACHE_DURATION_HOURS * MINUTES_PER_HOUR * MILLISECONDS_PER_MINUTE,
|
||||
storeIdentifier: options.cacheStore?.storeIdentifier,
|
||||
storeType: options.cacheStore?.storeType,
|
||||
customPath: options.cacheStore?.customPath,
|
||||
});
|
||||
|
||||
// Initialize notifier
|
||||
this.notifier = new UpdateNotifier({
|
||||
logLevel: options.logLevel || 'INFO',
|
||||
useColors: !options.noColor,
|
||||
customLogger: options.customLogger,
|
||||
});
|
||||
|
||||
// Backward compatibility: expose kvStore
|
||||
this.kvStore = this.cacheManager.kvStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for updates with caching (primarily for CLI use)
|
||||
*
|
||||
* @deprecated Use checkForUpdate with cacheStrategy: 'time-based' instead
|
||||
* @param packageName - The npm package name to check
|
||||
* @param currentVersion - The current version to compare against
|
||||
* @param changelogUrl - Optional URL to open if update is available
|
||||
* @returns Promise resolving to true if update available, false otherwise
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const hasUpdate = await smartUpdate.checkForCli('my-cli', '1.0.0', 'https://changelog.url');
|
||||
* ```
|
||||
*/
|
||||
public async checkForCli(
|
||||
packageName: string,
|
||||
currentVersion: string,
|
||||
changelogUrl?: string
|
||||
): Promise<boolean> {
|
||||
const result = await this.checkForUpdate({
|
||||
packageName,
|
||||
currentVersion,
|
||||
changelogUrl,
|
||||
cacheStrategy: 'time-based',
|
||||
});
|
||||
|
||||
return (
|
||||
result.status === 'update-available' ||
|
||||
(result.status === 'check-skipped' &&
|
||||
!!result.latestVersion &&
|
||||
this.safeIsUpdateAvailable(result.latestVersion, currentVersion))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for updates (modern API)
|
||||
*
|
||||
* @param options - Update check options
|
||||
* @returns Promise resolving to detailed update check result
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const result = await smartUpdate.checkForUpdate({
|
||||
* packageName: 'lodash',
|
||||
* currentVersion: '1.0.0',
|
||||
* changelogUrl: 'https://changelog.url',
|
||||
* openChangelog: true,
|
||||
* cacheStrategy: 'time-based'
|
||||
* });
|
||||
*
|
||||
* console.log(result.status); // 'up-to-date' | 'update-available' | 'check-skipped' | 'error'
|
||||
* ```
|
||||
*/
|
||||
public async checkForUpdate(options: IUpdateCheckOptions): Promise<IUpdateCheckResult> {
|
||||
const {
|
||||
packageName,
|
||||
currentVersion,
|
||||
changelogUrl,
|
||||
openChangelog = true,
|
||||
cacheStrategy = 'time-based',
|
||||
} = options;
|
||||
|
||||
const checkTime = new Date();
|
||||
|
||||
try {
|
||||
this.validatePackageName(packageName);
|
||||
const versionLocal = this.createSmartVersion(currentVersion);
|
||||
const registryUrl = this.getRegistryUrl();
|
||||
|
||||
// Check if we should use cache or check registry
|
||||
const cacheCheck = await this.cacheManager.shouldCheckRegistry(packageName, cacheStrategy, {
|
||||
currentVersion,
|
||||
registryUrl,
|
||||
});
|
||||
|
||||
// If we should skip the check due to cache
|
||||
if (!cacheCheck.shouldCheck) {
|
||||
const skippedResult: IUpdateCheckResult = {
|
||||
status: 'check-skipped',
|
||||
packageName,
|
||||
currentVersion,
|
||||
latestVersion: cacheCheck.cacheStatus?.latestVersion,
|
||||
checkTime,
|
||||
cacheHit: true,
|
||||
nextCheckTime: cacheCheck.nextCheckTime,
|
||||
reason:
|
||||
cacheStrategy === 'always'
|
||||
? 'Cached result reused'
|
||||
: 'Rate limited - checked recently',
|
||||
};
|
||||
this.notifier.notifyUpdateCheckResult(skippedResult);
|
||||
return skippedResult;
|
||||
}
|
||||
|
||||
// Fetch package info from registry
|
||||
const npmPackage = await this.getNpmPackageFromRegistry(packageName);
|
||||
|
||||
// Compare versions
|
||||
const versionNpm = this.createSmartVersion(npmPackage.version);
|
||||
const status: TCachedUpdateStatus = versionNpm.greaterThan(versionLocal)
|
||||
? 'update-available'
|
||||
: 'up-to-date';
|
||||
|
||||
const result: IUpdateCheckResult = {
|
||||
status,
|
||||
packageName: npmPackage.name,
|
||||
currentVersion,
|
||||
latestVersion: npmPackage.version,
|
||||
checkTime,
|
||||
cacheHit: false,
|
||||
};
|
||||
|
||||
// Notify user
|
||||
this.notifier.notifyUpdateCheckResult(result);
|
||||
|
||||
// If update is available, handle changelog
|
||||
if (result.status === 'update-available' && changelogUrl && openChangelog && !process.env.CI) {
|
||||
this.notifier.notifyOpeningChangelog();
|
||||
await plugins.smartopen.openUrl(changelogUrl).catch((error) => {
|
||||
this.notifier.warn(
|
||||
`Could not open changelog: ${error instanceof Error ? error.message : String(error)}`
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Cache both positive and negative checks to avoid repeated registry hits.
|
||||
const cacheStatus = this.cacheManager.createCacheStatus(npmPackage.version, false, {
|
||||
currentVersion,
|
||||
registryUrl,
|
||||
status,
|
||||
});
|
||||
await this.cacheManager.setCached(packageName, cacheStatus);
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
if (error instanceof RegistryUnavailableError) {
|
||||
this.notifier.notifyRegistryError();
|
||||
} else {
|
||||
this.notifier.error(error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'error',
|
||||
packageName,
|
||||
currentVersion,
|
||||
checkTime,
|
||||
cacheHit: false,
|
||||
error: error instanceof Error ? error : new Error(String(error)),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple check for updates without caching
|
||||
*
|
||||
* @deprecated Use checkForUpdate with cacheStrategy: 'never' instead
|
||||
* @param packageName - The npm package name to check
|
||||
* @param currentVersion - The current version to compare against
|
||||
* @param changelogUrl - Optional URL to open if update is available
|
||||
* @returns Promise resolving to true if update available, false otherwise
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const hasUpdate = await smartUpdate.check('lodash', '1.0.0');
|
||||
* ```
|
||||
*/
|
||||
public async check(
|
||||
packageName: string,
|
||||
currentVersion: string,
|
||||
changelogUrl?: string
|
||||
): Promise<boolean> {
|
||||
const result = await this.checkForUpdate({
|
||||
packageName,
|
||||
currentVersion,
|
||||
changelogUrl,
|
||||
cacheStrategy: 'never',
|
||||
});
|
||||
|
||||
return result.status === 'update-available';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the latest version of a package
|
||||
*
|
||||
* @param packageName - The npm package name
|
||||
* @returns Promise resolving to the latest version string
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const latestVersion = await smartUpdate.getLatestVersion('lodash');
|
||||
* console.log(latestVersion); // e.g., "4.17.21"
|
||||
* ```
|
||||
*/
|
||||
public async getLatestVersion(packageName: string): Promise<string> {
|
||||
this.validatePackageName(packageName);
|
||||
const npmPackage = await this.getNpmPackageFromRegistry(packageName);
|
||||
return npmPackage.version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the cache for a specific package
|
||||
*
|
||||
* @param packageName - The package name to clear cache for
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* await smartUpdate.clearCache('lodash');
|
||||
* ```
|
||||
*/
|
||||
public async clearCache(packageName: string): Promise<void> {
|
||||
await this.cacheManager.clearCache(packageName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch package information from the npm registry
|
||||
* @private
|
||||
*/
|
||||
private async getNpmPackageFromRegistry(packageName: string): Promise<plugins.smartnpm.NpmPackage> {
|
||||
this.notifier.notifyCheckingForUpdate(packageName);
|
||||
try {
|
||||
const npmPackage = await this.npmRegistry.getPackageInfo(packageName);
|
||||
if (!npmPackage?.version) {
|
||||
throw new PackageNotFoundError(packageName);
|
||||
}
|
||||
return npmPackage;
|
||||
} catch (error) {
|
||||
if (error instanceof SmartUpdateError) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
const lowerMessage = message.toLowerCase();
|
||||
if (lowerMessage.includes('not found') || lowerMessage.includes('404')) {
|
||||
throw new PackageNotFoundError(packageName);
|
||||
}
|
||||
|
||||
throw new RegistryUnavailableError(message);
|
||||
}
|
||||
}
|
||||
|
||||
private validatePackageName(packageName: string): void {
|
||||
if (!packageName || packageName.trim() !== packageName || /\s/.test(packageName)) {
|
||||
throw new InvalidPackageNameError(packageName);
|
||||
}
|
||||
}
|
||||
|
||||
private createSmartVersion(version: string): plugins.smartversion.SmartVersion {
|
||||
try {
|
||||
return new plugins.smartversion.SmartVersion(version);
|
||||
} catch (error) {
|
||||
throw new InvalidVersionError(version, error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
}
|
||||
|
||||
private isUpdateAvailable(latestVersion: string, currentVersion: string): boolean {
|
||||
const versionNpm = this.createSmartVersion(latestVersion);
|
||||
const versionLocal = this.createSmartVersion(currentVersion);
|
||||
return versionNpm.greaterThan(versionLocal);
|
||||
}
|
||||
|
||||
private safeIsUpdateAvailable(latestVersion: string, currentVersion: string): boolean {
|
||||
try {
|
||||
return this.isUpdateAvailable(latestVersion, currentVersion);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private getRegistryUrl(): string {
|
||||
return this.npmRegistry.options.npmRegistryUrl || 'https://registry.npmjs.org';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Constants used throughout the smartupdate library
|
||||
*/
|
||||
|
||||
// Time constants
|
||||
export const MILLISECONDS_PER_MINUTE = 60_000;
|
||||
export const MINUTES_PER_HOUR = 60;
|
||||
export const DEFAULT_CACHE_DURATION_HOURS = 1;
|
||||
|
||||
// Console output constants
|
||||
export const DEFAULT_MESSAGE_COLOR = 'pink';
|
||||
|
||||
// Log level constants
|
||||
export const LOG_LEVELS = {
|
||||
SILENT: 0,
|
||||
ERROR: 1,
|
||||
WARN: 2,
|
||||
INFO: 3,
|
||||
DEBUG: 4,
|
||||
} as const;
|
||||
|
||||
export type TLogLevel = keyof typeof LOG_LEVELS;
|
||||
|
||||
// Message prefixes
|
||||
export const MESSAGE_PREFIXES = {
|
||||
ERROR: 'error:',
|
||||
WARN: 'warn:',
|
||||
INFO: 'info:',
|
||||
SMARTUPDATE: 'smartupdate:',
|
||||
} as const;
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Base error class for smartupdate errors
|
||||
*/
|
||||
export class SmartUpdateError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = 'SmartUpdateError';
|
||||
// Maintains proper stack trace for where our error was thrown (only available on V8)
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this, this.constructor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error thrown when the npm registry is unavailable
|
||||
*/
|
||||
export class RegistryUnavailableError extends SmartUpdateError {
|
||||
constructor(message: string = 'Failed to retrieve package information from npm registry') {
|
||||
super(message);
|
||||
this.name = 'RegistryUnavailableError';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error thrown when a package is not found in the registry
|
||||
*/
|
||||
export class PackageNotFoundError extends SmartUpdateError {
|
||||
public readonly packageName: string;
|
||||
|
||||
constructor(packageName: string) {
|
||||
super(`Package '${packageName}' not found in npm registry`);
|
||||
this.name = 'PackageNotFoundError';
|
||||
this.packageName = packageName;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error thrown when a version string is invalid
|
||||
*/
|
||||
export class InvalidVersionError extends SmartUpdateError {
|
||||
public readonly version: string;
|
||||
|
||||
constructor(version: string, reason?: string) {
|
||||
const message = reason
|
||||
? `Invalid version string '${version}': ${reason}`
|
||||
: `Invalid version string '${version}'`;
|
||||
super(message);
|
||||
this.name = 'InvalidVersionError';
|
||||
this.version = version;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error thrown when package name validation fails
|
||||
*/
|
||||
export class InvalidPackageNameError extends SmartUpdateError {
|
||||
public readonly packageName: string;
|
||||
|
||||
constructor(packageName: string) {
|
||||
super(`Invalid package name '${packageName}'. Package names must follow npm naming conventions.`);
|
||||
this.name = 'InvalidPackageNameError';
|
||||
this.packageName = packageName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
import type { TLogLevel } from './smartupdate.constants.js';
|
||||
import type * as smartnpm from '@push.rocks/smartnpm';
|
||||
|
||||
export type TCacheStrategy = 'always' | 'never' | 'time-based';
|
||||
export type TCacheStoreType = 'custom' | 'userHomeDir' | 'ephemeral';
|
||||
export type TCachedUpdateStatus = 'up-to-date' | 'update-available';
|
||||
|
||||
/**
|
||||
* Cache status stored for each package
|
||||
*/
|
||||
export interface ICacheStatus {
|
||||
lastCheck: number;
|
||||
latestVersion: string;
|
||||
currentVersion?: string;
|
||||
registryUrl?: string;
|
||||
status?: TCachedUpdateStatus;
|
||||
performedUpgrade: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for configuring the SmartUpdate instance
|
||||
*/
|
||||
export interface ISmartUpdateOptions {
|
||||
/**
|
||||
* Options for the npm registry connection
|
||||
*/
|
||||
npmRegistryOptions?: smartnpm.INpmRegistryConstructorOptions;
|
||||
|
||||
/**
|
||||
* Cache duration configuration
|
||||
* @default { hours: 1 }
|
||||
*/
|
||||
cacheDuration?: {
|
||||
hours?: number;
|
||||
minutes?: number;
|
||||
seconds?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Logging level
|
||||
* @default 'INFO'
|
||||
*/
|
||||
logLevel?: TLogLevel;
|
||||
|
||||
/**
|
||||
* Custom logger function
|
||||
* If provided, this will be used instead of console output
|
||||
*/
|
||||
customLogger?: (level: TLogLevel, message: string) => void;
|
||||
|
||||
/**
|
||||
* Disable colored output
|
||||
* @default false
|
||||
*/
|
||||
noColor?: boolean;
|
||||
|
||||
/**
|
||||
* Cache storage configuration
|
||||
* @default { storeType: 'userHomeDir', storeIdentifier: 'global_smartupdate' }
|
||||
*/
|
||||
cacheStore?: {
|
||||
storeType?: TCacheStoreType;
|
||||
storeIdentifier?: string;
|
||||
customPath?: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for checking for updates
|
||||
*/
|
||||
export interface IUpdateCheckOptions {
|
||||
/**
|
||||
* The npm package name to check
|
||||
*/
|
||||
packageName: string;
|
||||
|
||||
/**
|
||||
* The current version to compare against
|
||||
*/
|
||||
currentVersion: string;
|
||||
|
||||
/**
|
||||
* Optional URL to the changelog
|
||||
* If provided and an update is available, the changelog will be opened
|
||||
*/
|
||||
changelogUrl?: string;
|
||||
|
||||
/**
|
||||
* Whether to open the changelog URL automatically
|
||||
* Only applies if running in a non-CI environment
|
||||
* @default true
|
||||
*/
|
||||
openChangelog?: boolean;
|
||||
|
||||
/**
|
||||
* Cache strategy for this check
|
||||
* - 'always': Always use an existing matching cache entry
|
||||
* - 'never': Always check registry, bypass cache
|
||||
* - 'time-based': Check based on cache duration
|
||||
* @default 'time-based'
|
||||
*/
|
||||
cacheStrategy?: TCacheStrategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Result of an update check
|
||||
*/
|
||||
export interface IUpdateCheckResult {
|
||||
/**
|
||||
* Status of the update check
|
||||
*/
|
||||
status: 'up-to-date' | 'update-available' | 'check-skipped' | 'error';
|
||||
|
||||
/**
|
||||
* The current version being checked
|
||||
*/
|
||||
currentVersion: string;
|
||||
|
||||
/**
|
||||
* The latest version available (if found)
|
||||
*/
|
||||
latestVersion?: string;
|
||||
|
||||
/**
|
||||
* The package name that was checked
|
||||
*/
|
||||
packageName: string;
|
||||
|
||||
/**
|
||||
* Time when the check was performed
|
||||
*/
|
||||
checkTime: Date;
|
||||
|
||||
/**
|
||||
* Whether this result came from cache
|
||||
*/
|
||||
cacheHit: boolean;
|
||||
|
||||
/**
|
||||
* When the next check can be performed (if check was skipped due to rate limiting)
|
||||
*/
|
||||
nextCheckTime?: Date;
|
||||
|
||||
/**
|
||||
* Error details if status is 'error'
|
||||
*/
|
||||
error?: Error;
|
||||
|
||||
/**
|
||||
* Reason for the result (human-readable explanation)
|
||||
*/
|
||||
reason?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for the cache manager
|
||||
*/
|
||||
export interface ICacheOptions {
|
||||
/**
|
||||
* Cache duration in milliseconds
|
||||
*/
|
||||
durationMs: number;
|
||||
|
||||
/**
|
||||
* Identifier for the key-value store
|
||||
*/
|
||||
storeIdentifier?: string;
|
||||
|
||||
/**
|
||||
* Key-value store backend
|
||||
*/
|
||||
storeType?: TCacheStoreType;
|
||||
|
||||
/**
|
||||
* Custom path for custom key-value stores
|
||||
*/
|
||||
customPath?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for the notifier
|
||||
*/
|
||||
export interface INotificationOptions {
|
||||
/**
|
||||
* Log level for notifications
|
||||
*/
|
||||
logLevel: TLogLevel;
|
||||
|
||||
/**
|
||||
* Whether to use colors in output
|
||||
*/
|
||||
useColors: boolean;
|
||||
|
||||
/**
|
||||
* Custom logger function
|
||||
*/
|
||||
customLogger?: (level: TLogLevel, message: string) => void;
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
import * as smartlog from '@pushrocks/smartlog';
|
||||
smartlog.defaultLogger.enableConsole();
|
||||
import * as consolecolor from '@pushrocks/consolecolor';
|
||||
import * as npmextra from '@pushrocks/npmextra';
|
||||
import * as smartnpm from '@pushrocks/smartnpm';
|
||||
import * as smartopen from '@pushrocks/smartopen';
|
||||
import * as smarttime from '@pushrocks/smarttime';
|
||||
import * as smartversion from '@pushrocks/smartversion';
|
||||
import * as consolecolor from '@push.rocks/consolecolor';
|
||||
import * as npmextra from '@push.rocks/npmextra';
|
||||
import * as smartnpm from '@push.rocks/smartnpm';
|
||||
import * as smartopen from '@push.rocks/smartopen';
|
||||
import * as smartversion from '@push.rocks/smartversion';
|
||||
|
||||
export { smartlog, consolecolor, npmextra, smartnpm, smartopen, smarttime, smartversion };
|
||||
export { consolecolor, npmextra, smartnpm, smartopen, smartversion };
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true,
|
||||
"useDefineForClassFields": false,
|
||||
"target": "ES2022",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"noImplicitAny": true,
|
||||
"esModuleInterop": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"types": ["node"]
|
||||
},
|
||||
"exclude": ["dist_*/**/*.d.ts"]
|
||||
}
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"extends": ["tslint:latest", "tslint-config-prettier"],
|
||||
"rules": {
|
||||
"semicolon": [true, "always"],
|
||||
"no-console": false,
|
||||
"ordered-imports": false,
|
||||
"object-literal-sort-keys": false,
|
||||
"member-ordering": {
|
||||
"options":{
|
||||
"order": [
|
||||
"static-method"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaultSeverity": "warning"
|
||||
}
|
||||
Reference in New Issue
Block a user