From 825e592d42f2a9c9abc4cd65a07a3142f110f7d8 Mon Sep 17 00:00:00 2001 From: Sachin Sachdeva <7625278+sachinsachdeva@users.noreply.github.com> Date: Sat, 8 Aug 2026 23:22:54 +1000 Subject: [PATCH 1/9] Add GPU monitoring on macOS, including Apple Silicon Adds GPU utilization and GPU memory as two independently toggleable status bar resources. systeminformation cannot supply this: si.graphics() shells out to system_profiler, costs ~230ms, and reports no utilization field at all. Instead, statistics are read from the IOKit registry with `ioreg -r -d 1 -w 0 -c IOAccelerator`, which takes ~20ms and needs no elevated privileges, unlike powermetrics. Availability is decided by whether a machine actually reports usable statistics rather than by checking the architecture, so this also covers Intel Macs and stays hidden anywhere the data is missing. The sampler caches for 100ms so that a single update tick costs one ioreg invocation across both resources, and resolves to null rather than rejecting on any failure. That matters because ResMon.update() collects resources with Promise.all and has no error handling, so one rejection would break the setTimeout chain and freeze the whole status bar. GPU memory is reported as in-use over allocated, not used out of VRAM: Apple Silicon uses unified memory with no fixed GPU partition, so the second figure is what the driver has currently claimed from system memory rather than a fixed capacity. Also documents the existing behaviour that resources capture their configuration once at activation, so show.* and unit settings need a window reload to take effect. New settings: resmon.show.gpu, resmon.show.gpumem, resmon.gpu.unit. Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 3 + README.md | 23 ++++++- package-lock.json | 2 +- package.json | 23 ++++++- src/appleGpu.ts | 155 ++++++++++++++++++++++++++++++++++++++++++++++ src/extension.ts | 73 ++++++++++++++++++++++ 6 files changed, 276 insertions(+), 3 deletions(-) create mode 100644 src/appleGpu.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index fdec48c..34409ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Change Log +## [1.0.8] +- Added GPU utilization and GPU memory monitoring on macOS, including Apple Silicon (M-series). Statistics are read from the IOKit registry via `ioreg`, which needs no elevated privileges. Both metrics hide themselves automatically on machines that do not report GPU statistics. + ## [1.0.7] - Changed underlying CPU frequency API, added hiding battery/CPU temp information if the device lacks a battery/doesn't support CPU temp sensing, added some clarifications about CPU frequency behavior on Windows. diff --git a/README.md b/README.md index af53ce1..74150f1 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ## Features -Display CPU frequency, usage, memory consumption, and battery percentage remaining within the VSCode status bar. +Display CPU frequency, usage, memory consumption, GPU utilization, and battery percentage remaining within the VSCode status bar. ## Screenshots @@ -20,6 +20,9 @@ Just the system information node module. - `resmon.show.battery`: Show battery percentage remaining. - `resmon.show.disk`: Show disk space information. - `resmon.show.cputemp`: Show CPU temperature. May not work without the lm-sensors module on Linux. May require running VS Code as admin on Windows. +- `resmon.show.gpu`: Show GPU utilization. macOS only; see GPU Monitoring below. +- `resmon.show.gpumem`: Show GPU memory in use. macOS only; see GPU Monitoring below. +- `resmon.gpu.unit`: Unit used for GPU memory (GB-B). - `resmon.disk.format`: Configures how the disk space is displayed (percentage remaining/used, absolute remaining, used out of totel). - `resmon.disk.drives`: Drives to show. For example, 'C:' on Windows, and '/dev/sda1' on Linux. - `resmon.updatefrequencyms`: How frequently to query systeminformation. The minimum is 200 ms as to prevent accidentally updating so fast as to freeze up your machine. @@ -28,14 +31,32 @@ Just the system information node module. - `resmon.alignLeft`: Toggles the alignment of the status bar. - `resmon.color`: Color of the status bar text in hex code (for example, #FFFFFF is white). The color must be in the format #RRGGBB, using hex digits. +## GPU Monitoring + +GPU statistics are **macOS only**, and work on Apple Silicon (M-series) as well as Intel Macs. They are read from the IOKit registry with `ioreg`, which requires no elevated privileges — unlike `powermetrics`, which needs `sudo`. On any machine that does not report GPU statistics, both GPU metrics hide themselves automatically rather than showing an error. + +- `resmon.show.gpu` displays GPU utilization as a percentage. This is the driver's instantaneous `Device Utilization %`, sampled at each update rather than averaged over the interval, so expect it to fluctuate the same way CPU usage does. +- `resmon.show.gpumem` displays GPU memory as *in use / allocated*. Note that this is **not** used-out-of-VRAM: Apple Silicon uses unified memory with no fixed GPU partition, so the second figure is how much system memory the GPU driver has currently claimed, not a fixed capacity. It moves over time. + +GPU frequency and GPU power are not available, as both require `sudo powermetrics` or private APIs. + +Linux and Windows GPU monitoring is not implemented; each would need a separate backend (`nvidia-smi` / `/sys/class/drm`, and WMI / NVML respectively). + ## Known Issues +Settings are read once when the extension activates. All of the `resmon.show.*` toggles and the unit settings (`resmon.freq.unit`, `resmon.mem.unit`, `resmon.gpu.unit`, `resmon.disk.*`) therefore require a window reload before a change takes effect. Only `resmon.alignLeft`, `resmon.color`, and `resmon.updatefrequencyms` apply immediately. + +`resmon.mem.unit` is currently read under the wrong key internally and is ignored; memory is always reported in GB. + A better solution for Windows CPU Usage would be great. I investigated alternatives to counting Processor Time, but none of them seemed to match the Task Manager percentage. --- ## Change Log +### [1.0.8] +- Added GPU utilization and GPU memory monitoring on macOS, including Apple Silicon (M-series). + ### [1.0.7] - Changed underlying CPU frequency API, added hiding battery/CPU temp information if the device lacks a battery/doesn't support CPU temp sensing, added some clarifications about CPU frequency behavior on Windows. diff --git a/package-lock.json b/package-lock.json index 1742e70..a9412f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "resourcemonitor", - "version": "1.0.7", + "version": "1.0.8", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index ade4438..ceb180a 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "resourcemonitor", "displayName": "Resource Monitor", "description": "Displays current CPU stats, memory/disk consumption, and battery percentage remaining.", - "version": "1.0.7", + "version": "1.0.8", "publisher": "mutantdino", "engines": { "vscode": "^1.25.0" @@ -54,6 +54,27 @@ "default": false, "description": "Toggles the display of CPU temperature. May not work without the lm-sensors module on Linux. May require running VS Code as admin on Windows" }, + "resmon.show.gpu": { + "type": "boolean", + "default": true, + "description": "Toggles the display of GPU utilization. macOS only; automatically hidden on machines that do not report GPU statistics. Requires a window reload to take effect." + }, + "resmon.show.gpumem": { + "type": "boolean", + "default": false, + "description": "Toggles the display of GPU memory in use. macOS only; automatically hidden on machines that do not report GPU statistics. Requires a window reload to take effect." + }, + "resmon.gpu.unit": { + "type": "string", + "enum": [ + "GB", + "MB", + "KB", + "B" + ], + "default": "GB", + "description": "Unit used for GPU memory." + }, "resmon.disk.format": { "type": "string", "enum": [ diff --git a/src/appleGpu.ts b/src/appleGpu.ts new file mode 100644 index 0000000..e0845be --- /dev/null +++ b/src/appleGpu.ts @@ -0,0 +1,155 @@ +'use strict'; + +import { execFile } from 'child_process'; + +/** + * GPU statistics scraped from the IOKit registry on macOS. + * + * On Apple Silicon there is no fixed VRAM partition: the GPU shares system + * memory, so the two memory figures are "currently mapped" and "claimed from + * the system by the driver" rather than used/total of a dedicated pool. + */ +export interface GpuStats { + /** "Device Utilization %", 0-100. An instantaneous snapshot, not an interval average. */ + utilization: number; + /** "In use system memory", in bytes. */ + inUseMemory: number; + /** "Alloc system memory", in bytes. */ + allocatedMemory: number; +} + +// Recurse one level from each IOAccelerator node so we pick up its properties, +// with unlimited line width so the PerformanceStatistics dict isn't wrapped. +const IOREG_ARGS: string[] = ['-r', '-d', '1', '-w', '0', '-c', 'IOAccelerator']; + +// The whole registry dump for one node is ~46 KB on an M4; allow generous room +// for machines that expose several accelerators. +const IOREG_MAX_BUFFER: number = 4 * 1024 * 1024; + +// Two resources each ask whether they are shown and then what to display, so a +// single tick reads the sampler four times. Collapsing those into one ioreg +// invocation keeps the cost at ~20ms per tick. The 200ms floor on +// resmon.updatefrequencyms guarantees this window never spans two ticks. +const CACHE_WINDOW_MS: number = 100; + +const PERFORMANCE_STATISTICS_PATTERN = /"PerformanceStatistics" = \{([^}]*)\}/; + +/** + * Extracts GPU statistics from the output of + * `ioreg -r -d 1 -w 0 -c IOAccelerator`. + * + * Pure: takes text, returns data, never touches the system. Returns null if + * the output holds no readable statistics, which is how this feature detects + * that a machine cannot report GPU utilization. + */ +export function parsePerformanceStatistics(ioregOutput: string): GpuStats | null { + // On a multi-GPU Mac ioreg emits several nodes; read the first one that + // carries statistics. + let statisticsBlock = PERFORMANCE_STATISTICS_PATTERN.exec(ioregOutput); + if (statisticsBlock === null) { + return null; + } + let statistics: string = statisticsBlock[1]; + + // Utilization is the one value the feature cannot do without. + let utilization: number | null = readStatistic(statistics, "Device Utilization %"); + if (utilization === null) { + return null; + } + + return { + utilization: utilization, + inUseMemory: readStatistic(statistics, "In use system memory") || 0, + allocatedMemory: readStatistic(statistics, "Alloc system memory") || 0, + }; +} + +/** + * Reads a single `"key"=` entry out of a PerformanceStatistics dict. + * + * The closing quote in the pattern is load-bearing: without it "In use system + * memory" would also match its sibling "In use system memory (driver)", which + * is a different (and usually zero) value. + */ +function readStatistic(statistics: string, key: string): number | null { + let escapedKey: string = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + let match = new RegExp(`"${escapedKey}"=(\\d+)`).exec(statistics); + if (match === null) { + return null; + } + return parseInt(match[1], 10); +} + +/** + * Samples GPU statistics from the IOKit registry, caching briefly so that + * several resources can share one `ioreg` invocation per update tick. + * + * Reading IOAccelerator requires no elevated privileges, unlike powermetrics. + */ +export class AppleGpuSampler { + private _cachedStats: GpuStats | null; + private _cachedAt: number; + private _pending: Promise | null; + + constructor() { + this._cachedStats = null; + this._cachedAt = Number.NEGATIVE_INFINITY; + this._pending = null; + } + + /** + * Returns the current GPU statistics, or null when they are unavailable: + * off macOS, on a Mac whose driver exposes no statistics, or when ioreg + * fails. + * + * Never rejects. ResMon.update() gathers every resource with Promise.all + * and has no error handling, so a single rejection would break the update + * loop and freeze the whole status bar. + */ + public sample(): Promise { + if (process.platform !== 'darwin') { + return Promise.resolve(null); + } + + if (Date.now() - this._cachedAt < CACHE_WINDOW_MS) { + return Promise.resolve(this._cachedStats); + } + + // Fold callers that arrive mid-flight into the running invocation. + if (this._pending !== null) { + return this._pending; + } + + this._pending = this.readRegistry().then(stats => { + this._cachedStats = stats; + this._cachedAt = Date.now(); + this._pending = null; + return stats; + }); + + return this._pending; + } + + /** + * Runs ioreg and parses its output, resolving to null on any failure. + * + * execFile is wrapped by hand rather than with util.promisify because the + * pinned @types/node predates promisify's typings. + */ + private readRegistry(): Promise { + return new Promise(resolve => { + execFile('ioreg', IOREG_ARGS, { maxBuffer: IOREG_MAX_BUFFER }, (error, stdout) => { + if (error) { + resolve(null); + return; + } + + try { + resolve(parsePerformanceStatistics(stdout.toString())); + } catch (parseError) { + resolve(null); + } + }); + }); + } +} diff --git a/src/extension.ts b/src/extension.ts index 50791b9..9720c00 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,6 +1,7 @@ 'use strict'; import { window, ExtensionContext, StatusBarAlignment, StatusBarItem, workspace, WorkspaceConfiguration } from 'vscode'; import { Units, DiskSpaceFormat, DiskSpaceFormatMappings, FreqMappings, MemMappings } from './constants'; +import { AppleGpuSampler } from './appleGpu'; var si = require('systeminformation'); @@ -11,6 +12,13 @@ export function activate(context: ExtensionContext) { } abstract class Resource { + // Note: a WorkspaceConfiguration is an immutable snapshot, and this one is + // handed over once when ResMon constructs the resource. The refreshed + // configuration that ResMon.update() fetches each tick never reaches here, + // so every resource-level setting (show.*, show.precision, freq.unit, + // gpu.unit, disk.*) is fixed at activation and needs a window reload to + // take effect. Only alignLeft, color and updatefrequencyms, which ResMon + // reads off its own copy, apply live. protected _config: WorkspaceConfiguration; protected _isShownByDefault: boolean; protected _configKey: string; @@ -107,6 +115,65 @@ class CpuFreq extends Resource { } } +/** + * Base for the macOS GPU metrics, which all come from one IOKit registry read. + * + * Availability is decided by whether the machine actually reports statistics + * rather than by checking the architecture, so this covers Apple Silicon and + * Intel Macs alike and stays hidden anywhere the data is missing. + */ +abstract class AppleGpuResource extends Resource { + protected _sampler: AppleGpuSampler; + + constructor(config: WorkspaceConfiguration, isShownByDefault: boolean, configKey: string, sampler: AppleGpuSampler) { + super(config, isShownByDefault, configKey); + this._sampler = sampler; + } + + protected async isShown(): Promise { + // The sampler caches briefly, so this read and the one in getDisplay() + // share a single ioreg invocation. + let stats = await this._sampler.sample(); + return stats !== null && this._config.get(`show.${this._configKey}`, this._isShownByDefault); + } +} + +class GpuUsage extends AppleGpuResource { + + constructor(config: WorkspaceConfiguration, sampler: AppleGpuSampler) { + super(config, true, "gpu", sampler); + } + + async getDisplay(): Promise { + let stats = await this._sampler.sample(); + if (stats === null) { + return ""; + } + return `$(circuit-board) ${(stats.utilization).toFixed(this.getPrecision())}%`; + } +} + +class GpuMemory extends AppleGpuResource { + + constructor(config: WorkspaceConfiguration, sampler: AppleGpuSampler) { + super(config, false, "gpumem", sampler); + } + + async getDisplay(): Promise { + let stats = await this._sampler.sample(); + if (stats === null) { + return ""; + } + let unit = this._config.get('gpu.unit', "GB"); + var memDivisor = MemMappings[unit]; + // Apple Silicon shares system memory with the CPU, so this is memory + // currently mapped out of memory the driver has claimed, not VRAM. + let inUseWithUnits = stats.inUseMemory / memDivisor; + let allocatedWithUnits = stats.allocatedMemory / memDivisor; + return `$(server) ${(inUseWithUnits).toFixed(this.getPrecision())}/${(allocatedWithUnits).toFixed(this.getPrecision())} ${unit}`; + } +} + class Battery extends Resource { constructor(config: WorkspaceConfiguration) { @@ -234,10 +301,16 @@ class ResMon { this._statusBarItem.color = this._getColor(); this._statusBarItem.show(); + // The GPU resources share one sampler so that a tick costs a single + // read of the IOKit registry rather than one per resource. + let gpuSampler = new AppleGpuSampler(); + // Add all resources to monitor this._resources = []; this._resources.push(new CpuUsage(this._config)); this._resources.push(new CpuFreq(this._config)); + this._resources.push(new GpuUsage(this._config, gpuSampler)); + this._resources.push(new GpuMemory(this._config, gpuSampler)); this._resources.push(new Battery(this._config)); this._resources.push(new Memory(this._config)); this._resources.push(new DiskSpace(this._config)); From 4e1b61185d49fc0a8f25da3fa8bc505226467232 Mon Sep 17 00:00:00 2001 From: Sachin Sachdeva <7625278+sachinsachdeva@users.noreply.github.com> Date: Sat, 8 Aug 2026 23:25:21 +1000 Subject: [PATCH 2/9] Fix systeminformation being pruned from the packaged extension systeminformation was listed in both dependencies and devDependencies, and the lockfile recorded it with "dev": true. As a result vsce pruned it as a dev dependency and produced a VSIX containing no node_modules at all, so an installed build failed at activation on require('systeminformation'). Running from source with F5 was unaffected, which is why this went unnoticed. Drops the duplicate devDependencies entry and clears the stale dev flag in the lockfile. Verified with `npm ci --omit=dev`, which now resolves the dependency, and by repackaging: the VSIX went from 13 files with no node_modules to 34 files including systeminformation. Also excludes .claude and .github from the package, neither of which belongs in a published extension. Co-Authored-By: Claude Opus 5 --- .vscodeignore | 2 ++ package-lock.json | 3 +-- package.json | 1 - 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.vscodeignore b/.vscodeignore index ab83bbc..d346de8 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -1,5 +1,7 @@ .vscode/** .vscode-test/** +.claude/** +.github/** out/test/** out/**/*.map src/** diff --git a/package-lock.json b/package-lock.json index a9412f4..831ee66 100644 --- a/package-lock.json +++ b/package-lock.json @@ -783,8 +783,7 @@ "systeminformation": { "version": "4.27.11", "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-4.27.11.tgz", - "integrity": "sha512-U7bigXbOnsB8k1vNHS0Y13RCsRz5/UohiUmND+3mMUL6vfzrpbe/h4ZqewowB+B+tJNnmGFDj08Z8xGfYo45dQ==", - "dev": true + "integrity": "sha512-U7bigXbOnsB8k1vNHS0Y13RCsRz5/UohiUmND+3mMUL6vfzrpbe/h4ZqewowB+B+tJNnmGFDj08Z8xGfYo45dQ==" }, "tough-cookie": { "version": "2.5.0", diff --git a/package.json b/package.json index ceb180a..644e663 100644 --- a/package.json +++ b/package.json @@ -153,7 +153,6 @@ "systeminformation": "^4.27.11" }, "devDependencies": { - "systeminformation": "^4.27.11", "@types/mocha": "^2.2.42", "@types/node": "^7.0.43", "tslint": "^5.8.0", From b29fa9d5c8e83f58a2a3785546dfda4d98815285 Mon Sep 17 00:00:00 2001 From: Sachin Sachdeva <7625278+sachinsachdeva@users.noreply.github.com> Date: Sun, 9 Aug 2026 00:02:26 +1000 Subject: [PATCH 3/9] Rebrand fork as System Vitals and make it publishable Prepares the fork for publishing under its own marketplace identity rather than the upstream author's. Identity: publisher sachinsachdeva, id system-vitals, version reset to 1.0.0 for a fresh listing. Repository, bugs and homepage now point at this fork. The icon is replaced, as the previous one was the upstream author's artwork. Adds LICENSE.md recording that upstream declares no license, so the redistribution terms for this project are unresolved, and that only the changes made in this fork are ours to license. This documents the situation rather than asserting rights over code we do not own. Switches activation from "*" to onStartupFinished so the extension no longer loads during editor startup. That API needs VS Code 1.53, which forced the rest: @types/vscode 1.53 replaces the deprecated vscode module and its postinstall hook, and since those typings use `unknown`, TypeScript moves from 2.6 to 5.9. The only source change that required was dropping a meaningless `async` from an abstract method declaration, which TypeScript 4+ rejects. vsce now packages with no warnings and no override flags, where it previously needed --allow-star-activation and --allow-missing-repository. Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 30 +- LICENSE.md | 20 ++ README.md | 48 +-- images/icon.png | Bin 1859 -> 1726 bytes package-lock.json | 845 +++++++++++----------------------------------- package.json | 40 ++- src/extension.ts | 2 +- 7 files changed, 269 insertions(+), 716 deletions(-) create mode 100644 LICENSE.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 34409ff..e1f6225 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,28 +1,38 @@ # Change Log -## [1.0.8] +## [1.0.0] + +First release of System Vitals, forked from [resmon](https://github.com/Njanderson/resmon) 1.0.7. + - Added GPU utilization and GPU memory monitoring on macOS, including Apple Silicon (M-series). Statistics are read from the IOKit registry via `ioreg`, which needs no elevated privileges. Both metrics hide themselves automatically on machines that do not report GPU statistics. +- Fixed `systeminformation` being pruned from the packaged extension, which produced a build that failed at activation once installed. +- Changed activation from `*` to `onStartupFinished`, so the extension no longer delays editor startup. This raises the minimum VS Code version to 1.53. +- Modernized the build: TypeScript 5, `@types/vscode` in place of the deprecated `vscode` module, and no `postinstall` hook. -## [1.0.7] +## Inherited history + +Releases below are from the upstream resmon project, retained for context. + +### [1.0.7] - Changed underlying CPU frequency API, added hiding battery/CPU temp information if the device lacks a battery/doesn't support CPU temp sensing, added some clarifications about CPU frequency behavior on Windows. -## [1.0.6] +### [1.0.6] - Added DiskSpace, CPU Temperature. Adjusted battery icon. -## [1.0.5] +### [1.0.5] - Refactored code heavily, addressed Github issue with memory.used versus memory.active. -## [1.0.4] +### [1.0.4] - Added icon for store. -## [1.0.3] +### [1.0.3] - Changed icons. Added choosable units. -## [1.0.2] +### [1.0.2] - Actually properly added systeminformation as a real dependency. -## [1.0.1] +### [1.0.1] - Properly added systeminformation as a real dependency -## [1.0.0] -- Initial release \ No newline at end of file +### [1.0.0] +- Initial release diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..ece8b0c --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,20 @@ +# License + +System Vitals is a fork of [resmon](https://github.com/Njanderson/resmon) by Nicholas Anderson, published to the VS Code Marketplace as `mutantdino.resourcemonitor`. + +## Status of the upstream work + +At the time this fork was made, the upstream project declared **no license**: it carries no `LICENSE` file, no `license` field in its `package.json`, and GitHub reports no detected license for the repository. Under default copyright, that means the original author retains all rights in the portions of this project that originate upstream, and grants no redistribution or modification rights. + +This file records that situation honestly rather than asserting a license over work this fork does not own. + +## Modifications + +Changes made in this fork — including the macOS GPU monitoring support in `src/appleGpu.ts` and the related additions to `src/extension.ts` — are copyright © 2026 Sachin Sachdeva. + +## What this means + +Until the upstream project adopts an explicit license, the redistribution terms for this project as a whole are unresolved. Anyone intending to redistribute it, or to rely on it in a context where licensing matters, should treat that as an open question. + +If you are the author of the upstream project and would like this fork changed, renamed, or taken down, please open an issue at +. diff --git a/README.md b/README.md index 74150f1..df12fdb 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,16 @@ -# Resource Monitor +# System Vitals + +Display CPU frequency and usage, GPU utilization, memory consumption, disk space, and battery percentage in the VS Code status bar — including **native Apple Silicon GPU monitoring**, which needs no elevated privileges. + +> **A fork.** System Vitals is a fork of [resmon](https://github.com/Njanderson/resmon) by Nicholas Anderson, adding macOS GPU support and a modernized build. It is not affiliated with or endorsed by the original author. See [LICENSE.md](LICENSE.md) for the licensing situation, which is unresolved because the upstream project declares no license. ## Features -Display CPU frequency, usage, memory consumption, GPU utilization, and battery percentage remaining within the VSCode status bar. +- **CPU** — usage percentage and current frequency +- **GPU** — utilization and memory in use (macOS, including M-series Apple Silicon) +- **Memory** — consumed out of total +- **Disk** — space remaining or used, per drive +- **Battery** — percentage remaining, hidden automatically on devices without a battery ## Screenshots @@ -10,7 +18,7 @@ Display CPU frequency, usage, memory consumption, GPU utilization, and battery p ## Requirements -Just the system information node module. +None beyond VS Code 1.53 or newer. The `systeminformation` module is bundled with the extension, and macOS GPU statistics are read from the IOKit registry with `ioreg`, which requires no additional software and no elevated privileges. ## Extension Settings @@ -54,36 +62,4 @@ A better solution for Windows CPU Usage would be great. I investigated alternati ## Change Log -### [1.0.8] -- Added GPU utilization and GPU memory monitoring on macOS, including Apple Silicon (M-series). - -### [1.0.7] -- Changed underlying CPU frequency API, added hiding battery/CPU temp information if the device lacks a battery/doesn't support CPU temp sensing, added some clarifications about CPU frequency behavior on Windows. - -### [1.0.6] - -- Added DiskSpace, CPU Temperature. Adjusted battery icon. - -### [1.0.5] - -- Refactored code heavily, addressed Github issue with memory.used versus memory.active. - -### [1.0.4] - -- Added icon for store. - -### [1.0.3] - -- Changed icons. Added choosable units. - -### [1.0.2] - -- Actually properly added systeminformation as a real dependency. - -### [1.0.1] - -- Properly added systeminformation as a real dependency - -### [1.0.0] - -- Initial release +See [CHANGELOG.md](CHANGELOG.md). diff --git a/images/icon.png b/images/icon.png index 10000b20ccb15035b34e1477c852669967ec28f5..db224e649eb16a49f6d5e48e7220fa793ead328a 100644 GIT binary patch literal 1726 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 diff --git a/package-lock.json b/package-lock.json index 831ee66..999b153 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,812 +1,396 @@ { - "name": "resourcemonitor", - "version": "1.0.8", - "lockfileVersion": 1, + "name": "system-vitals", + "version": "1.0.0", + "lockfileVersion": 3, "requires": true, - "dependencies": { - "@babel/code-frame": { + "packages": { + "": { + "name": "system-vitals", + "version": "1.0.0", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "systeminformation": "^4.27.11" + }, + "devDependencies": { + "@types/mocha": "^2.2.42", + "@types/node": "^20.19.43", + "@types/vscode": "1.53.0", + "tslint": "^5.8.0", + "typescript": "^5.9.3" + }, + "engines": { + "vscode": "^1.53.0" + } + }, + "node_modules/@babel/code-frame": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", "dev": true, - "requires": { + "dependencies": { "@babel/highlight": "^7.8.3" } }, - "@babel/helper-validator-identifier": { + "node_modules/@babel/helper-validator-identifier": { "version": "7.9.0", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz", "integrity": "sha512-6G8bQKjOh+of4PV/ThDm/rRqlU7+IGoJuofpagU5GlEl29Vv0RGqqt86ZGRV8ZuSOY3o+8yXl5y782SMcG7SHw==", "dev": true }, - "@babel/highlight": { + "node_modules/@babel/highlight": { "version": "7.9.0", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz", "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", "dev": true, - "requires": { + "dependencies": { "@babel/helper-validator-identifier": "^7.9.0", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } }, - "@types/mocha": { + "node_modules/@types/mocha": { "version": "2.2.48", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-2.2.48.tgz", "integrity": "sha512-nlK/iyETgafGli8Zh9zJVCTicvU3iajSkRwOh3Hhiva598CMqNJ4NcVCGMTGKpGpTYj/9R8RLzS9NAykSSCqGw==", "dev": true }, - "@types/node": { - "version": "7.10.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-7.10.9.tgz", - "integrity": "sha512-usSpgoUsRtO5xNV5YEPU8PPnHisFx8u0rokj1BPVn/hDF7zwUDzVLiuKZM38B7z8V2111Fj6kd4rGtQFUZpNOw==", - "dev": true - }, - "agent-base": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "node_modules/@types/node": { + "version": "20.19.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", + "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", "dev": true, - "requires": { - "es6-promisify": "^5.0.0" + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" } }, - "ajv": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", - "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", + "node_modules/@types/vscode": { + "version": "1.53.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.53.0.tgz", + "integrity": "sha512-XjFWbSPOM0EKIT2XhhYm3D3cx3nn3lshMUcWNy1eqefk+oqRuBq8unVb6BYIZqXy9lQZyeUl7eaBCOZWv+LcXQ==", "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } + "license": "MIT" }, - "ansi-styles": { + "node_modules/ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, - "requires": { + "dependencies": { "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" } }, - "argparse": { + "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, - "requires": { + "dependencies": { "sprintf-js": "~1.0.2" } }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "dev": true, - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true - }, - "aws4": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", - "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==", - "dev": true - }, - "balanced-match": { + "node_modules/balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "dev": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "brace-expansion": { + "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "requires": { + "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, - "builtin-modules": { + "node_modules/builtin-modules": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", - "dev": true - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "chalk": { + "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, - "requires": { + "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" } }, - "color-convert": { + "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, - "requires": { + "dependencies": { "color-name": "1.1.3" } }, - "color-name": { + "node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "commander": { + "node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, - "concat-map": { + "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true - }, - "diff": { + "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "dev": true, - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", - "dev": true - }, - "es6-promisify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", "dev": true, - "requires": { - "es6-promise": "^4.0.3" + "engines": { + "node": ">=0.3.1" } }, - "escape-string-regexp": { + "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.8.0" + } }, - "esprima": { + "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true - }, - "fast-deep-equal": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", - "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true - }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" } }, - "fs.realpath": { + "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "glob": { + "node_modules/glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, - "requires": { + "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true - }, - "har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", - "dev": true, - "requires": { - "ajv": "^6.5.5", - "har-schema": "^2.0.0" - } - }, - "has-flag": { + "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", - "dev": true - }, - "http-proxy-agent": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", - "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", - "dev": true, - "requires": { - "agent-base": "4", - "debug": "3.1.0" - } - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" + "engines": { + "node": ">=4" } }, - "https-proxy-agent": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", - "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", - "dev": true, - "requires": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - } - }, - "inflight": { + "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, - "requires": { + "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, - "inherits": { + "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true - }, - "js-tokens": { + "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true }, - "js-yaml": { + "node_modules/js-yaml": { "version": "3.13.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", "dev": true, - "requires": { + "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "dev": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "mime-db": { - "version": "1.43.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", - "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==", - "dev": true - }, - "mime-types": { - "version": "2.1.26", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", - "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", - "dev": true, - "requires": { - "mime-db": "1.43.0" - } - }, - "minimatch": { + "node_modules/minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, - "requires": { + "dependencies": { "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" } }, - "minimist": { + "node_modules/minimist": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true }, - "mkdirp": { + "node_modules/mkdirp": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz", "integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==", + "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", "dev": true, - "requires": { + "dependencies": { "minimist": "^1.2.5" - } - }, - "mocha": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", - "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", - "dev": true, - "requires": { - "browser-stdout": "1.3.1", - "commander": "2.15.1", - "debug": "3.1.0", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "glob": "7.1.2", - "growl": "1.10.5", - "he": "1.1.1", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "supports-color": "5.4.0" }, - "dependencies": { - "commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", - "dev": true - }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } + "bin": { + "mkdirp": "bin/cmd.js" } }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true - }, - "once": { + "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, - "requires": { + "dependencies": { "wrappy": "1" } }, - "path-is-absolute": { + "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "path-parse": { + "node_modules/path-parse": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", "dev": true }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true - }, - "psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", - "dev": true - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true - }, - "querystringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", - "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==", - "dev": true - }, - "request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "dev": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - } - }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", - "dev": true - }, - "resolve": { + "node_modules/resolve": { "version": "1.15.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", "dev": true, - "requires": { + "dependencies": { "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "safe-buffer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", - "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==", - "dev": true - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "semver": { + "node_modules/semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-support": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz", - "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==", "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" + "bin": { + "semver": "bin/semver" } }, - "sprintf-js": { + "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, - "sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "dev": true, - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "supports-color": { + "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, - "requires": { + "dependencies": { "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" } }, - "systeminformation": { + "node_modules/systeminformation": { "version": "4.27.11", "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-4.27.11.tgz", - "integrity": "sha512-U7bigXbOnsB8k1vNHS0Y13RCsRz5/UohiUmND+3mMUL6vfzrpbe/h4ZqewowB+B+tJNnmGFDj08Z8xGfYo45dQ==" - }, - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" + "integrity": "sha512-U7bigXbOnsB8k1vNHS0Y13RCsRz5/UohiUmND+3mMUL6vfzrpbe/h4ZqewowB+B+tJNnmGFDj08Z8xGfYo45dQ==", + "os": [ + "darwin", + "linux", + "win32", + "freebsd", + "openbsd", + "netbsd", + "sunos" + ], + "bin": { + "systeminformation": "lib/cli.js" + }, + "engines": { + "node": ">=4.0.0" + }, + "funding": { + "type": "Buy me a coffee", + "url": "https://www.buymeacoffee.com/systeminfo" } }, - "tslib": { + "node_modules/tslib": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz", "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==", "dev": true }, - "tslint": { + "node_modules/tslint": { "version": "5.20.1", "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.20.1.tgz", "integrity": "sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg==", "dev": true, - "requires": { + "dependencies": { "@babel/code-frame": "^7.0.0", "builtin-modules": "^1.1.1", "chalk": "^2.3.0", @@ -820,100 +404,51 @@ "semver": "^5.3.0", "tslib": "^1.8.0", "tsutils": "^2.29.0" + }, + "bin": { + "tslint": "bin/tslint" + }, + "engines": { + "node": ">=4.8.0" + }, + "peerDependencies": { + "typescript": ">=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >=3.0.0-dev || >= 3.1.0-dev || >= 3.2.0-dev" } }, - "tsutils": { + "node_modules/tsutils": { "version": "2.29.0", "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", "dev": true, - "requires": { + "dependencies": { "tslib": "^1.8.1" + }, + "peerDependencies": { + "typescript": ">=2.1.0 || >=2.1.0-dev || >=2.2.0-dev || >=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >= 3.0.0-dev || >= 3.1.0-dev" } }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true - }, - "typescript": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz", - "integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==", - "dev": true - }, - "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "url-parse": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz", - "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==", - "dev": true, - "requires": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "vscode": { - "version": "1.1.36", - "resolved": "https://registry.npmjs.org/vscode/-/vscode-1.1.36.tgz", - "integrity": "sha512-cGFh9jmGLcTapCpPCKvn8aG/j9zVQ+0x5hzYJq5h5YyUXVGa1iamOaB2M2PZXoumQPES4qeAP1FwkI0b6tL4bQ==", + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, - "requires": { - "glob": "^7.1.2", - "mocha": "^5.2.0", - "request": "^2.88.0", - "semver": "^5.4.1", - "source-map-support": "^0.5.0", - "url-parse": "^1.4.4", - "vscode-test": "^0.4.1" + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" } }, - "vscode-test": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/vscode-test/-/vscode-test-0.4.3.tgz", - "integrity": "sha512-EkMGqBSefZH2MgW65nY05rdRSko15uvzq4VAPM5jVmwYuFQKE7eikKXNJDRxL+OITXHB6pI+a3XqqD32Y3KC5w==", + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "dev": true, - "requires": { - "http-proxy-agent": "^2.1.0", - "https-proxy-agent": "^2.2.1" - } + "license": "MIT" }, - "wrappy": { + "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", diff --git a/package.json b/package.json index 644e663..0fe1cf1 100644 --- a/package.json +++ b/package.json @@ -1,28 +1,36 @@ { - "name": "resourcemonitor", - "displayName": "Resource Monitor", - "description": "Displays current CPU stats, memory/disk consumption, and battery percentage remaining.", - "version": "1.0.8", - "publisher": "mutantdino", + "name": "system-vitals", + "displayName": "System Vitals", + "description": "CPU, GPU, memory, disk and battery monitoring in the VS Code status bar, with native Apple Silicon GPU support.", + "version": "1.0.0", + "publisher": "sachinsachdeva", + "license": "SEE LICENSE IN LICENSE.md", "engines": { - "vscode": "^1.25.0" + "vscode": "^1.53.0" }, "categories": [ "Other" ], + "keywords": [ + "monitor", + "cpu", + "gpu", + "memory", + "apple silicon" + ], "icon": "images/icon.png", "galleryBanner": { - "color": "#008080", + "color": "#0F172A", "theme": "dark" }, "activationEvents": [ - "*" + "onStartupFinished" ], "main": "./out/extension", "contributes": { "configuration": { "type": "object", - "title": "Resource Monitor", + "title": "System Vitals", "properties": { "resmon.show.cpuusage": { "type": "boolean", @@ -141,12 +149,16 @@ } }, "repository": { - "url": "https://github.com/Njanderson/resmon.git" + "type": "git", + "url": "https://github.com/sachinsachdeva/resmon.git" + }, + "bugs": { + "url": "https://github.com/sachinsachdeva/resmon/issues" }, + "homepage": "https://github.com/sachinsachdeva/resmon#readme", "scripts": { "vscode:prepublish": "npm run compile", "compile": "tsc -p ./", - "postinstall": "node ./node_modules/vscode/bin/install", "watch": "tsc -watch -p ./" }, "dependencies": { @@ -154,9 +166,9 @@ }, "devDependencies": { "@types/mocha": "^2.2.42", - "@types/node": "^7.0.43", + "@types/node": "^20.19.43", + "@types/vscode": "1.53.0", "tslint": "^5.8.0", - "typescript": "^2.6.1", - "vscode": "^1.1.6" + "typescript": "^5.9.3" } } diff --git a/src/extension.ts b/src/extension.ts index 9720c00..9131755 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -44,7 +44,7 @@ abstract class Resource { return null; } - protected async abstract getDisplay(): Promise; + protected abstract getDisplay(): Promise; protected async isShown(): Promise { return Promise.resolve(this._config.get(`show.${this._configKey}`, false)); From 3ccca72b1fa420be0432e85d77111f8aa2e0b5f1 Mon Sep 17 00:00:00 2001 From: Sachin Sachdeva <7625278+sachinsachdeva@users.noreply.github.com> Date: Sun, 9 Aug 2026 01:24:44 +1000 Subject: [PATCH 4/9] Move settings from the resmon.* namespace to systemvitals.* With System Vitals published as its own extension, sharing the resmon.* configuration namespace with the upstream project meant that installing both would give you two status bars driven by one set of keys, with no way to configure them independently. Renames all 17 contributed settings and both getConfiguration() call sites. Resource subclasses are unaffected, as they address keys relative to the namespace. The repository URL keeps the resmon name, since the GitHub repository is still called that; only the configuration namespace moves. Settings do not migrate: anyone coming from resmon has to set their preferences again, which the changelog now states. Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 1 + README.md | 40 ++++++++++++++++++++-------------------- package.json | 34 +++++++++++++++++----------------- src/appleGpu.ts | 2 +- src/extension.ts | 4 ++-- 5 files changed, 41 insertions(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e1f6225..b98b5f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ First release of System Vitals, forked from [resmon](https://github.com/Njanderson/resmon) 1.0.7. +- Settings moved from the `resmon.*` namespace to `systemvitals.*`. If you are coming from resmon, your existing settings will not carry over and need to be set again. This keeps the two extensions independent when both are installed, rather than sharing one set of keys. - Added GPU utilization and GPU memory monitoring on macOS, including Apple Silicon (M-series). Statistics are read from the IOKit registry via `ioreg`, which needs no elevated privileges. Both metrics hide themselves automatically on machines that do not report GPU statistics. - Fixed `systeminformation` being pruned from the packaged extension, which produced a build that failed at activation once installed. - Changed activation from `*` to `onStartupFinished`, so the extension no longer delays editor startup. This raises the minimum VS Code version to 1.53. diff --git a/README.md b/README.md index df12fdb..5453bae 100644 --- a/README.md +++ b/README.md @@ -22,29 +22,29 @@ None beyond VS Code 1.53 or newer. The `systeminformation` module is bundled wit ## Extension Settings -- `resmon.show.cpuusage`: Show CPU Usage. In Windows, this percentage is calculated with processor time, which doesn't quite match the task manager figure. -- `resmon.show.cpufreq`: Show CPU Frequency. This may just display a static frequency on Windows. -- `resmon.show.mem`: Show consumed and total memory as a fraction. -- `resmon.show.battery`: Show battery percentage remaining. -- `resmon.show.disk`: Show disk space information. -- `resmon.show.cputemp`: Show CPU temperature. May not work without the lm-sensors module on Linux. May require running VS Code as admin on Windows. -- `resmon.show.gpu`: Show GPU utilization. macOS only; see GPU Monitoring below. -- `resmon.show.gpumem`: Show GPU memory in use. macOS only; see GPU Monitoring below. -- `resmon.gpu.unit`: Unit used for GPU memory (GB-B). -- `resmon.disk.format`: Configures how the disk space is displayed (percentage remaining/used, absolute remaining, used out of totel). -- `resmon.disk.drives`: Drives to show. For example, 'C:' on Windows, and '/dev/sda1' on Linux. -- `resmon.updatefrequencyms`: How frequently to query systeminformation. The minimum is 200 ms as to prevent accidentally updating so fast as to freeze up your machine. -- `resmon.freq.unit`: Unit used for the CPU frequency (GHz-Hz). -- `resmon.mem.unit`: Unit used for the RAM consumption (GB-B). -- `resmon.alignLeft`: Toggles the alignment of the status bar. -- `resmon.color`: Color of the status bar text in hex code (for example, #FFFFFF is white). The color must be in the format #RRGGBB, using hex digits. +- `systemvitals.show.cpuusage`: Show CPU Usage. In Windows, this percentage is calculated with processor time, which doesn't quite match the task manager figure. +- `systemvitals.show.cpufreq`: Show CPU Frequency. This may just display a static frequency on Windows. +- `systemvitals.show.mem`: Show consumed and total memory as a fraction. +- `systemvitals.show.battery`: Show battery percentage remaining. +- `systemvitals.show.disk`: Show disk space information. +- `systemvitals.show.cputemp`: Show CPU temperature. May not work without the lm-sensors module on Linux. May require running VS Code as admin on Windows. +- `systemvitals.show.gpu`: Show GPU utilization. macOS only; see GPU Monitoring below. +- `systemvitals.show.gpumem`: Show GPU memory in use. macOS only; see GPU Monitoring below. +- `systemvitals.gpu.unit`: Unit used for GPU memory (GB-B). +- `systemvitals.disk.format`: Configures how the disk space is displayed (percentage remaining/used, absolute remaining, used out of totel). +- `systemvitals.disk.drives`: Drives to show. For example, 'C:' on Windows, and '/dev/sda1' on Linux. +- `systemvitals.updatefrequencyms`: How frequently to query systeminformation. The minimum is 200 ms as to prevent accidentally updating so fast as to freeze up your machine. +- `systemvitals.freq.unit`: Unit used for the CPU frequency (GHz-Hz). +- `systemvitals.mem.unit`: Unit used for the RAM consumption (GB-B). +- `systemvitals.alignLeft`: Toggles the alignment of the status bar. +- `systemvitals.color`: Color of the status bar text in hex code (for example, #FFFFFF is white). The color must be in the format #RRGGBB, using hex digits. ## GPU Monitoring GPU statistics are **macOS only**, and work on Apple Silicon (M-series) as well as Intel Macs. They are read from the IOKit registry with `ioreg`, which requires no elevated privileges — unlike `powermetrics`, which needs `sudo`. On any machine that does not report GPU statistics, both GPU metrics hide themselves automatically rather than showing an error. -- `resmon.show.gpu` displays GPU utilization as a percentage. This is the driver's instantaneous `Device Utilization %`, sampled at each update rather than averaged over the interval, so expect it to fluctuate the same way CPU usage does. -- `resmon.show.gpumem` displays GPU memory as *in use / allocated*. Note that this is **not** used-out-of-VRAM: Apple Silicon uses unified memory with no fixed GPU partition, so the second figure is how much system memory the GPU driver has currently claimed, not a fixed capacity. It moves over time. +- `systemvitals.show.gpu` displays GPU utilization as a percentage. This is the driver's instantaneous `Device Utilization %`, sampled at each update rather than averaged over the interval, so expect it to fluctuate the same way CPU usage does. +- `systemvitals.show.gpumem` displays GPU memory as *in use / allocated*. Note that this is **not** used-out-of-VRAM: Apple Silicon uses unified memory with no fixed GPU partition, so the second figure is how much system memory the GPU driver has currently claimed, not a fixed capacity. It moves over time. GPU frequency and GPU power are not available, as both require `sudo powermetrics` or private APIs. @@ -52,9 +52,9 @@ Linux and Windows GPU monitoring is not implemented; each would need a separate ## Known Issues -Settings are read once when the extension activates. All of the `resmon.show.*` toggles and the unit settings (`resmon.freq.unit`, `resmon.mem.unit`, `resmon.gpu.unit`, `resmon.disk.*`) therefore require a window reload before a change takes effect. Only `resmon.alignLeft`, `resmon.color`, and `resmon.updatefrequencyms` apply immediately. +Settings are read once when the extension activates. All of the `systemvitals.show.*` toggles and the unit settings (`systemvitals.freq.unit`, `systemvitals.mem.unit`, `systemvitals.gpu.unit`, `systemvitals.disk.*`) therefore require a window reload before a change takes effect. Only `systemvitals.alignLeft`, `systemvitals.color`, and `systemvitals.updatefrequencyms` apply immediately. -`resmon.mem.unit` is currently read under the wrong key internally and is ignored; memory is always reported in GB. +`systemvitals.mem.unit` is currently read under the wrong key internally and is ignored; memory is always reported in GB. A better solution for Windows CPU Usage would be great. I investigated alternatives to counting Processor Time, but none of them seemed to match the Task Manager percentage. diff --git a/package.json b/package.json index 0fe1cf1..49efa82 100644 --- a/package.json +++ b/package.json @@ -32,47 +32,47 @@ "type": "object", "title": "System Vitals", "properties": { - "resmon.show.cpuusage": { + "systemvitals.show.cpuusage": { "type": "boolean", "default": true, "description": "Toggles the display of CPU Usage." }, - "resmon.show.cpufreq": { + "systemvitals.show.cpufreq": { "type": "boolean", "default": true, "description": "Toggles the display of CPU frequency. For Windows, this displays the maximum clock speed." }, - "resmon.show.mem": { + "systemvitals.show.mem": { "type": "boolean", "default": true, "description": "Toggles the display of memory consumption." }, - "resmon.show.battery": { + "systemvitals.show.battery": { "type": "boolean", "default": true, "description": "Toggles the display of battery percentage remaining if a device with a battery. If the device reports not having a battery, this setting will always be treated as false." }, - "resmon.show.disk": { + "systemvitals.show.disk": { "type": "boolean", "default": false, "description": "Toggles the display of disk space information." }, - "resmon.show.cputemp": { + "systemvitals.show.cputemp": { "type": "boolean", "default": false, "description": "Toggles the display of CPU temperature. May not work without the lm-sensors module on Linux. May require running VS Code as admin on Windows" }, - "resmon.show.gpu": { + "systemvitals.show.gpu": { "type": "boolean", "default": true, "description": "Toggles the display of GPU utilization. macOS only; automatically hidden on machines that do not report GPU statistics. Requires a window reload to take effect." }, - "resmon.show.gpumem": { + "systemvitals.show.gpumem": { "type": "boolean", "default": false, "description": "Toggles the display of GPU memory in use. macOS only; automatically hidden on machines that do not report GPU statistics. Requires a window reload to take effect." }, - "resmon.gpu.unit": { + "systemvitals.gpu.unit": { "type": "string", "enum": [ "GB", @@ -83,7 +83,7 @@ "default": "GB", "description": "Unit used for GPU memory." }, - "resmon.disk.format": { + "systemvitals.disk.format": { "type": "string", "enum": [ "PercentRemaining", @@ -93,7 +93,7 @@ ], "description": "Configures how the disk space is displayed (percentage remaining/used, absolute remaining, used out of totel)." }, - "resmon.disk.drives": { + "systemvitals.disk.drives": { "type": "array", "items": { "type": "string" @@ -102,13 +102,13 @@ "default": [], "description": "Drives to show. For example, 'C:' on Windows, and '/dev/sda1' on Linux." }, - "resmon.updatefrequencyms": { + "systemvitals.updatefrequencyms": { "type": "number", "default": 2000, "minimum": 200, "description": "How long in ms between updates to resources." }, - "resmon.freq.unit": { + "systemvitals.freq.unit": { "type": "string", "enum": [ "GHz", @@ -118,7 +118,7 @@ ], "default": "GHz" }, - "resmon.mem.unit": { + "systemvitals.mem.unit": { "type": "string", "enum": [ "GB", @@ -128,19 +128,19 @@ ], "default": "GB" }, - "resmon.show.precision": { + "systemvitals.show.precision": { "type": "number", "default": 2, "minimum": 0, "maximum": 2, "description": "How long in ms between updates to resources." }, - "resmon.alignLeft": { + "systemvitals.alignLeft": { "type": "boolean", "default": true, "description": "Toggles the alignment of the status bar." }, - "resmon.color": { + "systemvitals.color": { "type": "string", "default": "#FFFFFF", "description": "Color of the status bar text in hex code (for example, #FFFFFF is white). The color must be in the format #RRGGBB, using hex digits." diff --git a/src/appleGpu.ts b/src/appleGpu.ts index e0845be..39bb32c 100644 --- a/src/appleGpu.ts +++ b/src/appleGpu.ts @@ -29,7 +29,7 @@ const IOREG_MAX_BUFFER: number = 4 * 1024 * 1024; // Two resources each ask whether they are shown and then what to display, so a // single tick reads the sampler four times. Collapsing those into one ioreg // invocation keeps the cost at ~20ms per tick. The 200ms floor on -// resmon.updatefrequencyms guarantees this window never spans two ticks. +// systemvitals.updatefrequencyms guarantees this window never spans two ticks. const CACHE_WINDOW_MS: number = 100; const PERFORMANCE_STATISTICS_PATTERN = /"PerformanceStatistics" = \{([^}]*)\}/; diff --git a/src/extension.ts b/src/extension.ts index 9131755..1e37721 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -294,7 +294,7 @@ class ResMon { private _resources: Resource[]; constructor() { - this._config = workspace.getConfiguration('resmon'); + this._config = workspace.getConfiguration('systemvitals'); this._delimiter = " "; this._updating = false; this._statusBarItem = window.createStatusBarItem(this._config.get('alignLeft') ? StatusBarAlignment.Left : StatusBarAlignment.Right); @@ -344,7 +344,7 @@ class ResMon { if (this._updating) { // Update the configuration in case it has changed - this._config = workspace.getConfiguration('resmon'); + this._config = workspace.getConfiguration('systemvitals'); // Update the status bar item's styling let proposedAlignment = this._config.get('alignLeft') ? StatusBarAlignment.Left : StatusBarAlignment.Right; From a645ac8e8fd85831502c314b3af335c53f653c86 Mon Sep 17 00:00:00 2001 From: Sachin Sachdeva <7625278+sachinsachdeva@users.noreply.github.com> Date: Sun, 9 Aug 2026 01:31:52 +1000 Subject: [PATCH 5/9] Fix settings being frozen at activation, and related defects Settings now apply live. Each Resource held the WorkspaceConfiguration it was handed at construction, and because that object is an immutable snapshot, the refreshed one ResMon fetched every tick never reached it. Every show.* and unit setting was therefore fixed at activation and needed a window reload. Adds setConfig(), called on each resource per tick. That exposed a second problem: _maxWidth only ever grows, so a setting that shortens a display, such as switching memory from B to GB, would leave the status bar padded at the old width for the rest of the session. A configuration listener now resets the high water mark when settings actually change. Memory ignored its unit setting: it read the key 'memunit' while the contributed key is 'mem.unit', so it always rendered GB. convertBytesToLargestUnit labelled its output from the Units enum, which yields "G" and "None" rather than "GB" and "B". Adds an explicit label lookup. Its unused precision parameter is gone; it already deferred to getPrecision(). The base isShown() hardcoded false instead of using _isShownByDefault, making that constructor argument dead. Now used, and the two values that disagreed with the manifest (cputemp, battery) are aligned with it. This is a no-op for contributed keys, since VS Code returns the contributed default, but it removes a trap for any key added later. Removes the unfinished Network resource. It logged every interface to the console on activation and its getDisplay() returned "". With isShown() honouring _isShownByDefault it would have started rendering an empty column, since show.net is not a contributed key and it was constructed with a default of true. Recoverable from 8720281 if picked back up. Corrects the show.precision description, which was copied from updatefrequencyms. Co-Authored-By: Claude Opus 5 --- README.md | 4 --- package.json | 6 ++-- src/constants.ts | 9 +++++ src/extension.ts | 94 +++++++++++++++++++++++++----------------------- 4 files changed, 61 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 5453bae..51ace5b 100644 --- a/README.md +++ b/README.md @@ -52,10 +52,6 @@ Linux and Windows GPU monitoring is not implemented; each would need a separate ## Known Issues -Settings are read once when the extension activates. All of the `systemvitals.show.*` toggles and the unit settings (`systemvitals.freq.unit`, `systemvitals.mem.unit`, `systemvitals.gpu.unit`, `systemvitals.disk.*`) therefore require a window reload before a change takes effect. Only `systemvitals.alignLeft`, `systemvitals.color`, and `systemvitals.updatefrequencyms` apply immediately. - -`systemvitals.mem.unit` is currently read under the wrong key internally and is ignored; memory is always reported in GB. - A better solution for Windows CPU Usage would be great. I investigated alternatives to counting Processor Time, but none of them seemed to match the Task Manager percentage. --- diff --git a/package.json b/package.json index 49efa82..0162c69 100644 --- a/package.json +++ b/package.json @@ -65,12 +65,12 @@ "systemvitals.show.gpu": { "type": "boolean", "default": true, - "description": "Toggles the display of GPU utilization. macOS only; automatically hidden on machines that do not report GPU statistics. Requires a window reload to take effect." + "description": "Toggles the display of GPU utilization. macOS only; automatically hidden on machines that do not report GPU statistics." }, "systemvitals.show.gpumem": { "type": "boolean", "default": false, - "description": "Toggles the display of GPU memory in use. macOS only; automatically hidden on machines that do not report GPU statistics. Requires a window reload to take effect." + "description": "Toggles the display of GPU memory in use. macOS only; automatically hidden on machines that do not report GPU statistics." }, "systemvitals.gpu.unit": { "type": "string", @@ -133,7 +133,7 @@ "default": 2, "minimum": 0, "maximum": 2, - "description": "How long in ms between updates to resources." + "description": "Number of decimal places shown for each resource." }, "systemvitals.alignLeft": { "type": "boolean", diff --git a/src/constants.ts b/src/constants.ts index b24f6b2..efef580 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -36,6 +36,15 @@ export var FreqMappings: UnitLookup = { "Hz": Units.None, }; +// Units[Units.G] yields "G", and Units[Units.None] yields "None", neither of +// which is a unit anyone wants to read in a status bar. +export var UnitLabels: { [unit: number]: string } = { + [Units.None]: "B", + [Units.K]: "KB", + [Units.M]: "MB", + [Units.G]: "GB", +}; + export var MemMappings: UnitLookup = { "GB": Units.G, "MB": Units.M, diff --git a/src/extension.ts b/src/extension.ts index 1e37721..302e519 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,6 +1,6 @@ 'use strict'; -import { window, ExtensionContext, StatusBarAlignment, StatusBarItem, workspace, WorkspaceConfiguration } from 'vscode'; -import { Units, DiskSpaceFormat, DiskSpaceFormatMappings, FreqMappings, MemMappings } from './constants'; +import { window, Disposable, ExtensionContext, StatusBarAlignment, StatusBarItem, workspace, WorkspaceConfiguration } from 'vscode'; +import { Units, UnitLabels, DiskSpaceFormat, DiskSpaceFormatMappings, FreqMappings, MemMappings } from './constants'; import { AppleGpuSampler } from './appleGpu'; var si = require('systeminformation'); @@ -12,13 +12,9 @@ export function activate(context: ExtensionContext) { } abstract class Resource { - // Note: a WorkspaceConfiguration is an immutable snapshot, and this one is - // handed over once when ResMon constructs the resource. The refreshed - // configuration that ResMon.update() fetches each tick never reaches here, - // so every resource-level setting (show.*, show.precision, freq.unit, - // gpu.unit, disk.*) is fixed at activation and needs a window reload to - // take effect. Only alignLeft, color and updatefrequencyms, which ResMon - // reads off its own copy, apply live. + // A WorkspaceConfiguration is an immutable snapshot, so this is replaced + // via setConfig() on every update tick. Holding the object handed over at + // construction would freeze every resource-level setting at activation. protected _config: WorkspaceConfiguration; protected _isShownByDefault: boolean; protected _configKey: string; @@ -31,6 +27,26 @@ abstract class Resource { this._maxWidth = 0; } + /** + * Points this resource at a freshly read configuration, so that changed + * settings apply on the next tick rather than at the next window reload. + */ + public setConfig(config: WorkspaceConfiguration) { + this._config = config; + } + + /** + * Clears the padding high water mark. + * + * _maxWidth only ever grows, which is what stops the status bar jittering + * as digits come and go. It does mean a setting that shortens the display, + * such as switching memory from B to GB, would otherwise leave a gap at the + * old width for the rest of the session. + */ + public resetWidth() { + this._maxWidth = 0; + } + public async getResourceDisplay(): Promise { if (await this.isShown()) { @@ -47,19 +63,19 @@ abstract class Resource { protected abstract getDisplay(): Promise; protected async isShown(): Promise { - return Promise.resolve(this._config.get(`show.${this._configKey}`, false)); + return Promise.resolve(this._config.get(`show.${this._configKey}`, this._isShownByDefault)); } public getPrecision(): number { return this._config.get("show.precision", 2); } - protected convertBytesToLargestUnit(bytes: number, precision: number = 2): string { + protected convertBytesToLargestUnit(bytes: number): string { let unit: Units = Units.None; while (bytes/unit >= 1024 && unit < Units.G) { unit *= 1024; } - return `${(bytes/unit).toFixed(this.getPrecision())} ${Units[unit]}`; + return `${(bytes/unit).toFixed(this.getPrecision())} ${UnitLabels[unit]}`; } } @@ -79,14 +95,14 @@ class CpuUsage extends Resource { class CpuTemp extends Resource { constructor(config: WorkspaceConfiguration) { - super(config, true, "cputemp"); + super(config, false, "cputemp"); } protected async isShown(): Promise { // If the CPU temp sensor cannot retrieve a valid temperature, disallow its reporting. var cpuTemp = (await si.cpuTemperature()).main; let hasCpuTemp = cpuTemp !== -1; - return Promise.resolve(hasCpuTemp && this._config.get("show.cputemp", true)); + return hasCpuTemp && await super.isShown(); } async getDisplay(): Promise { @@ -134,7 +150,7 @@ abstract class AppleGpuResource extends Resource { // The sampler caches briefly, so this read and the one in getDisplay() // share a single ioreg invocation. let stats = await this._sampler.sample(); - return stats !== null && this._config.get(`show.${this._configKey}`, this._isShownByDefault); + return stats !== null && await super.isShown(); } } @@ -177,12 +193,12 @@ class GpuMemory extends AppleGpuResource { class Battery extends Resource { constructor(config: WorkspaceConfiguration) { - super(config, false, "battery"); + super(config, true, "battery"); } protected async isShown(): Promise { let hasBattery = (await si.battery()).hasbattery; - return Promise.resolve(hasBattery && this._config.get("show.battery", false)); + return hasBattery && await super.isShown(); } async getDisplay(): Promise { @@ -199,7 +215,7 @@ class Memory extends Resource { } async getDisplay() : Promise { - let unit = this._config.get('memunit', "GB"); + let unit = this._config.get('mem.unit', "GB"); var memDivisor = MemMappings[unit]; let memoryData = await si.mem(); let memoryUsedWithUnits = memoryData.active / memDivisor; @@ -208,31 +224,6 @@ class Memory extends Resource { } } -class Network extends Resource { - - constructor(config: WorkspaceConfiguration) { - super(config, true, "net"); - - // Network stats are requested through returning the delta between - // multiple invocations - this.getInterfaceStats(); - } - - async getInterfaceStats() : Promise { - let networkInterfaces = await si.networkInterfaces(); - for (let networkInterface in networkInterfaces) { - console.log(networkInterface); - let networkStats = await si.networkStats(networkInterface); - console.log(networkStats); - } - } - - async getDisplay(): Promise { - // Not implemented - return ""; - } -} - class DiskSpace extends Resource { constructor(config: WorkspaceConfiguration) { @@ -292,6 +283,7 @@ class ResMon { private _delimiter: string; private _updating: boolean; private _resources: Resource[]; + private _configListener: Disposable; constructor() { this._config = workspace.getConfiguration('systemvitals'); @@ -315,7 +307,14 @@ class ResMon { this._resources.push(new Memory(this._config)); this._resources.push(new DiskSpace(this._config)); this._resources.push(new CpuTemp(this._config)); - this._resources.push(new Network(this._config)); + + // Settings that shorten a resource's display would otherwise leave the + // status bar padded out at the old width for the rest of the session. + this._configListener = workspace.onDidChangeConfiguration(event => { + if (event.affectsConfiguration('systemvitals')) { + this._resources.forEach(resource => resource.resetWidth()); + } + }); } public StartUpdating() { @@ -343,8 +342,12 @@ class ResMon { private async update() { if (this._updating) { - // Update the configuration in case it has changed + // Update the configuration in case it has changed. A + // WorkspaceConfiguration is a snapshot, so the resources need to be + // pointed at the new one or they would keep reading activation-time + // values. this._config = workspace.getConfiguration('systemvitals'); + this._resources.forEach(resource => resource.setConfig(this._config)); // Update the status bar item's styling let proposedAlignment = this._config.get('alignLeft') ? StatusBarAlignment.Left : StatusBarAlignment.Right; @@ -372,6 +375,7 @@ class ResMon { dispose() { this.StopUpdating(); + this._configListener.dispose(); this._statusBarItem.dispose(); } } From 5e31bf824a10050012dd4d46d4045b8dafc19474 Mon Sep 17 00:00:00 2001 From: Sachin Sachdeva <7625278+sachinsachdeva@users.noreply.github.com> Date: Sun, 9 Aug 2026 01:50:53 +1000 Subject: [PATCH 6/9] Replace inherited screenshots with one of this extension The two screenshots carried over from upstream were the original author's captures of the original extension: a Windows status bar showing a C: drive, with no GPU metrics. Same provenance question as the icon, and no longer an accurate picture of what this extension displays. Replaces them with a capture of System Vitals running on Apple Silicon, showing the default set of CPU usage, CPU frequency, GPU utilization and memory. Co-Authored-By: Claude Opus 5 --- README.md | 6 ++++-- images/disk_space_screenshot.png | Bin 10073 -> 0 bytes images/example.png | Bin 4154 -> 0 bytes images/statusbar.png | Bin 0 -> 16977 bytes 4 files changed, 4 insertions(+), 2 deletions(-) delete mode 100644 images/disk_space_screenshot.png delete mode 100644 images/example.png create mode 100644 images/statusbar.png diff --git a/README.md b/README.md index 51ace5b..f5ecb97 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,11 @@ Display CPU frequency and usage, GPU utilization, memory consumption, disk space - **Disk** — space remaining or used, per drive - **Battery** — percentage remaining, hidden automatically on devices without a battery -## Screenshots +## Screenshot -![Disk space feature](images/disk_space_screenshot.png). +![System Vitals in the VS Code status bar](images/statusbar.png) + +CPU usage, CPU frequency, GPU utilization and memory, running on an Apple Silicon Mac. GPU memory, disk space and CPU temperature are available as well, and are off by default. ## Requirements diff --git a/images/disk_space_screenshot.png b/images/disk_space_screenshot.png deleted file mode 100644 index 20d495ad6de4a93b7d9b292639fc2b994fc3f828..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10073 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 Ef?Mx0U;qFB diff --git a/images/statusbar.png b/images/statusbar.png new file mode 100644 index 0000000000000000000000000000000000000000..1a759cd186624365f01298f738783f6a1c8a77a9 GIT binary patch literal 16977 zcmd74bCe~|wk}$>jV^TAt8CluvTfV8ZKKP!ZQHKuvaQ#@z0bbqym#OI|E?S>W3HI< zi!bJ!kz+(eX0C8~SuuDR92g)VAb1IJVMQPy5c0n=0~EyHGcZ52!{4)nxsZ^&gpd%S zyo0TYxs@>x5JkLqT$hxf0@ko^O1`qN@a+69l87G4O6?jPjU5v0+iJDy0{^Ambu!Ew zF;02g&-D!J3cfsAim;I5<=*1MC;X~LM9$FE=Ml#o$LRrB9s#AYdV=4|+FXfRP*t{8 zAs{ng#<2_%vyDH{ky{}p+6$MF!wNgYdg6|oZs|iEF;!a8qJ!JV#*A%8F z%%#QES<|9b$1ngfCTO9lcW z`&$A5fhL3iLH;dK{(j~2K>nizAU*+^p^XfdFy4asCypjh*xf-K?!_968;1i2h~4`B(m@ z8bCz&FB2zA9wIdvc|svu2V+83I(j;KB3>9mLPBl_BNI+VVbTAB|NX^7Wai{##|Z$q zy1LT2GSS&Om;x9$I5+_Gi~vSP+P@aGj_x*2`fju~j>P|NdTj=45X2e}Mg?`8Vue ze*K${`=7=*<;~rUt<;6ht^Y>#Z`XL4Ik^AD^1mtmyPy9C{&F;S5VE!Yi*w@r@2vb6 z_lr2HS6|AG9+15P;y^S^BR|AdhDFXn%%`!9QLz(4W*AMyTo zGXGWkH=DdL+<^ZzGI(KZ<8*|9fOz30gawq{fG@Qny?%)-fA>5f335Wm%}V_dK~m8B zC65G-8&PdA5Rhw48`(kewAECtjix&H%V`K5U0zteh>B|7lki~{B#0l%{Q5oD@w#)f zvy(&|{(`5c)AM9Hi^K6)n#-|dFflRlAQ-k9Obfad#g24WvNzd}+TVi6KhL5+bRvWr zSQX^Ib^N5eROB`y|1tZQt~?n`>!?TrJ#-@K|7HBw5bys!!^wE5UdKZcEtvl}WheNL zPvpuUHv3|_AXhyRoNtWIl8jiSagNE9GOj9}_iw@bNZdi{N5OuHvP>2dT;Z*B9qTS8faJtxb7bd7~Ot3TNDw>p>NQGHQFQ2CjItjke##6?Hn zKa9$h{zz^yjl|`Q@R)l>iGG@-vdt(JCLitNA}rKMsd%a&+A6@9+ISC1NOh~GF+v34D?%s6Woaq@+KD)9vtN7&*E;h^cxPh7L74}|#~y0K z2F$nOJQS+vBy|2zIuKVA)cqTduKQnO75*R`AeCrnL*D9cWF!JACadmmmARJQ7@ z3No5MU+h0PVdEE+hO&7vqdc_$ITE4X<^>5I@mNy}(No{4Pi8=hxn-Js@x}EZ+w^&F zQ4hqpp|zKFL284-;s{H$Ij9nFNqcg1nkePR{?TI#-BK9703u4?D3j)r`oPv0^>L)R zz!1)1hGmK^`f&?3CN_vU;1JeBLUXx-9s5P*ePA0v8i6{vGdgf`ovQ=BgR_Du0d;HY ziSrtk+nzlNF%xqRr&t5Dw-4C%V>HJ)Yq7?Wjc-AdU z0cRBC(4vAB4WH$&I%3UH=A=RpcvujmhsN1|YN4VQT*Wss@=K9bMMDn%Q{1DB1V~0v z5LVucBeZ9llod@T2q#{KC``f{5{WT|pV13!q-nV~J9wYa(e~DCS@&(3*yJl&2gLf%;s0|%c_W61?t`iz-&jTyvL#NRCCq6$24Y;5%M;Bke;CD!}HI?v*Sxf<%9+9Hxg9JMq3s zMc&A|Iah@s$c~Hqw1k78M?@L$Nu`Sg_ul1ztZ^ZKeU=h18mGt2G94B-TQPAwuO>2c2g(VCJS9!1*C4`8TW{&IF zdNb5kr4AJnTnI-B8a?GV4%RXU1UxFy+DWROlnZpuyFC*vO%b`Xgq>QM23e>fe`Y%c z70nh>s;sxE>y7dEx_i@)Y)JhS5D*^;ysMRGNxm&S#<=bPBE~d zSA7mt5+FX`NJ95;6=X>^!)r#56#_ilOA5RXur8S}ZMOq`r;{A$PZSq0Qc$#A {`)nl!%w#D3vpG{Ui6qIr7t-5@;5r;?a+K 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 literal 0 HcmV?d00001 From fb2162ff68706c44271b4f8da31bd27690a05a0d Mon Sep 17 00:00:00 2001 From: Sachin Sachdeva <7625278+sachinsachdeva@users.noreply.github.com> Date: Sun, 9 Aug 2026 02:02:05 +1000 Subject: [PATCH 7/9] Add unit tests and repair the CI workflow CI could not have passed. It targeted Node 10 and 12, both long EOL and below the 14.17 that TypeScript 5 requires, and invoked a bare `tsc`, which is not on PATH in a run step. It also used checkout@v2 and setup-node@v1. Rebuilds it on Node 20 and 22 with current actions, and adds lint, test and package steps. Packaging in CI is the point: the systeminformation bug shipped a broken extension that compiled and linted cleanly, and only inspecting the built artifact catches that class of problem, so the workflow now asserts the runtime dependency is present in the VSIX and uploads it for download from the pull request. Adds unit tests over the logic that can run outside the editor, using the Node test runner so nothing new is added to the dependency tree. They cover the ioreg parser, including the "In use system memory (driver)" sibling key that a looser pattern would mistake for the real value, and its behaviour on empty, malformed and truncated input; the sampler's guarantee that it resolves rather than rejecting, since a rejection would freeze the status bar; and that every unit and disk format offered in the manifest has a mapping behind it. Tests live in src/test and compile to out/test, which .vscodeignore already excludes, so they do not ship. Verified: the packaged VSIX contains no test files. Drops @types/mocha, which was unused, and whose global describe/it declarations would have shadowed the test runner's. Co-Authored-By: Claude Opus 5 --- .github/workflows/nodejs.yml | 60 ++++++++++++++++++------ package-lock.json | 7 --- package.json | 3 +- src/test/appleGpu.test.ts | 91 ++++++++++++++++++++++++++++++++++++ src/test/constants.test.ts | 38 +++++++++++++++ 5 files changed, 178 insertions(+), 21 deletions(-) create mode 100644 src/test/appleGpu.test.ts create mode 100644 src/test/constants.test.ts diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 97ae6fd..1d5ec1d 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -1,28 +1,62 @@ -# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node -# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions - -name: Node.js CI +# Compiles the extension, lints it, and packages a VSIX to prove the published +# artifact would actually work. +name: CI on: push: branches: [ master ] pull_request: branches: [ master ] + workflow_dispatch: jobs: build: - runs-on: ubuntu-latest strategy: + fail-fast: false matrix: - node-version: [10.x, 12.x] + node-version: [ 20.x, 22.x ] steps: - - uses: actions/checkout@v2 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node-version }} - - run: npm ci - - run: tsc -p $GITHUB_WORKSPACE + - uses: actions/checkout@v4 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: npm + + - run: npm ci + + - name: Compile + run: npm run compile + + - name: Lint + run: npm run lint + + - name: Test + run: npm test + + - name: Package extension + run: npx --yes @vscode/vsce package --out system-vitals.vsix + + # systeminformation was once listed as both a dependency and a + # devDependency, so vsce pruned it and shipped a VSIX with no + # node_modules at all. That build compiled, linted and packaged + # cleanly, and failed at activation on require('systeminformation'). + # Only inspecting the artifact catches it. + - name: Verify the runtime dependency is bundled + run: | + if ! unzip -l system-vitals.vsix | grep -q 'node_modules/systeminformation/lib/'; then + echo "systeminformation is missing from the VSIX: the extension would fail on activation" + unzip -l system-vitals.vsix + exit 1 + fi + + - name: Upload VSIX + if: matrix.node-version == '22.x' + uses: actions/upload-artifact@v4 + with: + name: system-vitals-vsix + path: system-vitals.vsix diff --git a/package-lock.json b/package-lock.json index 999b153..b5b7e68 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,6 @@ "systeminformation": "^4.27.11" }, "devDependencies": { - "@types/mocha": "^2.2.42", "@types/node": "^20.19.43", "@types/vscode": "1.53.0", "tslint": "^5.8.0", @@ -48,12 +47,6 @@ "js-tokens": "^4.0.0" } }, - "node_modules/@types/mocha": { - "version": "2.2.48", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-2.2.48.tgz", - "integrity": "sha512-nlK/iyETgafGli8Zh9zJVCTicvU3iajSkRwOh3Hhiva598CMqNJ4NcVCGMTGKpGpTYj/9R8RLzS9NAykSSCqGw==", - "dev": true - }, "node_modules/@types/node": { "version": "20.19.43", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", diff --git a/package.json b/package.json index 0162c69..3b37531 100644 --- a/package.json +++ b/package.json @@ -159,13 +159,14 @@ "scripts": { "vscode:prepublish": "npm run compile", "compile": "tsc -p ./", + "lint": "tslint -c tslint.json 'src/**/*.ts'", + "test": "npm run compile && node --test out/test/*.test.js", "watch": "tsc -watch -p ./" }, "dependencies": { "systeminformation": "^4.27.11" }, "devDependencies": { - "@types/mocha": "^2.2.42", "@types/node": "^20.19.43", "@types/vscode": "1.53.0", "tslint": "^5.8.0", diff --git a/src/test/appleGpu.test.ts b/src/test/appleGpu.test.ts new file mode 100644 index 0000000..a337e56 --- /dev/null +++ b/src/test/appleGpu.test.ts @@ -0,0 +1,91 @@ +import * as assert from 'assert'; +import { test } from 'node:test'; +import { parsePerformanceStatistics, AppleGpuSampler } from '../appleGpu'; + +// Trimmed from real `ioreg -r -d 1 -w 0 -c IOAccelerator` output on an Apple M4. +// The "In use system memory (driver)" sibling key is kept deliberately: it is +// the trap the parser has to avoid. +const M4_SAMPLE = `+-o AGXAcceleratorG16G + { + "IOMatchedAtBoot" = Yes + "PerformanceStatistics" = {"In use system memory (driver)"=0,"Alloc system memory"=2181693440,"Tiler Utilization %"=30,"recoveryCount"=0,"Renderer Utilization %"=29,"Device Utilization %"=30,"In use system memory"=356089856} + "model" = "Apple M4" + "gpu-core-count" = 10 + } +`; + +test('parses utilization and memory from real ioreg output', () => { + let stats = parsePerformanceStatistics(M4_SAMPLE); + + assert.ok(stats !== null); + assert.strictEqual(stats!.utilization, 30); + assert.strictEqual(stats!.inUseMemory, 356089856); + assert.strictEqual(stats!.allocatedMemory, 2181693440); +}); + +test('does not mistake the "(driver)" sibling for in-use memory', () => { + // "In use system memory (driver)" is 0 and appears first, so a pattern + // without the closing quote would report 0 bytes in use. + let stats = parsePerformanceStatistics(M4_SAMPLE); + + assert.notStrictEqual(stats!.inUseMemory, 0); + assert.strictEqual(stats!.inUseMemory, 356089856); +}); + +test('returns null rather than throwing on unusable input', () => { + assert.strictEqual(parsePerformanceStatistics(''), null); + assert.strictEqual(parsePerformanceStatistics('not ioreg output at all'), null); + // Truncated before the statistics block closes. + assert.strictEqual(parsePerformanceStatistics('"PerformanceStatistics" = {"Alloc'), null); +}); + +test('returns null when utilization is absent, even if memory is present', () => { + // Utilization is the one value the feature cannot do without, so a machine + // reporting only memory counts as unsupported. + let stats = parsePerformanceStatistics('"PerformanceStatistics" = {"Alloc system memory"=123}'); + + assert.strictEqual(stats, null); +}); + +test('defaults missing memory values to zero', () => { + let stats = parsePerformanceStatistics('"PerformanceStatistics" = {"Device Utilization %"=42}'); + + assert.strictEqual(stats!.utilization, 42); + assert.strictEqual(stats!.inUseMemory, 0); + assert.strictEqual(stats!.allocatedMemory, 0); +}); + +test('reads the first accelerator when a machine reports several', () => { + let dualGpu = '"PerformanceStatistics" = {"Device Utilization %"=7}\n' + + '"PerformanceStatistics" = {"Device Utilization %"=88}'; + + assert.strictEqual(parsePerformanceStatistics(dualGpu)!.utilization, 7); +}); + +test('sampler resolves instead of rejecting, on every platform', async () => { + // ResMon.update() gathers resources with Promise.all and has no error + // handling, so a rejection here would freeze the whole status bar. + let stats = await new AppleGpuSampler().sample(); + + if (process.platform !== 'darwin') { + assert.strictEqual(stats, null, 'GPU statistics must be unavailable off macOS'); + return; + } + + // On macOS the values are real, so assert their shape rather than exact numbers. + if (stats !== null) { + assert.ok(stats.utilization >= 0 && stats.utilization <= 100); + assert.ok(stats.inUseMemory >= 0); + assert.ok(stats.allocatedMemory >= 0); + } +}); + +test('sampler serves concurrent callers from one sample', async () => { + // Two resources each call isShown() and getDisplay() per tick. Without the + // cache that is four ioreg invocations where one will do. + let sampler = new AppleGpuSampler(); + let results = await Promise.all([sampler.sample(), sampler.sample(), sampler.sample(), sampler.sample()]); + + let distinct = new Set(results.map(r => JSON.stringify(r))); + assert.strictEqual(distinct.size, 1, 'concurrent callers should observe one identical sample'); +}); diff --git a/src/test/constants.test.ts b/src/test/constants.test.ts new file mode 100644 index 0000000..41b5544 --- /dev/null +++ b/src/test/constants.test.ts @@ -0,0 +1,38 @@ +import * as assert from 'assert'; +import { test } from 'node:test'; +import { Units, UnitLabels, MemMappings, FreqMappings, DiskSpaceFormat, DiskSpaceFormatMappings } from '../constants'; + +test('unit labels read as byte units, not enum member names', () => { + // Units[Units.G] is "G" and Units[Units.None] is "None", which is what the + // disk display used to render. + assert.strictEqual(UnitLabels[Units.None], 'B'); + assert.strictEqual(UnitLabels[Units.K], 'KB'); + assert.strictEqual(UnitLabels[Units.M], 'MB'); + assert.strictEqual(UnitLabels[Units.G], 'GB'); +}); + +test('every unit the settings offer has a divisor', () => { + // These keys are the enums contributed in package.json; a missing entry + // would make the divisor undefined and render NaN in the status bar. + ['GB', 'MB', 'KB', 'B'].forEach(unit => { + assert.strictEqual(typeof MemMappings[unit], 'number', `no divisor for mem unit ${unit}`); + }); + ['GHz', 'MHz', 'KHz', 'Hz'].forEach(unit => { + assert.strictEqual(typeof FreqMappings[unit], 'number', `no divisor for freq unit ${unit}`); + }); +}); + +test('unit divisors are binary multiples', () => { + assert.strictEqual(MemMappings['B'], 1); + assert.strictEqual(MemMappings['KB'], 1024); + assert.strictEqual(MemMappings['MB'], 1024 * 1024); + assert.strictEqual(MemMappings['GB'], 1024 * 1024 * 1024); +}); + +test('every disk format the settings offer maps to an enum member', () => { + ['PercentRemaining', 'PercentUsed', 'Remaining', 'UsedOutOfTotal'].forEach(format => { + assert.strictEqual( + typeof DiskSpaceFormatMappings[format], 'number', `no mapping for disk format ${format}`); + }); + assert.strictEqual(DiskSpaceFormatMappings['PercentUsed'], DiskSpaceFormat.PercentUsed); +}); From c5d82b1ea534ed99ef803942697f0676065a6a73 Mon Sep 17 00:00:00 2001 From: Sachin Sachdeva <7625278+sachinsachdeva@users.noreply.github.com> Date: Sun, 9 Aug 2026 02:05:05 +1000 Subject: [PATCH 8/9] Move CI actions off the deprecated Node 20 runtime checkout, setup-node and upload-artifact at v4 target the Node 20 action runtime, which GitHub has deprecated and is already force-running on Node 24. v5 targets Node 24 directly, which clears the warning on every run before it becomes a failure. The build matrix is unaffected: it selects the Node used to compile and test, not the runtime the actions themselves execute on. Co-Authored-By: Claude Opus 5 --- .github/workflows/nodejs.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 1d5ec1d..2f60219 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -19,10 +19,10 @@ jobs: node-version: [ 20.x, 22.x ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: ${{ matrix.node-version }} cache: npm @@ -56,7 +56,7 @@ jobs: - name: Upload VSIX if: matrix.node-version == '22.x' - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: system-vitals-vsix path: system-vitals.vsix From 1ef94b318980f934f33c54f063eea09e9bb72209 Mon Sep 17 00:00:00 2001 From: Sachin Sachdeva <7625278+sachinsachdeva@users.noreply.github.com> Date: Sun, 9 Aug 2026 02:06:09 +1000 Subject: [PATCH 9/9] Build against Node 22 and 24 instead of 20 and 22 Node 20 is in maintenance and its action runtime is already deprecated on GitHub runners. Testing against the current LTS and the current release matches where users and the runners actually are. The VSIX upload stays pinned to the 22.x job, which is still in the matrix, so exactly one artifact is produced. Co-Authored-By: Claude Opus 5 --- .github/workflows/nodejs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 2f60219..b1e9d4d 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -16,7 +16,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [ 20.x, 22.x ] + node-version: [ 22.x, 24.x ] steps: - uses: actions/checkout@v5