jpm-pkg 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ # MIT License
2
+
3
+ Copyright (c) 2026 Muhammad Sulman
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,148 @@
1
+ # JPM ⚡ Joint Package Manager
2
+
3
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
+ [![Node.js Version](https://img.shields.io/badge/node-%3E%3D18.0.0-brightgreen.svg)](https://nodejs.org/)
5
+ [![Bun](https://img.shields.io/badge/Bun-Supported-black.svg)](https://bun.sh/)
6
+
7
+ **JPM (Joint Package Manager)** is an enterprise-grade, high-performance, and environment-aware package manager built for the modern JavaScript ecosystem. It provides a unified, blazing-fast experience across **Node.js** and **Bun** with a focus on security, speed, and advanced monorepo capabilities.
8
+
9
+ ---
10
+
11
+ ## 🚀 Key Features
12
+
13
+ ### ⚡ Blazing Performance
14
+
15
+ - **Environment-Aware**: Leverages native Bun I/O (`Bun.write`, `Bun.spawn`) for near-zero overhead or high-performance Node.js streams.
16
+ - **Worker-Pool Installation**: Uses a continuous worker-pool architecture for maximum parallel download and extraction speeds.
17
+ - **Platform-Aware Filtering**: Only downloads optional binaries (like native modules) compatible with your specific OS and CPU, saving bandwidth and disk space.
18
+ - **Incremental Core**: Intelligent caching and symlinking system ensures you never re-download the same package twice.
19
+
20
+ ### 🛡️ Hardened Security
21
+
22
+ - **Zip Slip Protection**: Built-in path traversal filtering prevents malicious packages from writing files outside their `node_modules`.
23
+ - **Strict HTTPS & Integrity**: Enforces mandatory SHA-512 integrity checks and strict SSL for all registry metadata and audits.
24
+ - **Malicious Script Detection**: Scans `preinstall`/`postinstall` scripts for suspicious patterns (e.g., unauthorized `curl` or `rm` commands).
25
+ - **Transactional Installs**: Automatic rollbacks ensure a failed installation never leaves your project in a corrupted state.
26
+
27
+ ### 📦 Handy CLI
28
+
29
+ - **Simplified Command Set**: Intuitive, easy-to-remember verbs like `get`, `drop`, and `syn`.
30
+ - **Remote Execution (`x`)**: Run any package without installing it (equivalent to `npx`), with built-in security auditing before every run.
31
+ - **Hive (Workspaces)**: First-class monorepo support with seamless discovery and cross-workspace script broad casting.
32
+
33
+ ---
34
+
35
+ ## 📦 Handy Commands Guide
36
+
37
+ | Command | Alias | Description |
38
+ | :----------------- | :-------------------- | :------------------------------------------- |
39
+ | `jpm get <pkg>` | `i`, `add`, `install` | Install packages and update `package.json` |
40
+ | `jpm drop <pkg>` | `remove`, `rm` | Remove packages and cleanup binaries |
41
+ | `jpm syn` | | Synchronize all dependencies (clean install) |
42
+ | `jpm x <pkg>` | `exec` | Execute remote package binary (like `npx`) |
43
+ | `jpm run <script>` | `do` | Execute a script defined in `package.json` |
44
+ | `jpm scan` | `audit` | Deep security and vulnerability audit |
45
+ | `jpm up` | `upgrade` | Upgrade dependencies to safe latest versions |
46
+ | `jpm peek` | `ls`, `list` | Inspect installed tree and metadata |
47
+ | `jpm info <pkg>` | `view` | Detailed package intelligence/manifest |
48
+ | `jpm hive` | `workspace` | Manage workspace clusters (Monorepos) |
49
+ | `jpm setup` | `init` | Initialize a new JPM project |
50
+ | `jpm cache` | | Manage the local high-speed cache |
51
+
52
+ ---
53
+
54
+ ## 🛠️ Detailed Usage
55
+
56
+ ### Installing Packages
57
+
58
+ ```bash
59
+ jpm get express # Add latest express
60
+ jpm get lodash@4.17.21 # Specific version
61
+ jpm get jest -D # Add to devDependencies
62
+ jpm get --save-exact # Pin versions precisely
63
+ ```
64
+
65
+ ### Remote Execution (`jpm x`)
66
+
67
+ Run packages without global installation. JPM performs a security scan on the remote package before it touches your machine.
68
+
69
+ ```bash
70
+ jpm x create-next-app@latest my-app
71
+ jpm x vite@latest --open
72
+ ```
73
+
74
+ ### Monorepo Management (`jpm hive`)
75
+
76
+ JPM makes managing complex project clusters effortless.
77
+
78
+ ```bash
79
+ jpm hive list # See all workspace packages
80
+ jpm hive run build # Build every package in the hive
81
+ jpm hive run test -f app # Run tests only in matching packages
82
+ ```
83
+
84
+ ---
85
+
86
+ ## 🔐 Security Deep Dive
87
+
88
+ ### Zip Slip Protection
89
+
90
+ JPM's extractor implements a robust `filter` that resolves every file in a tarball. If a package tries to extract a file outside it's legitimate directory (using `../` hacks), JPM kills the process and reports a security violation.
91
+
92
+ ### Insecure Protocol Blocking
93
+
94
+ In JPM's strict mode (enabled for all audits), any communication over plain `http:` is blocked to prevent man-in-the-middle attacks.
95
+
96
+ ### Mandatory Integrity
97
+
98
+ JPM refuses to install any package that doesn't provide a valid `sha512` or `shasum` from the registry, preventing "Trust-on-First-Use" (TOFU) vulnerabilities.
99
+
100
+ ---
101
+
102
+ ## ⚙️ Configuration
103
+
104
+ JPM is configured via `.jpmrc` files (INI format).
105
+
106
+ **Hierarchy:**
107
+
108
+ 1. CLI Flags (`--registry`, `--fast`)
109
+ 2. Project `.jpmrc`
110
+ 3. User `~/.jpmrc`
111
+
112
+ **Recommended `.jpmrc`:**
113
+
114
+ ```ini
115
+ registry=https://registry.npmjs.org/
116
+ save-exact=false
117
+ audit-level=moderate
118
+ loglevel=info
119
+ # Use --fast globally for speed, but manually verify integrity
120
+ # fast=true
121
+ ```
122
+
123
+ ---
124
+
125
+ ## � Installation
126
+
127
+ To build JPM from source and link it to your system:
128
+
129
+ ```bash
130
+ git clone https://github.com/whomaderules/jpm.git
131
+ cd jpm
132
+ npm install # Install build-time dependency (tar)
133
+ npm link # Link JPM to your global path
134
+ ```
135
+
136
+ ---
137
+
138
+ ## 📚 Documentation
139
+
140
+ - [Introduction](./docs/intro.md)
141
+ - [Getting Started](./docs/getting-started.md)
142
+ - [CLI Reference](./docs/cli.md)
143
+ - [Performance](./docs/performance.md)
144
+ - [Security](./docs/security.md)
145
+ - [Workspaces](./docs/workspaces.md)
146
+ - [Troubleshooting](./docs/troubleshooting.md)
147
+
148
+ **Built with ❤️ for the JS Community by [Muhammad Sulman](https://www.linkedin.com/in/sulmanedev)**
package/bin/jpm.js ADDED
@@ -0,0 +1,252 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Parses command-line arguments into a structured command, arguments, and flags object.
6
+ *
7
+ * @param {string[]} argv - Raw process.argv array
8
+ * @returns {{ command: string, args: string[], flags: Object.<string, string|boolean> }}
9
+ */
10
+ function parseArgs(argv) {
11
+ const args = [];
12
+ const flags = {};
13
+ for (const token of argv.slice(2)) {
14
+ if (token.startsWith('--')) {
15
+ const eq = token.indexOf('=');
16
+ const key = eq === -1 ? token.slice(2) : token.slice(2, eq);
17
+ const val = eq === -1 ? true : token.slice(eq + 1);
18
+ flags[key] = val;
19
+ } else if (token.startsWith('-') && token.length === 2) {
20
+ flags[token.slice(1)] = true;
21
+ } else {
22
+ args.push(token);
23
+ }
24
+ }
25
+ return { command: args[0], args: args.slice(1), flags };
26
+ }
27
+
28
+ const { command, args, flags } = parseArgs(process.argv);
29
+
30
+ // Initial configuration applying CLI-provided global overrides
31
+ const config = require('../src/utils/config');
32
+ if (flags.loglevel) config.setCLI({ loglevel: flags.loglevel });
33
+ if (flags.registry) config.setCLI({ registry: flags.registry });
34
+ if (flags.silent) config.setCLI({ loglevel: 'silent' });
35
+
36
+ const logger = require('../src/utils/logger');
37
+ logger.setLevel(config.loglevel);
38
+
39
+ /**
40
+ * Entry point for cache-related subcommands.
41
+ *
42
+ * @param {string[]} cmdArgs - Action and optional parameters
43
+ * @param {Object} cmdFlags - CLI flags
44
+ */
45
+ async function cacheCommand(cmdArgs, cmdFlags) {
46
+ const cache = require('../src/core/cache');
47
+ const { formatBytes } = require('../src/utils/fs');
48
+ const [action] = cmdArgs;
49
+
50
+ if (action === 'clean' || action === 'clear') {
51
+ cache.clear();
52
+ } else if (action === 'ls' || action === 'list') {
53
+ const items = cache.list();
54
+ if (!items.length) { logger.info('Cache is empty'); return; }
55
+ logger.table(items, ['name', 'version']);
56
+ } else {
57
+ const s = cache.stats();
58
+ logger.log('Cache directory: ' + logger.c.cyan(s.root || 'n/a'));
59
+ logger.log('Packages: ' + s.packages);
60
+ logger.log('Total size: ' + formatBytes(s.size));
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Entry point for workspace (hive) management.
66
+ *
67
+ * @param {string[]} cmdArgs - Subcommand and arguments
68
+ * @param {Object} cmdFlags - CLI flags
69
+ */
70
+ async function workspaceCommand(cmdArgs, cmdFlags) {
71
+ const Workspace = require('../src/workspace/workspace');
72
+ const ws = new Workspace(process.cwd());
73
+ const [action, ...rest] = cmdArgs;
74
+
75
+ if (!action || action === 'list' || action === 'ls') {
76
+ const packages = ws.getPackages();
77
+ if (!packages.length) { logger.warn('No workspaces found.'); return; }
78
+ logger.table(packages.map(p => ({ Name: p.name, Version: p.version, Path: p.dir })), ['Name', 'Version', 'Path']);
79
+ } else if (action === 'run') {
80
+ await ws.runScript(rest[0], { filter: cmdFlags.filter || cmdFlags.f });
81
+ } else if (action === 'link') {
82
+ await ws.link();
83
+ } else {
84
+ logger.error('Unknown workspace subcommand: ' + action);
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Displays version information for JPM and the current Node.js runtime.
90
+ */
91
+ function showVersion() {
92
+ const pkg = require('../package.json');
93
+ logger.log('jpm v' + pkg.version);
94
+ logger.log('node ' + process.version);
95
+ }
96
+
97
+ /**
98
+ * Renders the terminal help interface with available commands and descriptions.
99
+ */
100
+ function showHelp() {
101
+ const c = logger.c;
102
+ const lines = [
103
+ '',
104
+ c.bold('JPM') + ' \u2014 ' + c.yellow('Joint Package Manager') + ' v1.0.0',
105
+ c.gray('Universal, advanced, and blazing fast (Node.js & Bun)'),
106
+ '',
107
+ c.bold('Handy Commands:'),
108
+ ' ' + c.cyan('get [pkg@ver ...]') + ' Fetch and install packages',
109
+ ' ' + c.cyan('drop <pkg ...>') + ' Remove packages from project',
110
+ ' ' + c.cyan('syn') + ' Synchronize project dependencies',
111
+ ' ' + c.cyan('do <script> [args]') + ' Execute a package script',
112
+ ' ' + c.cyan('scan') + ' Deep security & integrity audit',
113
+ ' ' + c.cyan('up [pkg ...]') + ' Upgrade project dependencies',
114
+ ' ' + c.cyan('x <pkg> [args]') + ' Execute remote package binary',
115
+ '',
116
+ c.bold('Discovery & Metadata:'),
117
+ ' ' + c.cyan('peek') + ' Inspect installed dependency tree',
118
+ ' ' + c.cyan('find <query>') + ' Search for packages on registry',
119
+ ' ' + c.cyan('info <pkg>') + ' Detailed package intelligence',
120
+ '',
121
+ c.bold('Project Life-cycle:'),
122
+ ' ' + c.cyan('setup [-y]') + ' Initialize a new JPM project',
123
+ ' ' + c.cyan('ship') + ' Publish package to registry',
124
+ ' ' + c.cyan('config <get|set>') + ' Manage JPM environment (.jpmrc)',
125
+ ' ' + c.cyan('cache [clean|ls]') + ' Disk cache operations',
126
+ '',
127
+ c.bold('Advanced (Hive/Workspaces):'),
128
+ ' ' + c.cyan('hive list') + ' View workspace cluster',
129
+ ' ' + c.cyan('hive run <script>') + ' Broadcast script to all hives',
130
+ ' ' + c.cyan('hive link') + ' Inter-link workspace packages',
131
+ '',
132
+ c.bold('Global Controls:'),
133
+ ' ' + c.cyan('--fast') + ' Blind Install (Bypass all checks)',
134
+ ' ' + c.cyan('--registry <url>') + ' Custom registry target',
135
+ ' ' + c.cyan('--loglevel <level>') + ' Choose verbosity (silent|debug)',
136
+ ' ' + c.cyan('-D, --save-dev') + ' Mark as development tool',
137
+ '',
138
+ c.gray('JPM supports Node.js and Bun automatically.'),
139
+ '',
140
+ ];
141
+ logger.log(lines.join('\n'));
142
+ }
143
+
144
+ /**
145
+ * Command routing table mapping CLI verbs to their respective implementations.
146
+ */
147
+ const COMMANDS = {
148
+ get: () => require('../src/commands/install'),
149
+ drop: () => require('../src/commands/uninstall'),
150
+ syn: () => require('../src/commands/install'),
151
+ do: () => require('../src/commands/run'),
152
+ up: () => require('../src/commands/update'),
153
+ scan: () => require('../src/commands/audit'),
154
+ ship: () => require('../src/commands/publish'),
155
+ setup: () => require('../src/commands/init'),
156
+ peek: () => require('../src/commands/list'),
157
+ find: () => require('../src/commands/search'),
158
+ info: () => require('../src/commands/info'),
159
+ x: () => require('../src/commands/x'),
160
+ hive: () => workspaceCommand,
161
+
162
+ // Aliases & Standard Compatibility
163
+ install: () => require('../src/commands/install'),
164
+ i: () => require('../src/commands/install'),
165
+ add: () => require('../src/commands/install'),
166
+ uninstall: () => require('../src/commands/uninstall'),
167
+ remove: () => require('../src/commands/uninstall'),
168
+ rm: () => require('../src/commands/uninstall'),
169
+ un: () => require('../src/commands/uninstall'),
170
+ update: () => require('../src/commands/update'),
171
+ upgrade: () => require('../src/commands/update'),
172
+ outdated: () => require('../src/commands/update'),
173
+ search: () => require('../src/commands/search'),
174
+ publish: () => require('../src/commands/publish'),
175
+ audit: () => require('../src/commands/audit'),
176
+ run: () => require('../src/commands/run'),
177
+ init: () => require('../src/commands/init'),
178
+ create: () => require('../src/commands/init'),
179
+ show: () => require('../src/commands/info'),
180
+ view: () => require('../src/commands/info'),
181
+ list: () => require('../src/commands/list'),
182
+ ls: () => require('../src/commands/list'),
183
+ exec: () => require('../src/commands/x'),
184
+ workspace: () => workspaceCommand,
185
+ ws: () => workspaceCommand,
186
+ config: () => require('../src/commands/config'),
187
+ cfg: () => require('../src/commands/config'),
188
+ cache: () => cacheCommand,
189
+ help: () => showHelp,
190
+ version: () => showVersion,
191
+ };
192
+
193
+ // ── Global Error Boundaries ──────────────────────────────────────────────────
194
+
195
+ /**
196
+ * Global handler for uncaught exceptions to ensure graceful termination.
197
+ */
198
+ process.on('uncaughtException', (err) => {
199
+ logger.error('FATAL EXCEPTION: ' + err.message);
200
+ if (config.loglevel === 'debug' || config.loglevel === 'verbose') {
201
+ logger.log(err.stack);
202
+ }
203
+ logger.info('\nIf this persists, please report the issue at: https://github.com/whomaderules/jpm/issues');
204
+ process.exit(1);
205
+ });
206
+
207
+ /**
208
+ * Global handler for unhandled promise rejections.
209
+ */
210
+ process.on('unhandledRejection', (reason) => {
211
+ logger.error('UNHANDLED REJECTION: ' + (reason instanceof Error ? reason.message : reason));
212
+ if ((config.loglevel === 'debug' || config.loglevel === 'verbose') && reason instanceof Error) {
213
+ logger.log(reason.stack);
214
+ }
215
+ process.exit(1);
216
+ });
217
+
218
+ /**
219
+ * Principal execution routine of the JPM CLI.
220
+ */
221
+ async function main() {
222
+ if (!command || command === 'help' || flags.help || flags.h) {
223
+ await showHelp();
224
+ return;
225
+ }
226
+
227
+ if (command === 'version' || flags.version || flags.v) {
228
+ showVersion();
229
+ return;
230
+ }
231
+
232
+ const loader = COMMANDS[command];
233
+ if (!loader) {
234
+ logger.error('Unknown command: "' + command + '"');
235
+ logger.info('Run ' + logger.c.cyan('jpm help') + ' for usage.');
236
+ process.exit(1);
237
+ }
238
+
239
+ const handler = loader();
240
+ try {
241
+ await handler(args, flags, command);
242
+ } catch (err) {
243
+ if (flags.loglevel === 'debug' || flags.loglevel === 'verbose') {
244
+ logger.error(err.stack || err.message);
245
+ } else {
246
+ logger.error(err.message);
247
+ }
248
+ process.exit(1);
249
+ }
250
+ }
251
+
252
+ main();
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "jpm-pkg",
3
+ "version": "1.0.3",
4
+ "description": "Joint Package Manager — Joint, universal, advanced, and blazing fast package manager for Node.js and Bun.",
5
+ "main": "src/index.js",
6
+ "type": "commonjs",
7
+ "private": false,
8
+ "exports": {
9
+ ".": "./src/index.js"
10
+ },
11
+ "bin": {
12
+ "jpm": "./bin/jpm.js"
13
+ },
14
+ "scripts": {
15
+ "test": "jest",
16
+ "lint": "node --check src/**/*.js"
17
+ },
18
+ "keywords": [
19
+ "package-manager",
20
+ "npm",
21
+ "javascript",
22
+ "node",
23
+ "cli",
24
+ "fast",
25
+ "secure"
26
+ ],
27
+ "author": "Muhammad Sulman <whomaderules@gmail.com>",
28
+ "license": "MIT",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/whomaderules/jpm.git"
32
+ },
33
+ "bugs": {
34
+ "url": "https://github.com/whomaderules/jpm/issues"
35
+ },
36
+ "homepage": "https://github.com/whomaderules/jpm#readme",
37
+ "dependencies": {
38
+ "tar": "^6.2.0"
39
+ },
40
+ "devDependencies": {
41
+ "jest": "^29.7.0"
42
+ },
43
+ "engines": {
44
+ "node": ">=18.0.0"
45
+ },
46
+ "files": [
47
+ "src",
48
+ "bin",
49
+ "LICENSE.md",
50
+ "README.md"
51
+ ]
52
+ }
@@ -0,0 +1,56 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const auditSec = require('../security/audit');
6
+ const PackageJSON = require('../core/package-json');
7
+ const { Spinner } = require('../utils/progress');
8
+ const logger = require('../utils/logger');
9
+ const config = require('../utils/config');
10
+
11
+ module.exports = async function auditCmd(args, flags) {
12
+ const cwd = process.cwd();
13
+ const nodeModules = path.join(cwd, 'node_modules');
14
+
15
+ // Collect all installed packages from node_modules
16
+ const spinner = new Spinner('Scanning installed packages...').start();
17
+ const installed = [];
18
+
19
+ if (fs.existsSync(nodeModules)) {
20
+ for (const entry of fs.readdirSync(nodeModules, { withFileTypes: true })) {
21
+ // Handle scoped packages (@org)
22
+ if (entry.name.startsWith('@') && entry.isDirectory()) {
23
+ const scopeDir = path.join(nodeModules, entry.name);
24
+ for (const scoped of fs.readdirSync(scopeDir, { withFileTypes: true })) {
25
+ const pkgJson = path.join(scopeDir, scoped.name, 'package.json');
26
+ if (fs.existsSync(pkgJson)) {
27
+ try {
28
+ const pkg = JSON.parse(fs.readFileSync(pkgJson, 'utf8'));
29
+ installed.push({ name: pkg.name, version: pkg.version });
30
+ } catch { }
31
+ }
32
+ }
33
+ } else if (entry.isDirectory() && !entry.name.startsWith('.')) {
34
+ const pkgJson = path.join(nodeModules, entry.name, 'package.json');
35
+ if (fs.existsSync(pkgJson)) {
36
+ try {
37
+ const pkg = JSON.parse(fs.readFileSync(pkgJson, 'utf8'));
38
+ installed.push({ name: pkg.name, version: pkg.version });
39
+ } catch { }
40
+ }
41
+ }
42
+ }
43
+ }
44
+
45
+ spinner.succeed(`Found ${installed.length} installed packages`);
46
+
47
+ const level = flags.level || config.auditLevel || 'moderate';
48
+ const auditSpinner = new Spinner('Fetching advisory database...').start();
49
+
50
+ const { vulnerabilities, stats, total, error } = await auditSec.audit(installed, { level });
51
+ auditSpinner.succeed('Audit complete');
52
+
53
+ auditSec.formatAuditResults({ vulnerabilities, stats, total, error });
54
+
55
+ if (total > 0) process.exitCode = 1;
56
+ };
@@ -0,0 +1,59 @@
1
+ 'use strict';
2
+
3
+ const config = require('../utils/config');
4
+ const logger = require('../utils/logger');
5
+
6
+ module.exports = async function configCmd(args, flags) {
7
+ const [action, key, ...rest] = args;
8
+ const value = rest.join(' ');
9
+
10
+ switch (action) {
11
+ case 'get':
12
+ if (!key) { logger.error('Usage: jpm config get <key>'); process.exit(1); }
13
+ const val = config.get(key);
14
+ if (val === undefined) { logger.warn(`Key "${key}" not set`); }
15
+ else { logger.log(String(val)); }
16
+ break;
17
+
18
+ case 'set':
19
+ if (!key || value === '') { logger.error('Usage: jpm config set <key> <value>'); process.exit(1); }
20
+ const layer = flags.global ? 'user' : 'project';
21
+ config.set(key, value, layer);
22
+ logger.success(`Set ${key} = ${value} (${layer})`);
23
+ break;
24
+
25
+ case 'delete':
26
+ case 'del':
27
+ if (!key) { logger.error('Usage: jpm config delete <key>'); process.exit(1); }
28
+ config.delete(key, flags.global ? 'user' : 'project');
29
+ logger.success(`Deleted key: ${key}`);
30
+ break;
31
+
32
+ case 'list':
33
+ case 'ls': {
34
+ const all = config.list();
35
+ const rows = Object.entries(all).map(([k, v]) => ({ Key: k, Value: String(v) }));
36
+ logger.table(rows, ['Key', 'Value']);
37
+ break;
38
+ }
39
+
40
+ default:
41
+ logger.log(`
42
+ ${logger.c.bold('jpm config')} — manage configuration
43
+
44
+ ${logger.c.cyan('jpm config list')} List all configuration values
45
+ ${logger.c.cyan('jpm config get <key>')} Get a specific value
46
+ ${logger.c.cyan('jpm config set <key> <val>')} Set a value (project .jpmrc)
47
+ ${logger.c.cyan('jpm config delete <key>')} Delete a key
48
+
49
+ ${logger.c.gray('Add --global to modify ~/.jpmrc instead of .jpmrc')}
50
+
51
+ ${logger.c.bold('Common keys:')}
52
+ registry ${logger.c.gray('Registry URL (default: https://registry.npmjs.org/)')}
53
+ loglevel ${logger.c.gray('Log level: silent|error|warn|info|verbose|debug')}
54
+ save-exact ${logger.c.gray('Save exact versions (true/false)')}
55
+ audit-level ${logger.c.gray('Minimum audit severity: low|moderate|high|critical')}
56
+ cache ${logger.c.gray('Cache directory path')}
57
+ `);
58
+ }
59
+ };
@@ -0,0 +1,78 @@
1
+ 'use strict';
2
+
3
+ const registry = require('../core/registry');
4
+ const semver = require('../utils/semver');
5
+ const { Spinner } = require('../utils/progress');
6
+ const logger = require('../utils/logger');
7
+
8
+ module.exports = async function info(args, flags) {
9
+ if (!args.length) { logger.error('Usage: jpm info <package> [version]'); process.exit(1); }
10
+
11
+ let [pkgSpec] = args;
12
+ let name, version;
13
+
14
+ const lastAt = pkgSpec.lastIndexOf('@');
15
+ if (lastAt > 0) { name = pkgSpec.slice(0, lastAt); version = pkgSpec.slice(lastAt + 1); }
16
+ else { name = pkgSpec; version = 'latest'; }
17
+
18
+ const spinner = new Spinner(`Fetching info for ${name}...`).start();
19
+
20
+ let meta, latest, versions, tags;
21
+ try {
22
+ [meta, latest, versions, tags] = await Promise.all([
23
+ registry.getVersion(name, version),
24
+ registry.getLatest(name),
25
+ registry.getVersions(name),
26
+ registry.getDistTags(name),
27
+ ]);
28
+ spinner.succeed(`${name}@${meta.version}`);
29
+ } catch (err) {
30
+ spinner.fail(`Package "${name}" not found: ${err.message}`);
31
+ process.exit(1);
32
+ }
33
+
34
+ const c = logger.c;
35
+ logger.log('');
36
+ logger.log(`${c.bold(meta.name)} ${c.gray(`v${meta.version}`)}`);
37
+ if (meta.description) logger.log(meta.description);
38
+
39
+ logger.log('');
40
+ logger.log(`${c.cyan('latest')} ${latest}`);
41
+ logger.log(`${c.cyan('license')} ${meta.license || 'n/a'}`);
42
+ logger.log(`${c.cyan('author')} ${typeof meta.author === 'object' ? meta.author.name : (meta.author || 'n/a')}`);
43
+ if (meta.homepage) logger.log(`${c.cyan('homepage')} ${meta.homepage}`);
44
+ if (meta.repository?.url) logger.log(`${c.cyan('repository')} ${meta.repository.url.replace('git+', '')}`);
45
+
46
+ // Keywords
47
+ if (meta.keywords?.length) {
48
+ logger.log(`${c.cyan('keywords')} ${meta.keywords.slice(0, 10).join(', ')}`);
49
+ }
50
+
51
+ // Dist-tags
52
+ if (Object.keys(tags).length > 1) {
53
+ logger.log('');
54
+ logger.section('Tags');
55
+ for (const [tag, ver] of Object.entries(tags)) {
56
+ logger.log(` ${tag.padEnd(12)} ${ver}`);
57
+ }
58
+ }
59
+
60
+ // Dependencies
61
+ const deps = meta.dependencies || {};
62
+ if (Object.keys(deps).length) {
63
+ logger.log('');
64
+ logger.section('Dependencies');
65
+ for (const [dep, range] of Object.entries(deps)) {
66
+ logger.log(` ${c.cyan(dep.padEnd(30))} ${range}`);
67
+ }
68
+ }
69
+
70
+ // Recent versions
71
+ const recentVersions = semver.rsort(versions).slice(0, 10);
72
+ logger.log('');
73
+ logger.section(`Versions (latest 10 of ${versions.length})`);
74
+ logger.log(' ' + recentVersions.join(' '));
75
+
76
+ logger.log('');
77
+ logger.log(c.gray(`jpm install ${name}@${meta.version}`));
78
+ };