├── .release-please-manifest.json
├── map.js
├── .npmrc
├── .github
├── CODEOWNERS
├── ISSUE_TEMPLATE
│ ├── config.yml
│ └── bug.yml
├── dependabot.yml
├── matchers
│ └── tap.json
├── settings.yml
├── workflows
│ ├── codeql-analysis.yml
│ ├── audit.yml
│ ├── pull-request.yml
│ ├── release-integration.yml
│ ├── ci.yml
│ ├── ci-release.yml
│ ├── post-dependabot.yml
│ └── release.yml
└── actions
│ ├── create-check
│ └── action.yml
│ └── install-latest-npm
│ └── action.yml
├── CODE_OF_CONDUCT.md
├── .commitlintrc.js
├── .eslintrc.js
├── lib
├── index.js
├── tracker.js
└── client.js
├── .gitignore
├── test
├── index.js
├── tracker.js
└── client.js
├── LICENSE
├── release-please-config.json
├── SECURITY.md
├── package.json
├── docs
└── examples
│ ├── npmlog.js
│ └── cli-progress.js
├── CONTRIBUTING.md
├── tap-snapshots
└── test
│ ├── tracker.js.test.cjs
│ └── client.js.test.cjs
├── CHANGELOG.md
└── README.md
/.release-please-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | ".": "4.0.0"
3 | }
4 |
--------------------------------------------------------------------------------
/map.js:
--------------------------------------------------------------------------------
1 | module.exports = test => test.replace(/^test\//, 'lib/')
2 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | ; This file is automatically added by @npmcli/template-oss. Do not edit.
2 |
3 | package-lock=false
4 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # This file is automatically added by @npmcli/template-oss. Do not edit.
2 |
3 | * @npm/cli-team
4 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | # This file is automatically added by @npmcli/template-oss. Do not edit.
2 |
3 | blank_issues_enabled: true
4 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | All interactions in this repo are covered by the [npm Code of
4 | Conduct](https://docs.npmjs.com/policies/conduct)
5 |
6 | The npm cli team may, at its own discretion, moderate, remove, or edit
7 | any interactions such as pull requests, issues, and comments.
8 |
--------------------------------------------------------------------------------
/.commitlintrc.js:
--------------------------------------------------------------------------------
1 | /* This file is automatically added by @npmcli/template-oss. Do not edit. */
2 |
3 | module.exports = {
4 | extends: ['@commitlint/config-conventional'],
5 | rules: {
6 | 'type-enum': [2, 'always', ['feat', 'fix', 'docs', 'deps', 'chore']],
7 | 'header-max-length': [2, 'always', 80],
8 | 'subject-case': [0],
9 | 'body-max-line-length': [0],
10 | 'footer-max-line-length': [0],
11 | },
12 | }
13 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | /* This file is automatically added by @npmcli/template-oss. Do not edit. */
2 |
3 | 'use strict'
4 |
5 | const { readdirSync: readdir } = require('fs')
6 |
7 | const localConfigs = readdir(__dirname)
8 | .filter((file) => file.startsWith('.eslintrc.local.'))
9 | .map((file) => `./${file}`)
10 |
11 | module.exports = {
12 | root: true,
13 | ignorePatterns: [
14 | 'tap-testdir*/',
15 | ],
16 | extends: [
17 | '@npmcli',
18 | ...localConfigs,
19 | ],
20 | }
21 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # This file is automatically added by @npmcli/template-oss. Do not edit.
2 |
3 | version: 2
4 |
5 | updates:
6 | - package-ecosystem: npm
7 | directory: /
8 | schedule:
9 | interval: daily
10 | target-branch: "main"
11 | allow:
12 | - dependency-type: direct
13 | versioning-strategy: increase-if-necessary
14 | commit-message:
15 | prefix: deps
16 | prefix-development: chore
17 | labels:
18 | - "Dependencies"
19 | open-pull-requests-limit: 10
20 |
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | exports.Client = require('./client.js')
2 | exports.Tracker = require('./tracker.js')
3 |
4 | const trackers = new Map()
5 | exports.createTracker = (name, key, total) => {
6 | const tracker = new exports.Tracker(name, key, total)
7 | if (trackers.has(tracker.key)) {
8 | const msg = `proggy: duplicate progress id ${JSON.stringify(tracker.key)}`
9 | throw new Error(msg)
10 | }
11 | trackers.set(tracker.key, tracker)
12 | tracker.on('done', () => trackers.delete(tracker.key))
13 | return tracker
14 | }
15 | exports.createClient = (options = {}) => new exports.Client(options)
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # This file is automatically added by @npmcli/template-oss. Do not edit.
2 |
3 | # ignore everything in the root
4 | /*
5 |
6 | !**/.gitignore
7 | !/.commitlintrc.js
8 | !/.eslint.config.js
9 | !/.eslintrc.js
10 | !/.eslintrc.local.*
11 | !/.git-blame-ignore-revs
12 | !/.github/
13 | !/.gitignore
14 | !/.npmrc
15 | !/.prettierignore
16 | !/.prettierrc.js
17 | !/.release-please-manifest.json
18 | !/bin/
19 | !/CHANGELOG*
20 | !/CODE_OF_CONDUCT.md
21 | !/CONTRIBUTING.md
22 | !/docs/
23 | !/lib/
24 | !/LICENSE*
25 | !/map.js
26 | !/package.json
27 | !/README*
28 | !/release-please-config.json
29 | !/scripts/
30 | !/SECURITY.md
31 | !/tap-snapshots/
32 | !/test/
33 | !/tsconfig.json
34 | tap-testdir*/
35 |
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | const t = require('tap')
2 | const proggy = require('../lib/index.js')
3 | const Client = require('../lib/client.js')
4 | const Tracker = require('../lib/tracker.js')
5 | t.equal(proggy.Client, Client, 'Client class is exported')
6 | t.equal(proggy.Tracker, Tracker, 'Tracker class is exported')
7 |
8 | const client = proggy.createClient()
9 | t.type(client, Client, 'createClient returns a client')
10 |
11 | t.test('createTracker', t => {
12 | const tracker = proggy.createTracker('hello')
13 | t.throws(() => proggy.createTracker('hello'), {
14 | message: 'proggy: duplicate progress id "hello"',
15 | })
16 | tracker.emit('done')
17 | t.doesNotThrow(() => proggy.createTracker('hello'))
18 | t.end()
19 | })
20 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The ISC License
2 |
3 | Copyright (c) GitHub, Inc.
4 |
5 | Permission to use, copy, modify, and/or distribute this software for any
6 | purpose with or without fee is hereby granted, provided that the above
7 | copyright notice and this permission notice appear in all copies.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
15 | IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 |
--------------------------------------------------------------------------------
/.github/matchers/tap.json:
--------------------------------------------------------------------------------
1 | {
2 | "//@npmcli/template-oss": "This file is automatically added by @npmcli/template-oss. Do not edit.",
3 | "problemMatcher": [
4 | {
5 | "owner": "tap",
6 | "pattern": [
7 | {
8 | "regexp": "^\\s*not ok \\d+ - (.*)",
9 | "message": 1
10 | },
11 | {
12 | "regexp": "^\\s*---"
13 | },
14 | {
15 | "regexp": "^\\s*at:"
16 | },
17 | {
18 | "regexp": "^\\s*line:\\s*(\\d+)",
19 | "line": 1
20 | },
21 | {
22 | "regexp": "^\\s*column:\\s*(\\d+)",
23 | "column": 1
24 | },
25 | {
26 | "regexp": "^\\s*file:\\s*(.*)",
27 | "file": 1
28 | }
29 | ]
30 | }
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/release-please-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "group-pull-request-title-pattern": "chore: release ${version}",
3 | "pull-request-title-pattern": "chore: release${component} ${version}",
4 | "changelog-sections": [
5 | {
6 | "type": "feat",
7 | "section": "Features",
8 | "hidden": false
9 | },
10 | {
11 | "type": "fix",
12 | "section": "Bug Fixes",
13 | "hidden": false
14 | },
15 | {
16 | "type": "docs",
17 | "section": "Documentation",
18 | "hidden": false
19 | },
20 | {
21 | "type": "deps",
22 | "section": "Dependencies",
23 | "hidden": false
24 | },
25 | {
26 | "type": "chore",
27 | "section": "Chores",
28 | "hidden": true
29 | }
30 | ],
31 | "packages": {
32 | ".": {
33 | "package-name": ""
34 | }
35 | },
36 | "prerelease-type": "pre.0"
37 | }
38 |
--------------------------------------------------------------------------------
/.github/settings.yml:
--------------------------------------------------------------------------------
1 | # This file is automatically added by @npmcli/template-oss. Do not edit.
2 |
3 | repository:
4 | allow_merge_commit: false
5 | allow_rebase_merge: true
6 | allow_squash_merge: true
7 | squash_merge_commit_title: PR_TITLE
8 | squash_merge_commit_message: PR_BODY
9 | delete_branch_on_merge: true
10 | enable_automated_security_fixes: true
11 | enable_vulnerability_alerts: true
12 |
13 | branches:
14 | - name: main
15 | protection:
16 | required_status_checks: null
17 | enforce_admins: true
18 | block_creations: true
19 | required_pull_request_reviews:
20 | required_approving_review_count: 1
21 | require_code_owner_reviews: true
22 | require_last_push_approval: true
23 | dismiss_stale_reviews: true
24 | restrictions:
25 | apps: []
26 | users: []
27 | teams: [ "cli-team" ]
28 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # This file is automatically added by @npmcli/template-oss. Do not edit.
2 |
3 | name: CodeQL
4 |
5 | on:
6 | push:
7 | branches:
8 | - main
9 | pull_request:
10 | branches:
11 | - main
12 | schedule:
13 | # "At 10:00 UTC (03:00 PT) on Monday" https://crontab.guru/#0_10_*_*_1
14 | - cron: "0 10 * * 1"
15 |
16 | permissions:
17 | contents: read
18 |
19 | jobs:
20 | analyze:
21 | name: Analyze
22 | runs-on: ubuntu-latest
23 | permissions:
24 | actions: read
25 | contents: read
26 | security-events: write
27 | steps:
28 | - name: Checkout
29 | uses: actions/checkout@v4
30 | - name: Setup Git User
31 | run: |
32 | git config --global user.email "npm-cli+bot@github.com"
33 | git config --global user.name "npm CLI robot"
34 | - name: Initialize CodeQL
35 | uses: github/codeql-action/init@v3
36 | with:
37 | languages: javascript
38 | - name: Perform CodeQL Analysis
39 | uses: github/codeql-action/analyze@v3
40 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | GitHub takes the security of our software products and services seriously, including the open source code repositories managed through our GitHub organizations, such as [GitHub](https://github.com/GitHub).
4 |
5 | If you believe you have found a security vulnerability in this GitHub-owned open source repository, you can report it to us in one of two ways.
6 |
7 | If the vulnerability you have found is *not* [in scope for the GitHub Bug Bounty Program](https://bounty.github.com/#scope) or if you do not wish to be considered for a bounty reward, please report the issue to us directly through [opensource-security@github.com](mailto:opensource-security@github.com).
8 |
9 | If the vulnerability you have found is [in scope for the GitHub Bug Bounty Program](https://bounty.github.com/#scope) and you would like for your finding to be considered for a bounty reward, please submit the vulnerability to us through [HackerOne](https://hackerone.com/github) in order to be eligible to receive a bounty award.
10 |
11 | **Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.**
12 |
13 | Thanks for helping make GitHub safe for everyone.
14 |
--------------------------------------------------------------------------------
/.github/workflows/audit.yml:
--------------------------------------------------------------------------------
1 | # This file is automatically added by @npmcli/template-oss. Do not edit.
2 |
3 | name: Audit
4 |
5 | on:
6 | workflow_dispatch:
7 | schedule:
8 | # "At 08:00 UTC (01:00 PT) on Monday" https://crontab.guru/#0_8_*_*_1
9 | - cron: "0 8 * * 1"
10 |
11 | permissions:
12 | contents: read
13 |
14 | jobs:
15 | audit:
16 | name: Audit Dependencies
17 | if: github.repository_owner == 'npm'
18 | runs-on: ubuntu-latest
19 | defaults:
20 | run:
21 | shell: bash
22 | steps:
23 | - name: Checkout
24 | uses: actions/checkout@v4
25 | - name: Setup Git User
26 | run: |
27 | git config --global user.email "npm-cli+bot@github.com"
28 | git config --global user.name "npm CLI robot"
29 | - name: Setup Node
30 | uses: actions/setup-node@v4
31 | id: node
32 | with:
33 | node-version: 22.x
34 | check-latest: contains('22.x', '.x')
35 | - name: Install Latest npm
36 | uses: ./.github/actions/install-latest-npm
37 | with:
38 | node: ${{ steps.node.outputs.node-version }}
39 | - name: Install Dependencies
40 | run: npm i --ignore-scripts --no-audit --no-fund --package-lock
41 | - name: Run Production Audit
42 | run: npm audit --omit=dev
43 | - name: Run Full Audit
44 | run: npm audit --audit-level=none
45 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "proggy",
3 | "version": "4.0.0",
4 | "files": [
5 | "bin/",
6 | "lib/"
7 | ],
8 | "main": "lib/index.js",
9 | "description": "Progress bar updates at a distance",
10 | "repository": {
11 | "type": "git",
12 | "url": "git+https://github.com/npm/proggy.git"
13 | },
14 | "author": "GitHub Inc.",
15 | "license": "ISC",
16 | "scripts": {
17 | "test": "tap",
18 | "posttest": "npm run lint",
19 | "snap": "tap",
20 | "postsnap": "eslint lib test --fix",
21 | "lint": "npm run eslint",
22 | "postlint": "template-oss-check",
23 | "lintfix": "npm run eslint -- --fix",
24 | "template-oss-apply": "template-oss-apply --force",
25 | "eslint": "eslint \"**/*.{js,cjs,ts,mjs,jsx,tsx}\""
26 | },
27 | "devDependencies": {
28 | "@npmcli/eslint-config": "^6.0.0",
29 | "@npmcli/template-oss": "4.28.1",
30 | "chalk": "^4.1.2",
31 | "cli-progress": "^3.10.0",
32 | "npmlog": "^7.0.0",
33 | "tap": "^16.0.1"
34 | },
35 | "tap": {
36 | "coverage-map": "map.js",
37 | "nyc-arg": [
38 | "--exclude",
39 | "tap-snapshots/**"
40 | ]
41 | },
42 | "engines": {
43 | "node": "^20.17.0 || >=22.9.0"
44 | },
45 | "templateOSS": {
46 | "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
47 | "version": "4.28.1",
48 | "publish": true
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug.yml:
--------------------------------------------------------------------------------
1 | # This file is automatically added by @npmcli/template-oss. Do not edit.
2 |
3 | name: Bug
4 | description: File a bug/issue
5 | title: "[BUG]
"
6 | labels: [ Bug, Needs Triage ]
7 |
8 | body:
9 | - type: checkboxes
10 | attributes:
11 | label: Is there an existing issue for this?
12 | description: Please [search here](./issues) to see if an issue already exists for your problem.
13 | options:
14 | - label: I have searched the existing issues
15 | required: true
16 | - type: textarea
17 | attributes:
18 | label: Current Behavior
19 | description: A clear & concise description of what you're experiencing.
20 | validations:
21 | required: false
22 | - type: textarea
23 | attributes:
24 | label: Expected Behavior
25 | description: A clear & concise description of what you expected to happen.
26 | validations:
27 | required: false
28 | - type: textarea
29 | attributes:
30 | label: Steps To Reproduce
31 | description: Steps to reproduce the behavior.
32 | value: |
33 | 1. In this environment...
34 | 2. With this config...
35 | 3. Run '...'
36 | 4. See error...
37 | validations:
38 | required: false
39 | - type: textarea
40 | attributes:
41 | label: Environment
42 | description: |
43 | examples:
44 | - **npm**: 7.6.3
45 | - **Node**: 13.14.0
46 | - **OS**: Ubuntu 20.04
47 | - **platform**: Macbook Pro
48 | value: |
49 | - npm:
50 | - Node:
51 | - OS:
52 | - platform:
53 | validations:
54 | required: false
55 |
--------------------------------------------------------------------------------
/lib/tracker.js:
--------------------------------------------------------------------------------
1 | // The tracker class is intentionally as naive as possible. it is just
2 | // an ergonomic wrapper around process.emit('progress', ...)
3 | const EE = require('events')
4 | class Tracker extends EE {
5 | constructor (name, key, total) {
6 | super()
7 | if (!name) {
8 | throw new Error('proggy: Tracker needs a name')
9 | }
10 |
11 | if (typeof key === 'number' && !total) {
12 | total = key
13 | key = null
14 | }
15 |
16 | if (!total) {
17 | total = 100
18 | }
19 |
20 | if (!key) {
21 | key = name
22 | }
23 |
24 | this.done = false
25 | this.name = name
26 | this.key = key
27 | this.value = 0
28 | this.total = total
29 | }
30 |
31 | finish (metadata = {}) {
32 | this.update(this.total, this.total, metadata)
33 | }
34 |
35 | update (value, total, metadata) {
36 | if (!metadata) {
37 | if (total && typeof total === 'object') {
38 | metadata = total
39 | } else {
40 | metadata = {}
41 | }
42 | }
43 | if (typeof total !== 'number') {
44 | total = this.total
45 | }
46 |
47 | if (this.done) {
48 | const msg = `proggy: updating completed tracker: ${JSON.stringify(this.key)}`
49 | throw new Error(msg)
50 | }
51 | this.value = value
52 | this.total = total
53 | const done = this.value >= this.total
54 | process.emit('progress', this.key, {
55 | ...metadata,
56 | name: this.name,
57 | key: this.key,
58 | value,
59 | total,
60 | done,
61 | })
62 | if (done) {
63 | this.done = true
64 | this.emit('done')
65 | }
66 | }
67 | }
68 | module.exports = Tracker
69 |
--------------------------------------------------------------------------------
/.github/actions/create-check/action.yml:
--------------------------------------------------------------------------------
1 | # This file is automatically added by @npmcli/template-oss. Do not edit.
2 |
3 | name: 'Create Check'
4 | inputs:
5 | name:
6 | required: true
7 | token:
8 | required: true
9 | sha:
10 | required: true
11 | check-name:
12 | default: ''
13 | outputs:
14 | check-id:
15 | value: ${{ steps.create-check.outputs.check_id }}
16 | runs:
17 | using: "composite"
18 | steps:
19 | - name: Get Workflow Job
20 | uses: actions/github-script@v7
21 | id: workflow
22 | env:
23 | JOB_NAME: "${{ inputs.name }}"
24 | SHA: "${{ inputs.sha }}"
25 | with:
26 | result-encoding: string
27 | script: |
28 | const { repo: { owner, repo}, runId, serverUrl } = context
29 | const { JOB_NAME, SHA } = process.env
30 |
31 | const job = await github.rest.actions.listJobsForWorkflowRun({
32 | owner,
33 | repo,
34 | run_id: runId,
35 | per_page: 100
36 | }).then(r => r.data.jobs.find(j => j.name.endsWith(JOB_NAME)))
37 |
38 | return [
39 | `This check is assosciated with ${serverUrl}/${owner}/${repo}/commit/${SHA}.`,
40 | 'Run logs:',
41 | job?.html_url || `could not be found for a job ending with: "${JOB_NAME}"`,
42 | ].join(' ')
43 | - name: Create Check
44 | uses: LouisBrunner/checks-action@v1.6.0
45 | id: create-check
46 | with:
47 | token: ${{ inputs.token }}
48 | sha: ${{ inputs.sha }}
49 | status: in_progress
50 | name: ${{ inputs.check-name || inputs.name }}
51 | output: |
52 | {"summary":"${{ steps.workflow.outputs.result }}"}
53 |
--------------------------------------------------------------------------------
/.github/workflows/pull-request.yml:
--------------------------------------------------------------------------------
1 | # This file is automatically added by @npmcli/template-oss. Do not edit.
2 |
3 | name: Pull Request
4 |
5 | on:
6 | pull_request:
7 | types:
8 | - opened
9 | - reopened
10 | - edited
11 | - synchronize
12 |
13 | permissions:
14 | contents: read
15 |
16 | jobs:
17 | commitlint:
18 | name: Lint Commits
19 | if: github.repository_owner == 'npm'
20 | runs-on: ubuntu-latest
21 | defaults:
22 | run:
23 | shell: bash
24 | steps:
25 | - name: Checkout
26 | uses: actions/checkout@v4
27 | with:
28 | fetch-depth: 0
29 | - name: Setup Git User
30 | run: |
31 | git config --global user.email "npm-cli+bot@github.com"
32 | git config --global user.name "npm CLI robot"
33 | - name: Setup Node
34 | uses: actions/setup-node@v4
35 | id: node
36 | with:
37 | node-version: 22.x
38 | check-latest: contains('22.x', '.x')
39 | - name: Install Latest npm
40 | uses: ./.github/actions/install-latest-npm
41 | with:
42 | node: ${{ steps.node.outputs.node-version }}
43 | - name: Install Dependencies
44 | run: npm i --ignore-scripts --no-audit --no-fund
45 | - name: Run Commitlint on Commits
46 | id: commit
47 | continue-on-error: true
48 | run: npx --offline commitlint -V --from 'origin/${{ github.base_ref }}' --to ${{ github.event.pull_request.head.sha }}
49 | - name: Run Commitlint on PR Title
50 | if: steps.commit.outcome == 'failure'
51 | env:
52 | PR_TITLE: ${{ github.event.pull_request.title }}
53 | run: echo "$PR_TITLE" | npx --offline commitlint -V
54 |
--------------------------------------------------------------------------------
/test/tracker.js:
--------------------------------------------------------------------------------
1 | const Tracker = require('../lib/tracker.js')
2 | const t = require('tap')
3 |
4 | t.test('ctor', t => {
5 | t.throws(() => new Tracker(), {
6 | message: 'proggy: Tracker needs a name',
7 | })
8 | t.matchSnapshot(new Tracker('hello'), 'name, no key or total')
9 | t.matchSnapshot(new Tracker('hello', 5), 'name and total')
10 | t.matchSnapshot(new Tracker('hello', 'hellokey'), 'name and key')
11 | t.matchSnapshot(new Tracker('hello', 'hellokey', 5), 'name, key, and total')
12 | t.end()
13 | })
14 |
15 | t.test('emit some events', t => {
16 | const listener = (key, data) =>
17 | t.matchSnapshot([key, data], 'progress event')
18 |
19 | t.teardown(() => process.removeListener('progress', listener))
20 | process.on('progress', listener)
21 |
22 | const tracker = new Tracker('hello', 'key', 10)
23 | // 6 updates, 1 throw for update after done
24 | t.plan(7)
25 | tracker.on('done', () => {
26 | t.throws(() => tracker.update(1, 2), {
27 | message: 'proggy: updating completed tracker: "key"',
28 | })
29 | t.end()
30 | })
31 | tracker.update(2)
32 | tracker.update(2, 5, { message: 'reduced total' })
33 | tracker.update(3, { message: 'no change to total' })
34 | tracker.update(7, 100, { message: 'increased total' })
35 | tracker.update(4, 200, { message: 'reduced value' })
36 | tracker.update(100, 100, { message: 'implicitly done' })
37 | })
38 |
39 | t.test('finish() alias for update(total, total)', t => {
40 | const runTest = data => t => {
41 | const tracker = new Tracker('hello', 'key', 10)
42 | tracker.on('done', doneData => {
43 | t.matchSnapshot(doneData, 'data received by done event')
44 | t.end()
45 | })
46 | tracker.finish(data)
47 | }
48 | t.test('run with data', runTest({ hello: 'world' }))
49 | t.test('run without', runTest())
50 | t.end()
51 | })
52 |
--------------------------------------------------------------------------------
/.github/actions/install-latest-npm/action.yml:
--------------------------------------------------------------------------------
1 | # This file is automatically added by @npmcli/template-oss. Do not edit.
2 |
3 | name: 'Install Latest npm'
4 | description: 'Install the latest version of npm compatible with the Node version'
5 | inputs:
6 | node:
7 | description: 'Current Node version'
8 | required: true
9 | runs:
10 | using: "composite"
11 | steps:
12 | # node 10/12/14 ship with npm@6, which is known to fail when updating itself in windows
13 | - name: Update Windows npm
14 | if: |
15 | runner.os == 'Windows' && (
16 | startsWith(inputs.node, 'v10.') ||
17 | startsWith(inputs.node, 'v12.') ||
18 | startsWith(inputs.node, 'v14.')
19 | )
20 | shell: cmd
21 | run: |
22 | curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz
23 | tar xf npm-7.5.4.tgz
24 | cd package
25 | node lib/npm.js install --no-fund --no-audit -g ..\npm-7.5.4.tgz
26 | cd ..
27 | rmdir /s /q package
28 | - name: Install Latest npm
29 | shell: bash
30 | env:
31 | NODE_VERSION: ${{ inputs.node }}
32 | working-directory: ${{ runner.temp }}
33 | run: |
34 | MATCH=""
35 | SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6")
36 |
37 | echo "node@$NODE_VERSION"
38 |
39 | for SPEC in ${SPECS[@]}; do
40 | ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node')
41 | echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)"
42 |
43 | if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then
44 | MATCH=$SPEC
45 | echo "Found compatible version: npm@$MATCH"
46 | break
47 | fi
48 | done
49 |
50 | if [ -z $MATCH ]; then
51 | echo "Could not find a compatible version of npm for node@$NODE_VERSION"
52 | exit 1
53 | fi
54 |
55 | npm i --prefer-online --no-fund --no-audit -g npm@$MATCH
56 | - name: npm Version
57 | shell: bash
58 | run: npm -v
59 |
--------------------------------------------------------------------------------
/docs/examples/npmlog.js:
--------------------------------------------------------------------------------
1 | const proggy = require('../../lib')
2 |
3 | // set up our display thing
4 | const npmlog = require('npmlog')
5 | npmlog.enableProgress()
6 | const group = npmlog.newGroup('npmlog example')
7 |
8 | // update it with events from the proggy client
9 | const client = proggy.createClient({
10 | // don't show reverse progress
11 | // this is false by default
12 | normalize: true,
13 | })
14 |
15 | const bars = {}
16 |
17 | // new bar is created, tell npmlog about it
18 | client.on('bar', (key, data) => {
19 | bars[key] = group.newItem(key, data.total)
20 | group.notice('starting progress bar', key, data)
21 | })
22 |
23 | // got some progress for a progress bar, tell npmlog to show it
24 | client.on('progress', (key, data) => {
25 | const bar = bars[key]
26 | group.verbose(key, data.actualValue, data.actualTotal, `${Math.floor(data.value)}%`)
27 | bar.addWork(bar.workTodo - data.total)
28 | bars[key].completeWork(data.value - bar.workDone)
29 | })
30 |
31 | // a bar is done, tell npmlog to stop updating it
32 | client.on('barDone', (key, data) => {
33 | group.notice('task completed', key, data)
34 | bars[key].finish()
35 | })
36 |
37 | // all bars done, tell npmlog to close entirely
38 | client.on('done', () => {
39 | group.notice('all progress completed')
40 | npmlog.disableProgress()
41 | })
42 |
43 | // the thing that emits the events
44 | let b1value = 0
45 | let b1total = 100
46 | let b2value = 0
47 | const b2total = 200
48 | let b3value = 0
49 | const b3total = 50
50 |
51 | const b1 = proggy.createTracker('bar 1')
52 | const b2 = proggy.createTracker('bar 2')
53 | const b3 = proggy.createTracker('bar 3')
54 |
55 | let i = 0
56 | const interval = setInterval(() => {
57 | const inc = Math.ceil(Math.random() * 10)
58 |
59 | if (b1value < b1total) {
60 | if ((i++ % 2 === 0) && b1total < 2000) {
61 | b1total += inc * 3
62 | } else {
63 | b1value += inc
64 | }
65 | b1.update(Math.min(b1total, b1value), b1total)
66 | }
67 |
68 | if (b2value < b2total) {
69 | b2.update(Math.min(b2total, b2value += inc), b2total)
70 | }
71 |
72 | if (b3value < b3total) {
73 | b3value = Math.ceil(b3value + 0.003 * (b3total + b3value))
74 | b3.update(Math.min(b3value, b3total), b3total)
75 | }
76 |
77 | if (b1value >= b1total && b2value >= b2total && b3value >= b3total) {
78 | clearInterval(interval)
79 | }
80 | }, 50)
81 |
--------------------------------------------------------------------------------
/.github/workflows/release-integration.yml:
--------------------------------------------------------------------------------
1 | # This file is automatically added by @npmcli/template-oss. Do not edit.
2 |
3 | name: Release Integration
4 |
5 | on:
6 | workflow_dispatch:
7 | inputs:
8 | releases:
9 | required: true
10 | type: string
11 | description: 'A json array of releases. Required fields: publish: tagName, publishTag. publish check: pkgName, version'
12 | workflow_call:
13 | inputs:
14 | releases:
15 | required: true
16 | type: string
17 | description: 'A json array of releases. Required fields: publish: tagName, publishTag. publish check: pkgName, version'
18 | secrets:
19 | PUBLISH_TOKEN:
20 | required: true
21 |
22 | permissions:
23 | contents: read
24 | id-token: write
25 |
26 | jobs:
27 | publish:
28 | name: Publish
29 | runs-on: ubuntu-latest
30 | defaults:
31 | run:
32 | shell: bash
33 | permissions:
34 | id-token: write
35 | steps:
36 | - name: Checkout
37 | uses: actions/checkout@v4
38 | with:
39 | ref: ${{ fromJSON(inputs.releases)[0].tagName }}
40 | - name: Setup Git User
41 | run: |
42 | git config --global user.email "npm-cli+bot@github.com"
43 | git config --global user.name "npm CLI robot"
44 | - name: Setup Node
45 | uses: actions/setup-node@v4
46 | id: node
47 | with:
48 | node-version: 22.x
49 | check-latest: contains('22.x', '.x')
50 | - name: Install Latest npm
51 | uses: ./.github/actions/install-latest-npm
52 | with:
53 | node: ${{ steps.node.outputs.node-version }}
54 | - name: Install Dependencies
55 | run: npm i --ignore-scripts --no-audit --no-fund
56 | - name: Set npm authToken
57 | run: npm config set '//registry.npmjs.org/:_authToken'=\${PUBLISH_TOKEN}
58 | - name: Publish
59 | env:
60 | PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }}
61 | RELEASES: ${{ inputs.releases }}
62 | run: |
63 | EXIT_CODE=0
64 |
65 | for release in $(echo $RELEASES | jq -r '.[] | @base64'); do
66 | PUBLISH_TAG=$(echo "$release" | base64 --decode | jq -r .publishTag)
67 | npm publish --provenance --tag="$PUBLISH_TAG"
68 | STATUS=$?
69 | if [[ "$STATUS" -eq 1 ]]; then
70 | EXIT_CODE=$STATUS
71 | fi
72 | done
73 |
74 | exit $EXIT_CODE
75 |
--------------------------------------------------------------------------------
/docs/examples/cli-progress.js:
--------------------------------------------------------------------------------
1 | const chalk = require('chalk')
2 | const proggy = require('../../lib')
3 |
4 | // note that data.actualValue and data.actualTotal will reflect the real
5 | // done/remaining values. data.value will always be less than 100, and
6 | // data.total will always be 100, so we never show reverse motion.
7 | // eslint-disable-next-line max-len
8 | const format = `[${chalk.cyan('{bar}')}] {percentage}% | {name} {actualValue}/{actualTotal} | {duration_formatted} ETA: {eta_formatted}`
9 |
10 | // set up our display thing
11 | const cliProgress = require('cli-progress')
12 | const multibar = new cliProgress.MultiBar({
13 | clearOnComplete: false,
14 | hideCursor: true,
15 | format,
16 | barCompleteChar: '\u2588',
17 | barIncompleteChar: '\u2591',
18 | }, cliProgress.Presets.shades_grey)
19 |
20 | // update it with events from the proggy client
21 | const client = proggy.createClient({
22 | // don't show reverse progress
23 | // this is false by default
24 | normalize: true,
25 | })
26 | const bars = {}
27 | // new bar is created, tell multibar about it
28 | client.on('bar', (key, data) => {
29 | bars[key] = multibar.create(data.total)
30 | })
31 | // got some progress for a progress bar, tell multibar to show it
32 | client.on('progress', (key, data) => {
33 | bars[key].update(data.value, data)
34 | bars[key].setTotal(data.total)
35 | })
36 | // a bar is done, tell multibar to stop updating it
37 | client.on('barDone', (key) => {
38 | bars[key].stop()
39 | })
40 | // all bars done, tell multibar to close entirely
41 | client.on('done', () => {
42 | multibar.stop()
43 | })
44 |
45 | // the thing that emits the events
46 | let b1value = 0
47 | let b1total = 100
48 | let b2value = 0
49 | const b2total = 2000
50 | let b3value = 0
51 | const b3total = 500
52 |
53 | const b1 = proggy.createTracker('bar 1')
54 | const b2 = proggy.createTracker('bar 2')
55 | const b3 = proggy.createTracker('bar 3')
56 |
57 | const interval = setInterval(() => {
58 | const inc = Math.ceil(Math.random() * 10)
59 | if (b3value < b3total) {
60 | b3value = Math.ceil(b3value + 0.003 * (b3total + b3value))
61 | b3.update(Math.min(b3value, b3total), b3total)
62 | }
63 | if (2.9 * Math.random() < 1 && b1value < b1total && b1total < 2000) {
64 | b1total += inc * 3
65 | }
66 | if (b1value < b1total) {
67 | b1.update(Math.min(b1total, b1value += inc), b1total)
68 | }
69 | if (b2value < b2total) {
70 | b2.update(Math.min(b2total, b2value += inc), b2total)
71 | }
72 |
73 | if (b1value >= b1total && b2value >= b2total && b3value >= b3total) {
74 | clearInterval(interval)
75 | }
76 | }, 50)
77 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Contributing
4 |
5 | ## Code of Conduct
6 |
7 | All interactions in the **npm** organization on GitHub are considered to be covered by our standard [Code of Conduct](https://docs.npmjs.com/policies/conduct).
8 |
9 | ## Reporting Bugs
10 |
11 | Before submitting a new bug report please search for an existing or similar report.
12 |
13 | Use one of our existing issue templates if you believe you've come across a unique problem.
14 |
15 | Duplicate issues, or issues that don't use one of our templates may get closed without a response.
16 |
17 | ## Pull Request Conventions
18 |
19 | ### Commits
20 |
21 | We use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/).
22 |
23 | When opening a pull request please be sure that either the pull request title, or each commit in the pull request, has one of the following prefixes:
24 |
25 | - `feat`: For when introducing a new feature. The result will be a new semver minor version of the package when it is next published.
26 | - `fix`: For bug fixes. The result will be a new semver patch version of the package when it is next published.
27 | - `docs`: For documentation updates. The result will be a new semver patch version of the package when it is next published.
28 | - `chore`: For changes that do not affect the published module. Often these are changes to tests. The result will be *no* change to the version of the package when it is next published (as the commit does not affect the published version).
29 |
30 | ### Test Coverage
31 |
32 | Pull requests made against this repo will run `npm test` automatically. Please make sure tests pass locally before submitting a PR.
33 |
34 | Every new feature or bug fix should come with a corresponding test or tests that validate the solutions. Testing also reports on code coverage and will fail if code coverage drops.
35 |
36 | ### Linting
37 |
38 | Linting is also done automatically once tests pass. `npm run lintfix` will fix most linting errors automatically.
39 |
40 | Please make sure linting passes before submitting a PR.
41 |
42 | ## What _not_ to contribute?
43 |
44 | ### Dependencies
45 |
46 | It should be noted that our team does not accept third-party dependency updates/PRs. If you submit a PR trying to update our dependencies we will close it with or without a reference to these contribution guidelines.
47 |
48 | ### Tools/Automation
49 |
50 | Our core team is responsible for the maintenance of the tooling/automation in this project and we ask contributors to not make changes to these when contributing (e.g. `.github/*`, `.eslintrc.json`, `.licensee.json`). Most of those files also have a header at the top to remind folks they are automatically generated. Pull requests that alter these will not be accepted.
51 |
--------------------------------------------------------------------------------
/lib/client.js:
--------------------------------------------------------------------------------
1 | const EE = require('events')
2 | const onProgress = Symbol('onProgress')
3 | const bars = Symbol('bars')
4 | const listener = Symbol('listener')
5 | const normData = Symbol('normData')
6 | class Client extends EE {
7 | constructor ({ normalize = false, stopOnDone = false } = {}) {
8 | super()
9 | this.normalize = !!normalize
10 | this.stopOnDone = !!stopOnDone
11 | this[bars] = new Map()
12 | this[listener] = null
13 | }
14 |
15 | get size () {
16 | return this[bars].size
17 | }
18 |
19 | get listening () {
20 | return !!this[listener]
21 | }
22 |
23 | addListener (...args) {
24 | return this.on(...args)
25 | }
26 |
27 | on (ev, ...args) {
28 | if (ev === 'progress' && !this[listener]) {
29 | this.start()
30 | }
31 | return super.on(ev, ...args)
32 | }
33 |
34 | off (ev, ...args) {
35 | return this.removeListener(ev, ...args)
36 | }
37 |
38 | removeListener (ev, ...args) {
39 | const ret = super.removeListener(ev, ...args)
40 | if (ev === 'progress' && this.listeners(ev).length === 0) {
41 | this.stop()
42 | }
43 | return ret
44 | }
45 |
46 | stop () {
47 | if (this[listener]) {
48 | process.removeListener('progress', this[listener])
49 | this[listener] = null
50 | }
51 | }
52 |
53 | start () {
54 | if (!this[listener]) {
55 | this[listener] = (...args) => this[onProgress](...args)
56 | process.on('progress', this[listener])
57 | }
58 | }
59 |
60 | [onProgress] (key, data) {
61 | data = this[normData](key, data)
62 | if (!this[bars].has(key)) {
63 | this.emit('bar', key, data)
64 | }
65 | this[bars].set(key, data)
66 | this.emit('progress', key, data)
67 | if (data.done) {
68 | this[bars].delete(key)
69 | this.emit('barDone', key, data)
70 | if (this.size === 0) {
71 | if (this.stopOnDone) {
72 | this.stop()
73 | }
74 | this.emit('done')
75 | }
76 | }
77 | }
78 |
79 | [normData] (key, data) {
80 | const actualValue = data.value
81 | const actualTotal = data.total
82 | let value = actualValue
83 | let total = actualTotal
84 | const done = data.done || value >= total
85 | if (this.normalize) {
86 | const bar = this[bars].get(key)
87 | total = 100
88 | if (done) {
89 | value = 100
90 | } else {
91 | // show value as a portion of 100
92 | const pct = 100 * actualValue / actualTotal
93 | if (bar) {
94 | // don't ever go backwards, and don't stand still
95 | // move at least 1% of the remaining value if it wouldn't move.
96 | value = (pct > bar.value) ? pct
97 | : (100 - bar.value) / 100 + bar.value
98 | }
99 | }
100 | }
101 | // include the key
102 | return {
103 | ...data,
104 | key,
105 | name: data.name || key,
106 | value,
107 | total,
108 | actualValue,
109 | actualTotal,
110 | done,
111 | }
112 | }
113 | }
114 | module.exports = Client
115 |
--------------------------------------------------------------------------------
/tap-snapshots/test/tracker.js.test.cjs:
--------------------------------------------------------------------------------
1 | /* IMPORTANT
2 | * This snapshot file is auto-generated, but designed for humans.
3 | * It should be checked into source control and tracked carefully.
4 | * Re-generate by setting TAP_SNAPSHOT=1 and running tests.
5 | * Make sure to inspect the output below. Do not ignore changes!
6 | */
7 | 'use strict'
8 | exports[`test/tracker.js TAP ctor > name and key 1`] = `
9 | Tracker {
10 | "_events": Null Object {},
11 | "_eventsCount": 0,
12 | "_maxListeners": undefined,
13 | "done": false,
14 | "key": "hellokey",
15 | "name": "hello",
16 | "total": 100,
17 | "value": 0,
18 | }
19 | `
20 |
21 | exports[`test/tracker.js TAP ctor > name and total 1`] = `
22 | Tracker {
23 | "_events": Null Object {},
24 | "_eventsCount": 0,
25 | "_maxListeners": undefined,
26 | "done": false,
27 | "key": "hello",
28 | "name": "hello",
29 | "total": 5,
30 | "value": 0,
31 | }
32 | `
33 |
34 | exports[`test/tracker.js TAP ctor > name, key, and total 1`] = `
35 | Tracker {
36 | "_events": Null Object {},
37 | "_eventsCount": 0,
38 | "_maxListeners": undefined,
39 | "done": false,
40 | "key": "hellokey",
41 | "name": "hello",
42 | "total": 5,
43 | "value": 0,
44 | }
45 | `
46 |
47 | exports[`test/tracker.js TAP ctor > name, no key or total 1`] = `
48 | Tracker {
49 | "_events": Null Object {},
50 | "_eventsCount": 0,
51 | "_maxListeners": undefined,
52 | "done": false,
53 | "key": "hello",
54 | "name": "hello",
55 | "total": 100,
56 | "value": 0,
57 | }
58 | `
59 |
60 | exports[`test/tracker.js TAP emit some events > progress event 1`] = `
61 | Array [
62 | "key",
63 | Object {
64 | "done": false,
65 | "key": "key",
66 | "name": "hello",
67 | "total": 10,
68 | "value": 2,
69 | },
70 | ]
71 | `
72 |
73 | exports[`test/tracker.js TAP emit some events > progress event 2`] = `
74 | Array [
75 | "key",
76 | Object {
77 | "done": false,
78 | "key": "key",
79 | "message": "reduced total",
80 | "name": "hello",
81 | "total": 5,
82 | "value": 2,
83 | },
84 | ]
85 | `
86 |
87 | exports[`test/tracker.js TAP emit some events > progress event 3`] = `
88 | Array [
89 | "key",
90 | Object {
91 | "done": false,
92 | "key": "key",
93 | "message": "no change to total",
94 | "name": "hello",
95 | "total": 5,
96 | "value": 3,
97 | },
98 | ]
99 | `
100 |
101 | exports[`test/tracker.js TAP emit some events > progress event 4`] = `
102 | Array [
103 | "key",
104 | Object {
105 | "done": false,
106 | "key": "key",
107 | "message": "increased total",
108 | "name": "hello",
109 | "total": 100,
110 | "value": 7,
111 | },
112 | ]
113 | `
114 |
115 | exports[`test/tracker.js TAP emit some events > progress event 5`] = `
116 | Array [
117 | "key",
118 | Object {
119 | "done": false,
120 | "key": "key",
121 | "message": "reduced value",
122 | "name": "hello",
123 | "total": 200,
124 | "value": 4,
125 | },
126 | ]
127 | `
128 |
129 | exports[`test/tracker.js TAP emit some events > progress event 6`] = `
130 | Array [
131 | "key",
132 | Object {
133 | "done": true,
134 | "key": "key",
135 | "message": "implicitly done",
136 | "name": "hello",
137 | "total": 100,
138 | "value": 100,
139 | },
140 | ]
141 | `
142 |
143 | exports[`test/tracker.js TAP finish() alias for update(total, total) run with data > data received by done event 1`] = `
144 | undefined
145 | `
146 |
147 | exports[`test/tracker.js TAP finish() alias for update(total, total) run without > data received by done event 1`] = `
148 | undefined
149 | `
150 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | # This file is automatically added by @npmcli/template-oss. Do not edit.
2 |
3 | name: CI
4 |
5 | on:
6 | workflow_dispatch:
7 | pull_request:
8 | push:
9 | branches:
10 | - main
11 | schedule:
12 | # "At 09:00 UTC (02:00 PT) on Monday" https://crontab.guru/#0_9_*_*_1
13 | - cron: "0 9 * * 1"
14 |
15 | permissions:
16 | contents: read
17 |
18 | jobs:
19 | lint:
20 | name: Lint
21 | if: github.repository_owner == 'npm'
22 | runs-on: ubuntu-latest
23 | defaults:
24 | run:
25 | shell: bash
26 | steps:
27 | - name: Checkout
28 | uses: actions/checkout@v4
29 | - name: Setup Git User
30 | run: |
31 | git config --global user.email "npm-cli+bot@github.com"
32 | git config --global user.name "npm CLI robot"
33 | - name: Setup Node
34 | uses: actions/setup-node@v4
35 | id: node
36 | with:
37 | node-version: 22.x
38 | check-latest: contains('22.x', '.x')
39 | - name: Install Latest npm
40 | uses: ./.github/actions/install-latest-npm
41 | with:
42 | node: ${{ steps.node.outputs.node-version }}
43 | - name: Install Dependencies
44 | run: npm i --ignore-scripts --no-audit --no-fund
45 | - name: Lint
46 | run: npm run lint --ignore-scripts
47 | - name: Post Lint
48 | run: npm run postlint --ignore-scripts
49 |
50 | test:
51 | name: Test - ${{ matrix.platform.name }} - ${{ matrix.node-version }}
52 | if: github.repository_owner == 'npm'
53 | strategy:
54 | fail-fast: false
55 | matrix:
56 | platform:
57 | - name: Linux
58 | os: ubuntu-latest
59 | shell: bash
60 | - name: macOS
61 | os: macos-latest
62 | shell: bash
63 | - name: macOS
64 | os: macos-13
65 | shell: bash
66 | - name: Windows
67 | os: windows-latest
68 | shell: cmd
69 | node-version:
70 | - 20.17.0
71 | - 20.x
72 | - 22.9.0
73 | - 22.x
74 | exclude:
75 | - platform: { name: macOS, os: macos-13, shell: bash }
76 | node-version: 20.17.0
77 | - platform: { name: macOS, os: macos-13, shell: bash }
78 | node-version: 20.x
79 | - platform: { name: macOS, os: macos-13, shell: bash }
80 | node-version: 22.9.0
81 | - platform: { name: macOS, os: macos-13, shell: bash }
82 | node-version: 22.x
83 | runs-on: ${{ matrix.platform.os }}
84 | defaults:
85 | run:
86 | shell: ${{ matrix.platform.shell }}
87 | steps:
88 | - name: Checkout
89 | uses: actions/checkout@v4
90 | - name: Setup Git User
91 | run: |
92 | git config --global user.email "npm-cli+bot@github.com"
93 | git config --global user.name "npm CLI robot"
94 | - name: Setup Node
95 | uses: actions/setup-node@v4
96 | id: node
97 | with:
98 | node-version: ${{ matrix.node-version }}
99 | check-latest: contains(matrix.node-version, '.x')
100 | - name: Install Latest npm
101 | uses: ./.github/actions/install-latest-npm
102 | with:
103 | node: ${{ steps.node.outputs.node-version }}
104 | - name: Install Dependencies
105 | run: npm i --ignore-scripts --no-audit --no-fund
106 | - name: Add Problem Matcher
107 | run: echo "::add-matcher::.github/matchers/tap.json"
108 | - name: Test
109 | run: npm test --ignore-scripts
110 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [4.0.0](https://github.com/npm/proggy/compare/v3.0.0...v4.0.0) (2025-10-22)
4 | ### ⚠️ BREAKING CHANGES
5 | * align to npm 11 node engine range (#100)
6 | ### Bug Fixes
7 | * [`9ddf28f`](https://github.com/npm/proggy/commit/9ddf28f6b9644a87cdc4de00795441abbbbd8ec2) [#100](https://github.com/npm/proggy/pull/100) align to npm 11 node engine range (#100) (@owlstronaut)
8 | ### Chores
9 | * [`9f59807`](https://github.com/npm/proggy/commit/9f59807e53f34bfe754bc1934ddeb540ddbd6348) [#94](https://github.com/npm/proggy/pull/94) postinstall workflow updates (#94) (@owlstronaut)
10 | * [`76c68e4`](https://github.com/npm/proggy/commit/76c68e47ffccfcf2d72f413f1faa8ca636ebb108) [#99](https://github.com/npm/proggy/pull/99) bump @npmcli/template-oss from 4.26.0 to 4.27.1 (#99) (@dependabot[bot], @npm-cli-bot)
11 |
12 | ## [3.0.0](https://github.com/npm/proggy/compare/v2.0.0...v3.0.0) (2024-09-24)
13 | ### ⚠️ BREAKING CHANGES
14 | * `proggy` now supports node `^18.17.0 || >=20.5.0`
15 | ### Bug Fixes
16 | * [`9bff3e8`](https://github.com/npm/proggy/commit/9bff3e8eca0a79f9a5fb734ce63b67b7cb2f33d5) [#89](https://github.com/npm/proggy/pull/89) align to npm 10 node engine range (@hashtagchris)
17 | ### Chores
18 | * [`91e9939`](https://github.com/npm/proggy/commit/91e993936040e14a691c6a60c2a1addaf3b68a2e) [#91](https://github.com/npm/proggy/pull/91) enable auto publish (#91) (@reggi)
19 | * [`c601919`](https://github.com/npm/proggy/commit/c60191930cefa87e531b6193996e353c081856ca) [#89](https://github.com/npm/proggy/pull/89) run template-oss-apply (@hashtagchris)
20 | * [`8d9ae26`](https://github.com/npm/proggy/commit/8d9ae2664c90f80a4a98098b4bb362daf3b3e0d1) [#87](https://github.com/npm/proggy/pull/87) bump @npmcli/eslint-config from 4.0.5 to 5.0.0 (@dependabot[bot])
21 | * [`9795e99`](https://github.com/npm/proggy/commit/9795e993dc4b909f82134e67bdb17c9dc6f51ce5) [#76](https://github.com/npm/proggy/pull/76) linting: no-unused-vars (@lukekarrys)
22 | * [`36b6935`](https://github.com/npm/proggy/commit/36b69351b1a363e6427415820dadeb755b0361fa) [#76](https://github.com/npm/proggy/pull/76) bump @npmcli/template-oss to 4.22.0 (@lukekarrys)
23 | * [`ed3e179`](https://github.com/npm/proggy/commit/ed3e1798abb890a6cd1b5b4a79ceaf8bb1bc0e0e) [#36](https://github.com/npm/proggy/pull/36) bump npmlog from 6.0.2 to 7.0.0 (@dependabot[bot])
24 | * [`5b40616`](https://github.com/npm/proggy/commit/5b406161b8f18078c848171a1de34f468aac27d7) [#35](https://github.com/npm/proggy/pull/35) bump @npmcli/eslint-config from 3.1.0 to 4.0.0 (@dependabot[bot])
25 | * [`253f9d4`](https://github.com/npm/proggy/commit/253f9d492b41cbb16155ad1e02f11d98c9e442f8) [#88](https://github.com/npm/proggy/pull/88) postinstall for dependabot template-oss PR (@hashtagchris)
26 | * [`beaba03`](https://github.com/npm/proggy/commit/beaba03289b2c0bf6678004b8526b6d03be3e23a) [#88](https://github.com/npm/proggy/pull/88) bump @npmcli/template-oss from 4.23.1 to 4.23.3 (@dependabot[bot])
27 |
28 | ## [2.0.0](https://github.com/npm/proggy/compare/v1.0.0...v2.0.0) (2022-10-10)
29 |
30 | ### ⚠️ BREAKING CHANGES
31 |
32 | * `proggy` is now compatible with the following semver range for node: `^14.17.0 || ^16.13.0 || >=18.0.0`
33 |
34 | ### Features
35 |
36 | * [`870d78f`](https://github.com/npm/proggy/commit/870d78fc9a8a88ccdd6f55bbe62b8c487d054489) [#28](https://github.com/npm/proggy/pull/28) postinstall for dependabot template-oss PR (@lukekarrys)
37 |
38 | ## [1.0.0](https://www.github.com/npm/proggy/compare/v0.0.1...v1.0.0) (2022-02-16)
39 |
40 |
41 | ### ⚠ BREAKING CHANGES
42 |
43 | * this drops support for node10 and non-LTS versions of node12 and node14
44 |
45 | ### Features
46 |
47 | * @npmcli/template-oss@2.7.1 ([#1](https://www.github.com/npm/proggy/issues/1)) ([5bfa329](https://www.github.com/npm/proggy/commit/5bfa3293ef9a8771625be646b5a52b4560a766a7))
48 |
--------------------------------------------------------------------------------
/test/client.js:
--------------------------------------------------------------------------------
1 | const Client = require('../lib/client.js')
2 | const t = require('tap')
3 |
4 | t.test('basic', t => {
5 | const c = new Client()
6 | t.equal(c.stopOnDone, false, 'stopOnDone false by default')
7 | t.equal(c.normalize, false, 'normalize false by default')
8 | t.equal(c.size, 0, 'size is 0')
9 | t.equal(c.listening, false, 'not listening')
10 | c.stop()
11 | t.equal(c.listening, false, 'still not listening')
12 | c.start()
13 | t.equal(c.listening, true, 'now listening')
14 | c.start()
15 | t.equal(c.listening, true, 'still listening')
16 |
17 | c.stop()
18 | c.once('progress', () => {})
19 | t.equal(c.listening, true, 'listening because once handler added')
20 | c.emit('progress')
21 | t.equal(c.listening, false, 'not listening because once handler removed')
22 |
23 | c.addListener('progress', (key, data) => t.matchSnapshot([key, data], 'progress data'))
24 | t.equal(c.listening, true, 'listening, because progress event handler added')
25 |
26 | let sawProgress = false
27 | c.once('progress', () => {
28 | sawProgress = true
29 | })
30 |
31 | let sawBarDone = false
32 | c.once('barDone', (key, data) => {
33 | sawBarDone = true
34 | t.matchSnapshot([key, data], 'barDone data')
35 | })
36 |
37 | let sawBar = false
38 | c.once('bar', (key, data) => {
39 | sawBar = true
40 | t.matchSnapshot([key, data], 'bar data')
41 | })
42 |
43 | process.emit('progress', 'key', { hello: 'world', value: 1, total: 99 })
44 | t.equal(c.size, 1, '1 progress bar now')
45 | t.equal(sawBar, true, 'saw bar event')
46 | t.equal(sawProgress, true, 'saw progress event')
47 |
48 | process.emit('progress', 'yek', { hello: 'world', value: 5, total: 50 })
49 | t.equal(c.size, 2, 'got a second bar')
50 |
51 | process.emit('progress', 'key', { hello: 'world', value: 100, total: 50 })
52 | t.equal(c.size, 1, 'first progress bar ended')
53 | t.equal(sawBarDone, true, 'saw barDone event')
54 |
55 | process.emit('progress', 'yek', { hello: 'world', value: 100, total: 50 })
56 | t.equal(c.size, 0, 'second progress bar ended, all done')
57 |
58 | process.emit('progress', 'foo', { hello: 'world', value: 100, total: 50 })
59 | t.equal(c.size, 0, 'progress bar ended right away')
60 |
61 | c.off('progress', c.listeners('progress')[0])
62 | t.equal(c.listening, false, 'stopped because last listener removed')
63 |
64 | t.end()
65 | })
66 |
67 | t.test('stop on done', t => {
68 | const c = new Client({ stopOnDone: true })
69 | c.on('progress', (key, data) => t.matchSnapshot([key, data], 'progress data'))
70 | process.emit('progress', 'key', { hello: 'world', value: 1, total: 99 })
71 | process.emit('progress', 'key', { hello: 'world', value: 100, total: 100 })
72 | t.equal(c.listening, false, 'not listening, because last bar done')
73 | t.end()
74 | })
75 |
76 | t.test('normalize data', t => {
77 | const c = new Client({ normalize: true })
78 | c.on('progress', (key, data) => t.matchSnapshot([key, data], 'progress data'))
79 | c.on('barDone', () => t.end())
80 | process.emit('progress', 'key', { value: 1, total: 5 })
81 | process.emit('progress', 'key', { value: 2, total: 12 })
82 | process.emit('progress', 'key', { value: 3, total: 12 })
83 | process.emit('progress', 'key', { value: 4, total: 13 })
84 | process.emit('progress', 'key', { value: 6, total: 13 })
85 | process.emit('progress', 'key', { value: 3, total: 6 })
86 | process.emit('progress', 'key', { value: 5, total: 6 })
87 | process.emit('progress', 'key', { value: 45, total: 50 })
88 | process.emit('progress', 'key', { value: 45, total: 50 })
89 | process.emit('progress', 'key', { value: 45, total: 50 })
90 | process.emit('progress', 'key', { value: 45, total: 50 })
91 | process.emit('progress', 'key', { value: 45, total: 50 })
92 | process.emit('progress', 'key', { value: 45, total: 50 })
93 | process.emit('progress', 'key', { value: 45, total: 50 })
94 | process.emit('progress', 'key', { value: 45, total: 50 })
95 | process.emit('progress', 'key', { value: 9001, total: 420 })
96 | c.stop()
97 | })
98 |
--------------------------------------------------------------------------------
/.github/workflows/ci-release.yml:
--------------------------------------------------------------------------------
1 | # This file is automatically added by @npmcli/template-oss. Do not edit.
2 |
3 | name: CI - Release
4 |
5 | on:
6 | workflow_dispatch:
7 | inputs:
8 | ref:
9 | required: true
10 | type: string
11 | default: main
12 | workflow_call:
13 | inputs:
14 | ref:
15 | required: true
16 | type: string
17 | check-sha:
18 | required: true
19 | type: string
20 |
21 | permissions:
22 | contents: read
23 | checks: write
24 |
25 | jobs:
26 | lint-all:
27 | name: Lint All
28 | if: github.repository_owner == 'npm'
29 | runs-on: ubuntu-latest
30 | defaults:
31 | run:
32 | shell: bash
33 | steps:
34 | - name: Checkout
35 | uses: actions/checkout@v4
36 | with:
37 | ref: ${{ inputs.ref }}
38 | - name: Setup Git User
39 | run: |
40 | git config --global user.email "npm-cli+bot@github.com"
41 | git config --global user.name "npm CLI robot"
42 | - name: Create Check
43 | id: create-check
44 | if: ${{ inputs.check-sha }}
45 | uses: ./.github/actions/create-check
46 | with:
47 | name: "Lint All"
48 | token: ${{ secrets.GITHUB_TOKEN }}
49 | sha: ${{ inputs.check-sha }}
50 | - name: Setup Node
51 | uses: actions/setup-node@v4
52 | id: node
53 | with:
54 | node-version: 22.x
55 | check-latest: contains('22.x', '.x')
56 | - name: Install Latest npm
57 | uses: ./.github/actions/install-latest-npm
58 | with:
59 | node: ${{ steps.node.outputs.node-version }}
60 | - name: Install Dependencies
61 | run: npm i --ignore-scripts --no-audit --no-fund
62 | - name: Lint
63 | run: npm run lint --ignore-scripts
64 | - name: Post Lint
65 | run: npm run postlint --ignore-scripts
66 | - name: Conclude Check
67 | uses: LouisBrunner/checks-action@v1.6.0
68 | if: steps.create-check.outputs.check-id && always()
69 | with:
70 | token: ${{ secrets.GITHUB_TOKEN }}
71 | conclusion: ${{ job.status }}
72 | check_id: ${{ steps.create-check.outputs.check-id }}
73 |
74 | test-all:
75 | name: Test All - ${{ matrix.platform.name }} - ${{ matrix.node-version }}
76 | if: github.repository_owner == 'npm'
77 | strategy:
78 | fail-fast: false
79 | matrix:
80 | platform:
81 | - name: Linux
82 | os: ubuntu-latest
83 | shell: bash
84 | - name: macOS
85 | os: macos-latest
86 | shell: bash
87 | - name: macOS
88 | os: macos-13
89 | shell: bash
90 | - name: Windows
91 | os: windows-latest
92 | shell: cmd
93 | node-version:
94 | - 20.17.0
95 | - 20.x
96 | - 22.9.0
97 | - 22.x
98 | exclude:
99 | - platform: { name: macOS, os: macos-13, shell: bash }
100 | node-version: 20.17.0
101 | - platform: { name: macOS, os: macos-13, shell: bash }
102 | node-version: 20.x
103 | - platform: { name: macOS, os: macos-13, shell: bash }
104 | node-version: 22.9.0
105 | - platform: { name: macOS, os: macos-13, shell: bash }
106 | node-version: 22.x
107 | runs-on: ${{ matrix.platform.os }}
108 | defaults:
109 | run:
110 | shell: ${{ matrix.platform.shell }}
111 | steps:
112 | - name: Checkout
113 | uses: actions/checkout@v4
114 | with:
115 | ref: ${{ inputs.ref }}
116 | - name: Setup Git User
117 | run: |
118 | git config --global user.email "npm-cli+bot@github.com"
119 | git config --global user.name "npm CLI robot"
120 | - name: Create Check
121 | id: create-check
122 | if: ${{ inputs.check-sha }}
123 | uses: ./.github/actions/create-check
124 | with:
125 | name: "Test All - ${{ matrix.platform.name }} - ${{ matrix.node-version }}"
126 | token: ${{ secrets.GITHUB_TOKEN }}
127 | sha: ${{ inputs.check-sha }}
128 | - name: Setup Node
129 | uses: actions/setup-node@v4
130 | id: node
131 | with:
132 | node-version: ${{ matrix.node-version }}
133 | check-latest: contains(matrix.node-version, '.x')
134 | - name: Install Latest npm
135 | uses: ./.github/actions/install-latest-npm
136 | with:
137 | node: ${{ steps.node.outputs.node-version }}
138 | - name: Install Dependencies
139 | run: npm i --ignore-scripts --no-audit --no-fund
140 | - name: Add Problem Matcher
141 | run: echo "::add-matcher::.github/matchers/tap.json"
142 | - name: Test
143 | run: npm test --ignore-scripts
144 | - name: Conclude Check
145 | uses: LouisBrunner/checks-action@v1.6.0
146 | if: steps.create-check.outputs.check-id && always()
147 | with:
148 | token: ${{ secrets.GITHUB_TOKEN }}
149 | conclusion: ${{ job.status }}
150 | check_id: ${{ steps.create-check.outputs.check-id }}
151 |
--------------------------------------------------------------------------------
/.github/workflows/post-dependabot.yml:
--------------------------------------------------------------------------------
1 | # This file is automatically added by @npmcli/template-oss. Do not edit.
2 |
3 | name: Post Dependabot
4 |
5 | on: pull_request
6 |
7 | permissions:
8 | contents: write
9 |
10 | jobs:
11 | template-oss:
12 | name: template-oss
13 | if: github.repository_owner == 'npm' && github.actor == 'dependabot[bot]'
14 | runs-on: ubuntu-latest
15 | defaults:
16 | run:
17 | shell: bash
18 | steps:
19 | - name: Checkout
20 | uses: actions/checkout@v4
21 | with:
22 | ref: ${{ github.event.pull_request.head.ref }}
23 | - name: Setup Git User
24 | run: |
25 | git config --global user.email "npm-cli+bot@github.com"
26 | git config --global user.name "npm CLI robot"
27 | - name: Setup Node
28 | uses: actions/setup-node@v4
29 | id: node
30 | with:
31 | node-version: 22.x
32 | check-latest: contains('22.x', '.x')
33 | - name: Install Latest npm
34 | uses: ./.github/actions/install-latest-npm
35 | with:
36 | node: ${{ steps.node.outputs.node-version }}
37 | - name: Install Dependencies
38 | run: npm i --ignore-scripts --no-audit --no-fund
39 | - name: Fetch Dependabot Metadata
40 | id: metadata
41 | uses: dependabot/fetch-metadata@v1
42 | with:
43 | github-token: ${{ secrets.GITHUB_TOKEN }}
44 |
45 | # Dependabot can update multiple directories so we output which directory
46 | # it is acting on so we can run the command for the correct root or workspace
47 | - name: Get Dependabot Directory
48 | if: contains(steps.metadata.outputs.dependency-names, '@npmcli/template-oss')
49 | id: flags
50 | run: |
51 | dependabot_dir="${{ steps.metadata.outputs.directory }}"
52 | if [[ "$dependabot_dir" == "/" || "$dependabot_dir" == "/main" ]]; then
53 | echo "workspace=-iwr" >> $GITHUB_OUTPUT
54 | else
55 | # strip leading slash from directory so it works as a
56 | # a path to the workspace flag
57 | echo "workspace=--workspace ${dependabot_dir#/}" >> $GITHUB_OUTPUT
58 | fi
59 |
60 | - name: Apply Changes
61 | if: steps.flags.outputs.workspace
62 | id: apply
63 | run: |
64 | npm run template-oss-apply ${{ steps.flags.outputs.workspace }}
65 | if [[ `git status --porcelain` ]]; then
66 | echo "changes=true" >> $GITHUB_OUTPUT
67 | fi
68 | # This only sets the conventional commit prefix. This workflow can't reliably determine
69 | # what the breaking change is though. If a BREAKING CHANGE message is required then
70 | # this PR check will fail and the commit will be amended with stafftools
71 | if [[ "${{ steps.metadata.outputs.update-type }}" == "version-update:semver-major" ]]; then
72 | prefix='feat!'
73 | else
74 | prefix='chore'
75 | fi
76 | echo "message=$prefix: postinstall for dependabot template-oss PR" >> $GITHUB_OUTPUT
77 |
78 | # This step will fail if template-oss has made any workflow updates. It is impossible
79 | # for a workflow to update other workflows. In the case it does fail, we continue
80 | # and then try to apply only a portion of the changes in the next step
81 | - name: Push All Changes
82 | if: steps.apply.outputs.changes
83 | id: push
84 | continue-on-error: true
85 | env:
86 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
87 | run: |
88 | git commit -am "${{ steps.apply.outputs.message }}"
89 | git push
90 |
91 | # If the previous step failed, then reset the commit and remove any workflow changes
92 | # and attempt to commit and push again. This is helpful because we will have a commit
93 | # with the correct prefix that we can then --amend with @npmcli/stafftools later.
94 | - name: Push All Changes Except Workflows
95 | if: steps.apply.outputs.changes && steps.push.outcome == 'failure'
96 | env:
97 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
98 | run: |
99 | git reset HEAD~
100 | git checkout HEAD -- .github/workflows/
101 | git clean -fd .github/workflows/
102 | git commit -am "${{ steps.apply.outputs.message }}"
103 | git push
104 |
105 | # Check if all the necessary template-oss changes were applied. Since we continued
106 | # on errors in one of the previous steps, this check will fail if our follow up
107 | # only applied a portion of the changes and we need to followup manually.
108 | #
109 | # Note that this used to run `lint` and `postlint` but that will fail this action
110 | # if we've also shipped any linting changes separate from template-oss. We do
111 | # linting in another action, so we want to fail this one only if there are
112 | # template-oss changes that could not be applied.
113 | - name: Check Changes
114 | if: steps.apply.outputs.changes
115 | run: |
116 | npm exec --offline ${{ steps.flags.outputs.workspace }} -- template-oss-check
117 |
118 | - name: Fail on Breaking Change
119 | if: steps.apply.outputs.changes && startsWith(steps.apply.outputs.message, 'feat!')
120 | run: |
121 | echo "This PR has a breaking change. Run 'npx -p @npmcli/stafftools gh template-oss-fix'"
122 | echo "for more information on how to fix this with a BREAKING CHANGE footer."
123 | exit 1
124 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # proggy
2 |
3 | Progress bar updates at a distance
4 |
5 | A decoupled progress bar connector component that lets you emit events on
6 | the process object to provide progress updates from various parts of a
7 | program.
8 |
9 | ## USAGE
10 |
11 | Somewhere in your program, you have a thing that is performing actions that
12 | take a while.
13 |
14 | In there, you will emit `progress` events on the global `process` object
15 | using Proggy.
16 |
17 | ```js
18 | const proggy = require('proggy')
19 | const doSomething = async (listOfItems) => {
20 | // This name should be unique within your program.
21 | // Proggy will do its best to make sure that you don't create the same
22 | // tracker more than once by throwing if you give it a name it's already
23 | // seen, but if there are multiple instances of Proggy, it won't be able
24 | // to guarantee it.
25 | const tracker = proggy.createTracker('doing something')
26 | let i = 0
27 | for (const item of listOfItems) {
28 | tracker.update(i++, listOfItems.length)
29 | const result = await doSomething(item)
30 |
31 | // changing the length is allowed! The progress bar will never go
32 | // backwards, but it will slow down if the total increases.
33 | if (result.more)
34 | listOfItems.push(result.more)
35 | }
36 | // can either explicitly end, or let it implicitly end when the value
37 | // is >= the total.
38 | tracker.end()
39 | }
40 | ```
41 |
42 | Proggy is not a UI component. It is a way to decouple the UI of a progress
43 | bar from the thing that is making the actual progress.
44 |
45 | In another part of your program, where you are handling showing stuff to
46 | the user, you can display this information using any of the wonderful
47 | progress bar UI modules available on npm.
48 |
49 | ```js
50 | const proggy = require('proggy')
51 |
52 | // Create a client that can consume the events emitted elsewhere
53 | const client = proggy.createClient()
54 | ```
55 |
56 | If you set the `normalize: true` flag, then the client will normalize how
57 | far it thinks the progress should have gone, in order to prevent backwards
58 | motion if the length increases along the way. If using this, then the
59 | `total` value will always be set to 100, and the `increment` value will
60 | always be some number smaller than 100. Use the `actualValue` and
61 | `actualTotal` fields to identify how many things have actually been done.
62 |
63 | For example, using
64 | [`cli-progress`](https://www.npmjs.com/package/cli-progress):
65 |
66 | ```js
67 | // set up our display thingmajig
68 | const cliProgress = require('cli-progress')
69 | const multibar = new cliProgress.MultiBar({
70 | clearOnComplete: true,
71 | hideCursor: true,
72 | // note that data.actualValue and data.actualTotal will reflect the real
73 | // done/remaining values. data.value will always be less than 100, and
74 | // data.total will always be 100, so we never show reverse motion.
75 | format: '[{bar}] {percentage}% | {name} {actualValue}/{actualTotal} {duration_formatted}',
76 | barCompleteChar: '\u2588',
77 | barIncompleteChar: '\u2591',
78 | }, cliProgress.Presets.shades_grey)
79 |
80 | // update it with events from the proggy client
81 | const client = proggy.createClient({
82 | // don't show reverse progress
83 | // this is false by default
84 | normalize: true,
85 | })
86 | const bars = {}
87 | // new bar is created, tell multibar about it
88 | client.on('bar', (key, data) => {
89 | bars[key] = multibar.create(data.total)
90 | })
91 | // got some progress for a progress bar, tell multibar to show it
92 | client.on('progress', (key, data) => {
93 | bars[key].update(data.value, data)
94 | bars[key].setTotal(data.total)
95 | })
96 | // a bar is done, tell multibar to stop updating it
97 | client.on('barDone', (key, data) => {
98 | bars[key].stop()
99 | })
100 | // all bars done, tell multibar to close entirely
101 | client.on('done', () => {
102 | multibar.stop()
103 | })
104 | ```
105 |
106 | ## API
107 |
108 | ### `proggy.createTracker(name, [key], [total])`
109 |
110 | Create a new `Tracker`.
111 |
112 | `key` will default to `name` if not set. It must be unique.
113 |
114 | ### `new proggy.Tracker(name, [key], [total])`
115 |
116 | The Tracker class, for emitting progress information.
117 |
118 | `total` will default to `100` if not set, but will be updated whenever
119 | progress is tracked.
120 |
121 | #### Fields
122 |
123 | * `name`, `key` - The name and identifer values set in the constructor
124 | * `done` - `true` if the tracker is completed.
125 | * `total` `value` - The most recent values updated.
126 |
127 | #### `tracker.update(value, total, [metadata])`
128 |
129 | Update the tracker and emit a `'progress'` event on the `process` object.
130 |
131 | #### `tracker.finish([metadata])`
132 |
133 | Alias for `tracker.update(tracker.total, tracker.total, metadata)`
134 |
135 | ### `proggy.createClient(options)`
136 |
137 | Create a new `Client`.
138 |
139 | ### `new proggy.Client({ normalize = false, stopOnDone = false })`
140 |
141 | The Client class, for consuming progress information.
142 |
143 | Set `normalize: true` in the options object to prevent backward motion and
144 | fix the `total` value at `100`.
145 |
146 | Set `stopOnDone: true` in the options object to tell the client to
147 | automatically stop when it emits its `'done'` event.
148 |
149 | #### Fields
150 |
151 | * `normalize` - whether this Client is in normalizing mode
152 | * `size` - the number of active trackers this Client is aware of
153 |
154 | #### Events
155 |
156 | * `client.on('bar', (key, data) => {})` - Emitted when a new progress bar
157 | is encountered.
158 | * `client.on('progress', (key, data) => {})` - Emitted when an update is
159 | available for a given progress bar.
160 | * `client.on('barDone', (key, data) => {})` - Emitted when a progress bar
161 | is completed.
162 | * `client.on('done', () => {})` - Emitted when all progress bars are
163 | completed.
164 |
165 | #### `client.start()`
166 |
167 | Begin listening for `'progress'` events on the `process` object.
168 |
169 | Called implicitly if `client.on('progress', fn)` is called.
170 |
171 | #### `client.stop()`
172 |
173 | Stop listening for `'progress'` events on the `process` object.
174 |
175 | Called implicitly when the `'done'` event is emitted, if
176 | `client.stopOnDone` is true.
177 |
178 | #### Progress Data
179 |
180 | All client events receive data objects containing the following fields, in
181 | addition to whatever else was sent by the `tracker`.
182 |
183 | * `key` - The unique key for this progress bar.
184 | * `name` - The name for this progress bar.
185 | * `value` - The current value of the progress. If `client.normalize` is
186 | true, then this will always be a number less than `100`, and never reduce
187 | from one update to the next.
188 | * `total` - The expected final value of the progress. If
189 | `client.normalize` is true, then this number will always be `100`.
190 | * `actualValue` - The value originally sent by the tracker. If
191 | `client.normalize` is not true, then this is always equal to `value`.
192 | * `actualTotal` - The total originally sent by the tracker. If
193 | `client.normalize` is not true, then this is always equal to `total`.
194 | * `done` - True if the tracker explicitly sent `done: true` in the data, or
195 | if `value` is greater than or equal to `total`.
196 | * Whatever other fields were set on the `metadata` sent by the tracker.
197 |
--------------------------------------------------------------------------------
/tap-snapshots/test/client.js.test.cjs:
--------------------------------------------------------------------------------
1 | /* IMPORTANT
2 | * This snapshot file is auto-generated, but designed for humans.
3 | * It should be checked into source control and tracked carefully.
4 | * Re-generate by setting TAP_SNAPSHOT=1 and running tests.
5 | * Make sure to inspect the output below. Do not ignore changes!
6 | */
7 | 'use strict'
8 | exports[`test/client.js TAP basic > bar data 1`] = `
9 | Array [
10 | "key",
11 | Object {
12 | "actualTotal": 99,
13 | "actualValue": 1,
14 | "done": false,
15 | "hello": "world",
16 | "key": "key",
17 | "name": "key",
18 | "total": 99,
19 | "value": 1,
20 | },
21 | ]
22 | `
23 |
24 | exports[`test/client.js TAP basic > barDone data 1`] = `
25 | Array [
26 | "key",
27 | Object {
28 | "actualTotal": 50,
29 | "actualValue": 100,
30 | "done": true,
31 | "hello": "world",
32 | "key": "key",
33 | "name": "key",
34 | "total": 50,
35 | "value": 100,
36 | },
37 | ]
38 | `
39 |
40 | exports[`test/client.js TAP basic > progress data 1`] = `
41 | Array [
42 | "key",
43 | Object {
44 | "actualTotal": 99,
45 | "actualValue": 1,
46 | "done": false,
47 | "hello": "world",
48 | "key": "key",
49 | "name": "key",
50 | "total": 99,
51 | "value": 1,
52 | },
53 | ]
54 | `
55 |
56 | exports[`test/client.js TAP basic > progress data 2`] = `
57 | Array [
58 | "yek",
59 | Object {
60 | "actualTotal": 50,
61 | "actualValue": 5,
62 | "done": false,
63 | "hello": "world",
64 | "key": "yek",
65 | "name": "yek",
66 | "total": 50,
67 | "value": 5,
68 | },
69 | ]
70 | `
71 |
72 | exports[`test/client.js TAP basic > progress data 3`] = `
73 | Array [
74 | "key",
75 | Object {
76 | "actualTotal": 50,
77 | "actualValue": 100,
78 | "done": true,
79 | "hello": "world",
80 | "key": "key",
81 | "name": "key",
82 | "total": 50,
83 | "value": 100,
84 | },
85 | ]
86 | `
87 |
88 | exports[`test/client.js TAP basic > progress data 4`] = `
89 | Array [
90 | "yek",
91 | Object {
92 | "actualTotal": 50,
93 | "actualValue": 100,
94 | "done": true,
95 | "hello": "world",
96 | "key": "yek",
97 | "name": "yek",
98 | "total": 50,
99 | "value": 100,
100 | },
101 | ]
102 | `
103 |
104 | exports[`test/client.js TAP basic > progress data 5`] = `
105 | Array [
106 | "foo",
107 | Object {
108 | "actualTotal": 50,
109 | "actualValue": 100,
110 | "done": true,
111 | "hello": "world",
112 | "key": "foo",
113 | "name": "foo",
114 | "total": 50,
115 | "value": 100,
116 | },
117 | ]
118 | `
119 |
120 | exports[`test/client.js TAP normalize data > progress data 1`] = `
121 | Array [
122 | "key",
123 | Object {
124 | "actualTotal": 5,
125 | "actualValue": 1,
126 | "done": false,
127 | "key": "key",
128 | "name": "key",
129 | "total": 100,
130 | "value": 1,
131 | },
132 | ]
133 | `
134 |
135 | exports[`test/client.js TAP normalize data > progress data 10`] = `
136 | Array [
137 | "key",
138 | Object {
139 | "actualTotal": 50,
140 | "actualValue": 45,
141 | "done": false,
142 | "key": "key",
143 | "name": "key",
144 | "total": 100,
145 | "value": 90.199,
146 | },
147 | ]
148 | `
149 |
150 | exports[`test/client.js TAP normalize data > progress data 11`] = `
151 | Array [
152 | "key",
153 | Object {
154 | "actualTotal": 50,
155 | "actualValue": 45,
156 | "done": false,
157 | "key": "key",
158 | "name": "key",
159 | "total": 100,
160 | "value": 90.29701,
161 | },
162 | ]
163 | `
164 |
165 | exports[`test/client.js TAP normalize data > progress data 12`] = `
166 | Array [
167 | "key",
168 | Object {
169 | "actualTotal": 50,
170 | "actualValue": 45,
171 | "done": false,
172 | "key": "key",
173 | "name": "key",
174 | "total": 100,
175 | "value": 90.3940399,
176 | },
177 | ]
178 | `
179 |
180 | exports[`test/client.js TAP normalize data > progress data 13`] = `
181 | Array [
182 | "key",
183 | Object {
184 | "actualTotal": 50,
185 | "actualValue": 45,
186 | "done": false,
187 | "key": "key",
188 | "name": "key",
189 | "total": 100,
190 | "value": 90.49009950099999,
191 | },
192 | ]
193 | `
194 |
195 | exports[`test/client.js TAP normalize data > progress data 14`] = `
196 | Array [
197 | "key",
198 | Object {
199 | "actualTotal": 50,
200 | "actualValue": 45,
201 | "done": false,
202 | "key": "key",
203 | "name": "key",
204 | "total": 100,
205 | "value": 90.58519850599,
206 | },
207 | ]
208 | `
209 |
210 | exports[`test/client.js TAP normalize data > progress data 15`] = `
211 | Array [
212 | "key",
213 | Object {
214 | "actualTotal": 50,
215 | "actualValue": 45,
216 | "done": false,
217 | "key": "key",
218 | "name": "key",
219 | "total": 100,
220 | "value": 90.6793465209301,
221 | },
222 | ]
223 | `
224 |
225 | exports[`test/client.js TAP normalize data > progress data 16`] = `
226 | Array [
227 | "key",
228 | Object {
229 | "actualTotal": 420,
230 | "actualValue": 9001,
231 | "done": true,
232 | "key": "key",
233 | "name": "key",
234 | "total": 100,
235 | "value": 100,
236 | },
237 | ]
238 | `
239 |
240 | exports[`test/client.js TAP normalize data > progress data 2`] = `
241 | Array [
242 | "key",
243 | Object {
244 | "actualTotal": 12,
245 | "actualValue": 2,
246 | "done": false,
247 | "key": "key",
248 | "name": "key",
249 | "total": 100,
250 | "value": 16.666666666666668,
251 | },
252 | ]
253 | `
254 |
255 | exports[`test/client.js TAP normalize data > progress data 3`] = `
256 | Array [
257 | "key",
258 | Object {
259 | "actualTotal": 12,
260 | "actualValue": 3,
261 | "done": false,
262 | "key": "key",
263 | "name": "key",
264 | "total": 100,
265 | "value": 25,
266 | },
267 | ]
268 | `
269 |
270 | exports[`test/client.js TAP normalize data > progress data 4`] = `
271 | Array [
272 | "key",
273 | Object {
274 | "actualTotal": 13,
275 | "actualValue": 4,
276 | "done": false,
277 | "key": "key",
278 | "name": "key",
279 | "total": 100,
280 | "value": 30.76923076923077,
281 | },
282 | ]
283 | `
284 |
285 | exports[`test/client.js TAP normalize data > progress data 5`] = `
286 | Array [
287 | "key",
288 | Object {
289 | "actualTotal": 13,
290 | "actualValue": 6,
291 | "done": false,
292 | "key": "key",
293 | "name": "key",
294 | "total": 100,
295 | "value": 46.15384615384615,
296 | },
297 | ]
298 | `
299 |
300 | exports[`test/client.js TAP normalize data > progress data 6`] = `
301 | Array [
302 | "key",
303 | Object {
304 | "actualTotal": 6,
305 | "actualValue": 3,
306 | "done": false,
307 | "key": "key",
308 | "name": "key",
309 | "total": 100,
310 | "value": 50,
311 | },
312 | ]
313 | `
314 |
315 | exports[`test/client.js TAP normalize data > progress data 7`] = `
316 | Array [
317 | "key",
318 | Object {
319 | "actualTotal": 6,
320 | "actualValue": 5,
321 | "done": false,
322 | "key": "key",
323 | "name": "key",
324 | "total": 100,
325 | "value": 83.33333333333333,
326 | },
327 | ]
328 | `
329 |
330 | exports[`test/client.js TAP normalize data > progress data 8`] = `
331 | Array [
332 | "key",
333 | Object {
334 | "actualTotal": 50,
335 | "actualValue": 45,
336 | "done": false,
337 | "key": "key",
338 | "name": "key",
339 | "total": 100,
340 | "value": 90,
341 | },
342 | ]
343 | `
344 |
345 | exports[`test/client.js TAP normalize data > progress data 9`] = `
346 | Array [
347 | "key",
348 | Object {
349 | "actualTotal": 50,
350 | "actualValue": 45,
351 | "done": false,
352 | "key": "key",
353 | "name": "key",
354 | "total": 100,
355 | "value": 90.1,
356 | },
357 | ]
358 | `
359 |
360 | exports[`test/client.js TAP stop on done > progress data 1`] = `
361 | Array [
362 | "key",
363 | Object {
364 | "actualTotal": 99,
365 | "actualValue": 1,
366 | "done": false,
367 | "hello": "world",
368 | "key": "key",
369 | "name": "key",
370 | "total": 99,
371 | "value": 1,
372 | },
373 | ]
374 | `
375 |
376 | exports[`test/client.js TAP stop on done > progress data 2`] = `
377 | Array [
378 | "key",
379 | Object {
380 | "actualTotal": 100,
381 | "actualValue": 100,
382 | "done": true,
383 | "hello": "world",
384 | "key": "key",
385 | "name": "key",
386 | "total": 100,
387 | "value": 100,
388 | },
389 | ]
390 | `
391 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | # This file is automatically added by @npmcli/template-oss. Do not edit.
2 |
3 | name: Release
4 |
5 | on:
6 | push:
7 | branches:
8 | - main
9 |
10 | permissions:
11 | contents: write
12 | pull-requests: write
13 | checks: write
14 |
15 | jobs:
16 | release:
17 | outputs:
18 | pr: ${{ steps.release.outputs.pr }}
19 | pr-branch: ${{ steps.release.outputs.pr-branch }}
20 | pr-number: ${{ steps.release.outputs.pr-number }}
21 | pr-sha: ${{ steps.release.outputs.pr-sha }}
22 | releases: ${{ steps.release.outputs.releases }}
23 | comment-id: ${{ steps.create-comment.outputs.comment-id || steps.update-comment.outputs.comment-id }}
24 | check-id: ${{ steps.create-check.outputs.check-id }}
25 | name: Release
26 | if: github.repository_owner == 'npm'
27 | runs-on: ubuntu-latest
28 | defaults:
29 | run:
30 | shell: bash
31 | steps:
32 | - name: Checkout
33 | uses: actions/checkout@v4
34 | - name: Setup Git User
35 | run: |
36 | git config --global user.email "npm-cli+bot@github.com"
37 | git config --global user.name "npm CLI robot"
38 | - name: Setup Node
39 | uses: actions/setup-node@v4
40 | id: node
41 | with:
42 | node-version: 22.x
43 | check-latest: contains('22.x', '.x')
44 | - name: Install Latest npm
45 | uses: ./.github/actions/install-latest-npm
46 | with:
47 | node: ${{ steps.node.outputs.node-version }}
48 | - name: Install Dependencies
49 | run: npm i --ignore-scripts --no-audit --no-fund
50 | - name: Release Please
51 | id: release
52 | env:
53 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
54 | run: npx --offline template-oss-release-please --branch="${{ github.ref_name }}" --backport="" --defaultTag="latest"
55 | - name: Create Release Manager Comment Text
56 | if: steps.release.outputs.pr-number
57 | uses: actions/github-script@v7
58 | id: comment-text
59 | with:
60 | result-encoding: string
61 | script: |
62 | const { runId, repo: { owner, repo } } = context
63 | const { data: workflow } = await github.rest.actions.getWorkflowRun({ owner, repo, run_id: runId })
64 | return['## Release Manager', `Release workflow run: ${workflow.html_url}`].join('\n\n')
65 | - name: Find Release Manager Comment
66 | uses: peter-evans/find-comment@v2
67 | if: steps.release.outputs.pr-number
68 | id: found-comment
69 | with:
70 | issue-number: ${{ steps.release.outputs.pr-number }}
71 | comment-author: 'github-actions[bot]'
72 | body-includes: '## Release Manager'
73 | - name: Create Release Manager Comment
74 | id: create-comment
75 | if: steps.release.outputs.pr-number && !steps.found-comment.outputs.comment-id
76 | uses: peter-evans/create-or-update-comment@v3
77 | with:
78 | issue-number: ${{ steps.release.outputs.pr-number }}
79 | body: ${{ steps.comment-text.outputs.result }}
80 | - name: Update Release Manager Comment
81 | id: update-comment
82 | if: steps.release.outputs.pr-number && steps.found-comment.outputs.comment-id
83 | uses: peter-evans/create-or-update-comment@v3
84 | with:
85 | comment-id: ${{ steps.found-comment.outputs.comment-id }}
86 | body: ${{ steps.comment-text.outputs.result }}
87 | edit-mode: 'replace'
88 | - name: Create Check
89 | id: create-check
90 | uses: ./.github/actions/create-check
91 | if: steps.release.outputs.pr-sha
92 | with:
93 | name: "Release"
94 | token: ${{ secrets.GITHUB_TOKEN }}
95 | sha: ${{ steps.release.outputs.pr-sha }}
96 |
97 | update:
98 | needs: release
99 | outputs:
100 | sha: ${{ steps.commit.outputs.sha }}
101 | check-id: ${{ steps.create-check.outputs.check-id }}
102 | name: Update - Release
103 | if: github.repository_owner == 'npm' && needs.release.outputs.pr
104 | runs-on: ubuntu-latest
105 | defaults:
106 | run:
107 | shell: bash
108 | steps:
109 | - name: Checkout
110 | uses: actions/checkout@v4
111 | with:
112 | fetch-depth: 0
113 | ref: ${{ needs.release.outputs.pr-branch }}
114 | - name: Setup Git User
115 | run: |
116 | git config --global user.email "npm-cli+bot@github.com"
117 | git config --global user.name "npm CLI robot"
118 | - name: Setup Node
119 | uses: actions/setup-node@v4
120 | id: node
121 | with:
122 | node-version: 22.x
123 | check-latest: contains('22.x', '.x')
124 | - name: Install Latest npm
125 | uses: ./.github/actions/install-latest-npm
126 | with:
127 | node: ${{ steps.node.outputs.node-version }}
128 | - name: Install Dependencies
129 | run: npm i --ignore-scripts --no-audit --no-fund
130 | - name: Create Release Manager Checklist Text
131 | id: comment-text
132 | env:
133 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
134 | run: npm exec --offline -- template-oss-release-manager --pr="${{ needs.release.outputs.pr-number }}" --backport="" --defaultTag="latest" --publish
135 | - name: Append Release Manager Comment
136 | uses: peter-evans/create-or-update-comment@v3
137 | with:
138 | comment-id: ${{ needs.release.outputs.comment-id }}
139 | body: ${{ steps.comment-text.outputs.result }}
140 | edit-mode: 'append'
141 | - name: Run Post Pull Request Actions
142 | env:
143 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
144 | run: npm run rp-pull-request --ignore-scripts --if-present -- --pr="${{ needs.release.outputs.pr-number }}" --commentId="${{ needs.release.outputs.comment-id }}"
145 | - name: Commit
146 | id: commit
147 | env:
148 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
149 | run: |
150 | git commit --all --amend --no-edit || true
151 | git push --force-with-lease
152 | echo "sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
153 | - name: Create Check
154 | id: create-check
155 | uses: ./.github/actions/create-check
156 | with:
157 | name: "Update - Release"
158 | check-name: "Release"
159 | token: ${{ secrets.GITHUB_TOKEN }}
160 | sha: ${{ steps.commit.outputs.sha }}
161 | - name: Conclude Check
162 | uses: LouisBrunner/checks-action@v1.6.0
163 | with:
164 | token: ${{ secrets.GITHUB_TOKEN }}
165 | conclusion: ${{ job.status }}
166 | check_id: ${{ needs.release.outputs.check-id }}
167 |
168 | ci:
169 | name: CI - Release
170 | needs: [ release, update ]
171 | if: needs.release.outputs.pr
172 | uses: ./.github/workflows/ci-release.yml
173 | with:
174 | ref: ${{ needs.release.outputs.pr-branch }}
175 | check-sha: ${{ needs.update.outputs.sha }}
176 |
177 | post-ci:
178 | needs: [ release, update, ci ]
179 | name: Post CI - Release
180 | if: github.repository_owner == 'npm' && needs.release.outputs.pr && always()
181 | runs-on: ubuntu-latest
182 | defaults:
183 | run:
184 | shell: bash
185 | steps:
186 | - name: Get CI Conclusion
187 | id: conclusion
188 | run: |
189 | result=""
190 | if [[ "${{ contains(needs.*.result, 'failure') }}" == "true" ]]; then
191 | result="failure"
192 | elif [[ "${{ contains(needs.*.result, 'cancelled') }}" == "true" ]]; then
193 | result="cancelled"
194 | else
195 | result="success"
196 | fi
197 | echo "result=$result" >> $GITHUB_OUTPUT
198 | - name: Conclude Check
199 | uses: LouisBrunner/checks-action@v1.6.0
200 | with:
201 | token: ${{ secrets.GITHUB_TOKEN }}
202 | conclusion: ${{ steps.conclusion.outputs.result }}
203 | check_id: ${{ needs.update.outputs.check-id }}
204 |
205 | post-release:
206 | needs: release
207 | outputs:
208 | comment-id: ${{ steps.create-comment.outputs.comment-id }}
209 | name: Post Release - Release
210 | if: github.repository_owner == 'npm' && needs.release.outputs.releases
211 | runs-on: ubuntu-latest
212 | defaults:
213 | run:
214 | shell: bash
215 | steps:
216 | - name: Create Release PR Comment Text
217 | id: comment-text
218 | uses: actions/github-script@v7
219 | env:
220 | RELEASES: ${{ needs.release.outputs.releases }}
221 | with:
222 | result-encoding: string
223 | script: |
224 | const releases = JSON.parse(process.env.RELEASES)
225 | const { runId, repo: { owner, repo } } = context
226 | const issue_number = releases[0].prNumber
227 | const runUrl = `https://github.com/${owner}/${repo}/actions/runs/${runId}`
228 |
229 | return [
230 | '## Release Workflow\n',
231 | ...releases.map(r => `- \`${r.pkgName}@${r.version}\` ${r.url}`),
232 | `- Workflow run: :arrows_counterclockwise: ${runUrl}`,
233 | ].join('\n')
234 | - name: Create Release PR Comment
235 | id: create-comment
236 | uses: peter-evans/create-or-update-comment@v3
237 | with:
238 | issue-number: ${{ fromJSON(needs.release.outputs.releases)[0].prNumber }}
239 | body: ${{ steps.comment-text.outputs.result }}
240 |
241 | release-integration:
242 | needs: release
243 | name: Release Integration
244 | if: needs.release.outputs.releases
245 | uses: ./.github/workflows/release-integration.yml
246 | permissions:
247 | contents: read
248 | id-token: write
249 | secrets:
250 | PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }}
251 | with:
252 | releases: ${{ needs.release.outputs.releases }}
253 |
254 | post-release-integration:
255 | needs: [ release, release-integration, post-release ]
256 | name: Post Release Integration - Release
257 | if: github.repository_owner == 'npm' && needs.release.outputs.releases && always()
258 | runs-on: ubuntu-latest
259 | defaults:
260 | run:
261 | shell: bash
262 | steps:
263 | - name: Get Post Release Conclusion
264 | id: conclusion
265 | run: |
266 | if [[ "${{ contains(needs.*.result, 'failure') }}" == "true" ]]; then
267 | result="x"
268 | elif [[ "${{ contains(needs.*.result, 'cancelled') }}" == "true" ]]; then
269 | result="heavy_multiplication_x"
270 | else
271 | result="white_check_mark"
272 | fi
273 | echo "result=$result" >> $GITHUB_OUTPUT
274 | - name: Find Release PR Comment
275 | uses: peter-evans/find-comment@v2
276 | id: found-comment
277 | with:
278 | issue-number: ${{ fromJSON(needs.release.outputs.releases)[0].prNumber }}
279 | comment-author: 'github-actions[bot]'
280 | body-includes: '## Release Workflow'
281 | - name: Create Release PR Comment Text
282 | id: comment-text
283 | if: steps.found-comment.outputs.comment-id
284 | uses: actions/github-script@v7
285 | env:
286 | RESULT: ${{ steps.conclusion.outputs.result }}
287 | BODY: ${{ steps.found-comment.outputs.comment-body }}
288 | with:
289 | result-encoding: string
290 | script: |
291 | const { RESULT, BODY } = process.env
292 | const body = [BODY.replace(/(Workflow run: :)[a-z_]+(:)/, `$1${RESULT}$2`)]
293 | if (RESULT !== 'white_check_mark') {
294 | body.push(':rotating_light::rotating_light::rotating_light:')
295 | body.push([
296 | '@npm/cli-team: The post-release workflow failed for this release.',
297 | 'Manual steps may need to be taken after examining the workflow output.'
298 | ].join(' '))
299 | body.push(':rotating_light::rotating_light::rotating_light:')
300 | }
301 | return body.join('\n\n').trim()
302 | - name: Update Release PR Comment
303 | if: steps.comment-text.outputs.result
304 | uses: peter-evans/create-or-update-comment@v3
305 | with:
306 | comment-id: ${{ steps.found-comment.outputs.comment-id }}
307 | body: ${{ steps.comment-text.outputs.result }}
308 | edit-mode: 'replace'
309 |
--------------------------------------------------------------------------------