jpm-pkg 1.0.3 → 1.0.4
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/bin/jpm.js +7 -1
- package/package.json +1 -1
- package/src/commands/audit.js +70 -41
- package/src/commands/base-command.js +62 -0
- package/src/commands/config.js +94 -51
- package/src/commands/info.js +111 -64
- package/src/commands/init.js +117 -72
- package/src/commands/install.js +121 -112
- package/src/commands/list.js +126 -80
- package/src/commands/publish.js +155 -124
- package/src/commands/run.js +86 -52
- package/src/commands/search.js +56 -32
- package/src/commands/uninstall.js +47 -30
- package/src/commands/update.js +92 -51
- package/src/commands/x.js +134 -96
- package/src/core/cache.js +188 -87
- package/src/core/lockfile.js +73 -26
- package/src/core/package-json.js +119 -1
- package/src/core/registry.js +182 -143
- package/src/core/resolver.js +60 -36
package/bin/jpm.js
CHANGED
|
@@ -238,7 +238,13 @@ async function main() {
|
|
|
238
238
|
|
|
239
239
|
const handler = loader();
|
|
240
240
|
try {
|
|
241
|
-
|
|
241
|
+
if (typeof handler === 'function' && handler.prototype instanceof (require('../src/commands/base-command'))) {
|
|
242
|
+
const instance = new handler(command);
|
|
243
|
+
await instance.run(args, flags);
|
|
244
|
+
} else {
|
|
245
|
+
// Backward compatibility for functional commands
|
|
246
|
+
await handler(args, flags, command);
|
|
247
|
+
}
|
|
242
248
|
} catch (err) {
|
|
243
249
|
if (flags.loglevel === 'debug' || flags.loglevel === 'verbose') {
|
|
244
250
|
logger.error(err.stack || err.message);
|
package/package.json
CHANGED
package/src/commands/audit.js
CHANGED
|
@@ -2,55 +2,84 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('node:fs');
|
|
4
4
|
const path = require('node:path');
|
|
5
|
+
const BaseCommand = require('./base-command');
|
|
5
6
|
const auditSec = require('../security/audit');
|
|
6
|
-
const PackageJSON = require('../core/package-json');
|
|
7
7
|
const { Spinner } = require('../utils/progress');
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* AuditCommand handles the 'jpm audit' and 'jpm scan' commands.
|
|
11
|
+
* It performs a security and integrity audit of all installed packages.
|
|
12
|
+
*/
|
|
13
|
+
class AuditCommand extends BaseCommand {
|
|
14
|
+
constructor() {
|
|
15
|
+
super('audit');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Executes the security audit.
|
|
20
|
+
*
|
|
21
|
+
* @param {string[]} args - Optional arguments
|
|
22
|
+
* @param {Object} flags - CLI flags (e.g., --level)
|
|
23
|
+
* @returns {Promise<void>}
|
|
24
|
+
*/
|
|
25
|
+
async run(args, flags) {
|
|
26
|
+
const cwd = process.cwd();
|
|
27
|
+
const nodeModules = path.join(cwd, 'node_modules');
|
|
28
|
+
|
|
29
|
+
const spinner = new Spinner('Scanning installed packages...').start();
|
|
30
|
+
const installed = [];
|
|
31
|
+
|
|
32
|
+
if (fs.existsSync(nodeModules)) {
|
|
33
|
+
for (const entry of fs.readdirSync(nodeModules, { withFileTypes: true })) {
|
|
34
|
+
// Handle scoped packages (@org/pkg)
|
|
35
|
+
if (entry.name.startsWith('@') && entry.isDirectory()) {
|
|
36
|
+
const scopeDir = path.join(nodeModules, entry.name);
|
|
37
|
+
for (const scoped of fs.readdirSync(scopeDir, { withFileTypes: true })) {
|
|
38
|
+
const pkgJsonPath = path.join(scopeDir, scoped.name, 'package.json');
|
|
39
|
+
this._tryAddPackage(pkgJsonPath, installed);
|
|
31
40
|
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
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 { }
|
|
41
|
+
} else if (entry.isDirectory() && !entry.name.startsWith('.')) {
|
|
42
|
+
const pkgJsonPath = path.join(nodeModules, entry.name, 'package.json');
|
|
43
|
+
this._tryAddPackage(pkgJsonPath, installed);
|
|
40
44
|
}
|
|
41
45
|
}
|
|
42
46
|
}
|
|
43
|
-
}
|
|
44
47
|
|
|
45
|
-
|
|
48
|
+
spinner.succeed(`Found ${installed.length} installed packages`);
|
|
49
|
+
|
|
50
|
+
const level = flags.level || this.config.auditLevel || 'moderate';
|
|
51
|
+
const auditSpinner = new Spinner('Fetching advisory database...').start();
|
|
46
52
|
|
|
47
|
-
|
|
48
|
-
|
|
53
|
+
const { vulnerabilities, stats, total, error } = await auditSec.audit(installed, { level });
|
|
54
|
+
auditSpinner.succeed('Audit complete');
|
|
49
55
|
|
|
50
|
-
|
|
51
|
-
|
|
56
|
+
auditSec.formatAuditResults({ vulnerabilities, stats, total, error });
|
|
57
|
+
|
|
58
|
+
if (total > 0) {
|
|
59
|
+
process.exitCode = 1;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Internal helper to read and add package metadata to the list.
|
|
65
|
+
*
|
|
66
|
+
* @param {string} pkgJsonPath - Path to the package.json file
|
|
67
|
+
* @param {Object[]} list - Reference to the installed packages list
|
|
68
|
+
* @private
|
|
69
|
+
*/
|
|
70
|
+
_tryAddPackage(pkgJsonPath, list) {
|
|
71
|
+
if (fs.existsSync(pkgJsonPath)) {
|
|
72
|
+
try {
|
|
73
|
+
const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'));
|
|
74
|
+
if (pkg.name && pkg.version) {
|
|
75
|
+
list.push({ name: pkg.name, version: pkg.version });
|
|
76
|
+
}
|
|
77
|
+
} catch (err) {
|
|
78
|
+
// Silently skip malformed package.json files
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
52
83
|
|
|
53
|
-
|
|
84
|
+
module.exports = AuditCommand;
|
|
54
85
|
|
|
55
|
-
if (total > 0) process.exitCode = 1;
|
|
56
|
-
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const logger = require('../utils/logger');
|
|
4
|
+
const config = require('../utils/config');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* BaseCommand is the abstract base class for all JPM CLI commands.
|
|
8
|
+
* It provides common infrastructure for argument parsing, validation, and logging.
|
|
9
|
+
*
|
|
10
|
+
* Each command must implement the `run` method.
|
|
11
|
+
*
|
|
12
|
+
* @abstract
|
|
13
|
+
*/
|
|
14
|
+
class BaseCommand {
|
|
15
|
+
/**
|
|
16
|
+
* @param {string} name - The canonical name of the command (e.g., 'install')
|
|
17
|
+
* @param {Object} [options={}] - Command configuration options
|
|
18
|
+
*/
|
|
19
|
+
constructor(name, options = {}) {
|
|
20
|
+
/** @type {string} */
|
|
21
|
+
this.name = name;
|
|
22
|
+
/** @type {Object} */
|
|
23
|
+
this.options = options;
|
|
24
|
+
/** @type {Object} */
|
|
25
|
+
this.config = config;
|
|
26
|
+
/** @type {Object} */
|
|
27
|
+
this.logger = logger;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The main execution entry point for the command.
|
|
32
|
+
* Must be implemented by subclasses.
|
|
33
|
+
*
|
|
34
|
+
* @param {string[]} args - Positional command line arguments
|
|
35
|
+
* @param {Object} flags - Parsed CLI flags/options
|
|
36
|
+
* @returns {Promise<void>}
|
|
37
|
+
* @abstract
|
|
38
|
+
*/
|
|
39
|
+
async run(args, flags) {
|
|
40
|
+
throw new Error(`Command "${this.name}" does not implement the run() method.`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Displays command-specific help information.
|
|
45
|
+
* Can be overridden by subclasses for more detailed help.
|
|
46
|
+
*/
|
|
47
|
+
help() {
|
|
48
|
+
this.logger.log(`Usage: jpm ${this.name} [options] [args]`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Validates command arguments. Can be overridden by subclasses.
|
|
53
|
+
*
|
|
54
|
+
* @param {string[]} args - Positional arguments
|
|
55
|
+
* @returns {boolean} True if arguments are valid
|
|
56
|
+
*/
|
|
57
|
+
validate(args) {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
module.exports = BaseCommand;
|
package/src/commands/config.js
CHANGED
|
@@ -1,59 +1,102 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
3
|
+
const BaseCommand = require('./base-command');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* ConfigCommand handles the 'jpm config' commands for managing settings.
|
|
7
|
+
* It provides subcommands for getting, setting, deleting, and listing configurations.
|
|
8
|
+
*/
|
|
9
|
+
class ConfigCommand extends BaseCommand {
|
|
10
|
+
constructor() {
|
|
11
|
+
super('config');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Executes the configuration management actions.
|
|
16
|
+
*
|
|
17
|
+
* @param {string[]} args - Action and optional key/value arguments
|
|
18
|
+
* @param {Object} flags - CLI flags (e.g., --global)
|
|
19
|
+
* @returns {Promise<void>}
|
|
20
|
+
*/
|
|
21
|
+
async run(args, flags) {
|
|
22
|
+
const [action, key, ...rest] = args;
|
|
23
|
+
const value = rest.join(' ');
|
|
24
|
+
|
|
25
|
+
switch (action) {
|
|
26
|
+
case 'get':
|
|
27
|
+
if (!key) {
|
|
28
|
+
this.logger.error('Usage: jpm config get <key>');
|
|
29
|
+
throw new Error('Key missing for config get.');
|
|
30
|
+
}
|
|
31
|
+
const val = this.config.get(key);
|
|
32
|
+
if (val === undefined) {
|
|
33
|
+
this.logger.warn(`Key "${key}" not set`);
|
|
34
|
+
} else {
|
|
35
|
+
this.logger.log(String(val));
|
|
36
|
+
}
|
|
37
|
+
break;
|
|
38
|
+
|
|
39
|
+
case 'set':
|
|
40
|
+
if (!key || value === '') {
|
|
41
|
+
this.logger.error('Usage: jpm config set <key> <value>');
|
|
42
|
+
throw new Error('Key or value missing for config set.');
|
|
43
|
+
}
|
|
44
|
+
const layer = flags.global ? 'user' : 'project';
|
|
45
|
+
this.config.set(key, value, layer);
|
|
46
|
+
this.logger.success(`Set ${key} = ${value} (${layer})`);
|
|
47
|
+
break;
|
|
48
|
+
|
|
49
|
+
case 'delete':
|
|
50
|
+
case 'del':
|
|
51
|
+
if (!key) {
|
|
52
|
+
this.logger.error('Usage: jpm config delete <key>');
|
|
53
|
+
throw new Error('Key missing for config delete.');
|
|
54
|
+
}
|
|
55
|
+
this.config.delete(key, flags.global ? 'user' : 'project');
|
|
56
|
+
this.logger.success(`Deleted key: ${key}`);
|
|
57
|
+
break;
|
|
58
|
+
|
|
59
|
+
case 'list':
|
|
60
|
+
case 'ls': {
|
|
61
|
+
const allConfigs = this.config.list();
|
|
62
|
+
const rows = Object.entries(allConfigs).map(([k, v]) => ({
|
|
63
|
+
Key: k,
|
|
64
|
+
Value: String(v)
|
|
65
|
+
}));
|
|
66
|
+
this.logger.table(rows, ['Key', 'Value']);
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
default:
|
|
71
|
+
this._renderHelp();
|
|
38
72
|
}
|
|
73
|
+
}
|
|
39
74
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Internal helper to render the config-specific help message.
|
|
77
|
+
* @private
|
|
78
|
+
*/
|
|
79
|
+
_renderHelp() {
|
|
80
|
+
const c = this.logger.c;
|
|
81
|
+
this.logger.log(`
|
|
82
|
+
${c.bold('jpm config')} — manage configuration
|
|
43
83
|
|
|
44
|
-
${
|
|
45
|
-
${
|
|
46
|
-
${
|
|
47
|
-
${
|
|
84
|
+
${c.cyan('jpm config list')} List all configuration values
|
|
85
|
+
${c.cyan('jpm config get <key>')} Get a specific value
|
|
86
|
+
${c.cyan('jpm config set <key> <val>')} Set a value (project .jpmrc)
|
|
87
|
+
${c.cyan('jpm config delete <key>')} Delete a key
|
|
48
88
|
|
|
49
|
-
${
|
|
50
|
-
|
|
51
|
-
${
|
|
52
|
-
registry ${
|
|
53
|
-
loglevel ${
|
|
54
|
-
save-exact ${
|
|
55
|
-
audit-level ${
|
|
56
|
-
cache ${
|
|
89
|
+
${c.gray('Add --global to modify ~/.jpmrc instead of .jpmrc')}
|
|
90
|
+
|
|
91
|
+
${c.bold('Common keys:')}
|
|
92
|
+
registry ${c.gray('Registry URL (default: https://registry.npmjs.org/)')}
|
|
93
|
+
loglevel ${c.gray('Log level: silent|error|warn|info|verbose|debug')}
|
|
94
|
+
save-exact ${c.gray('Save exact versions (true/false)')}
|
|
95
|
+
audit-level ${c.gray('Minimum audit severity: low|moderate|high|critical')}
|
|
96
|
+
cache ${c.gray('Cache directory path')}
|
|
57
97
|
`);
|
|
58
98
|
}
|
|
59
|
-
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
module.exports = ConfigCommand;
|
|
102
|
+
|
package/src/commands/info.js
CHANGED
|
@@ -1,78 +1,125 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const BaseCommand = require('./base-command');
|
|
3
4
|
const registry = require('../core/registry');
|
|
4
5
|
const semver = require('../utils/semver');
|
|
5
6
|
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
7
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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(', ')}`);
|
|
8
|
+
/**
|
|
9
|
+
* InfoCommand handles the 'jpm info' and 'jpm view' commands.
|
|
10
|
+
* It fetches and displays detailed metadata about a specific package and version.
|
|
11
|
+
*/
|
|
12
|
+
class InfoCommand extends BaseCommand {
|
|
13
|
+
constructor() {
|
|
14
|
+
super('info');
|
|
49
15
|
}
|
|
50
16
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Executes the package information retrieval.
|
|
19
|
+
*
|
|
20
|
+
* @param {string[]} args - Package specification (e.g., 'pkg@1.2.3' or 'pkg')
|
|
21
|
+
* @param {Object} flags - CLI flags
|
|
22
|
+
* @returns {Promise<void>}
|
|
23
|
+
*/
|
|
24
|
+
async run(args, flags) {
|
|
25
|
+
if (!args.length) {
|
|
26
|
+
this.logger.error('Usage: jpm info <package> [version]');
|
|
27
|
+
throw new Error('No package name provided.');
|
|
57
28
|
}
|
|
58
|
-
}
|
|
59
29
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
30
|
+
let [pkgSpec] = args;
|
|
31
|
+
let name, version;
|
|
32
|
+
|
|
33
|
+
// Parse name and version from spec (handling scoped packages)
|
|
34
|
+
const lastAt = pkgSpec.lastIndexOf('@');
|
|
35
|
+
if (lastAt > 0 && !(pkgSpec.startsWith('@') && pkgSpec.indexOf('@', 1) === -1)) {
|
|
36
|
+
name = pkgSpec.slice(0, lastAt);
|
|
37
|
+
version = pkgSpec.slice(lastAt + 1);
|
|
38
|
+
} else {
|
|
39
|
+
name = pkgSpec;
|
|
40
|
+
version = 'latest';
|
|
67
41
|
}
|
|
42
|
+
|
|
43
|
+
const spinner = new Spinner(`Fetching info for ${name}...`).start();
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const [meta, latest, versions, tags] = await Promise.all([
|
|
47
|
+
registry.getVersion(name, version),
|
|
48
|
+
registry.getLatest(name),
|
|
49
|
+
registry.getVersions(name),
|
|
50
|
+
registry.getDistTags(name),
|
|
51
|
+
]);
|
|
52
|
+
spinner.succeed(`${name}@${meta.version}`);
|
|
53
|
+
|
|
54
|
+
const c = this.logger.c;
|
|
55
|
+
this.logger.log('');
|
|
56
|
+
this.logger.log(`${c.bold(meta.name)} ${c.gray(`v${meta.version}`)}`);
|
|
57
|
+
if (meta.description) {
|
|
58
|
+
this.logger.log(meta.description);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
this.logger.log('');
|
|
62
|
+
this.logger.log(`${c.cyan('latest')} ${latest}`);
|
|
63
|
+
this.logger.log(`${c.cyan('license')} ${meta.license || 'n/a'}`);
|
|
64
|
+
this.logger.log(`${c.cyan('author')} ${this._formatAuthor(meta.author)}`);
|
|
65
|
+
if (meta.homepage) {
|
|
66
|
+
this.logger.log(`${c.cyan('homepage')} ${meta.homepage}`);
|
|
67
|
+
}
|
|
68
|
+
if (meta.repository?.url) {
|
|
69
|
+
this.logger.log(`${c.cyan('repository')} ${meta.repository.url.replace('git+', '')}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Render keywords
|
|
73
|
+
if (meta.keywords?.length) {
|
|
74
|
+
this.logger.log(`${c.cyan('keywords')} ${meta.keywords.slice(0, 10).join(', ')}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Render distribution tags
|
|
78
|
+
if (Object.keys(tags).length > 1) {
|
|
79
|
+
this.logger.log('');
|
|
80
|
+
this.logger.section('Tags');
|
|
81
|
+
for (const [tag, ver] of Object.entries(tags)) {
|
|
82
|
+
this.logger.log(` ${tag.padEnd(12)} ${ver}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Render dependencies
|
|
87
|
+
const deps = meta.dependencies || {};
|
|
88
|
+
if (Object.keys(deps).length) {
|
|
89
|
+
this.logger.log('');
|
|
90
|
+
this.logger.section('Dependencies');
|
|
91
|
+
for (const [dep, range] of Object.entries(deps)) {
|
|
92
|
+
this.logger.log(` ${c.cyan(dep.padEnd(30))} ${range}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Render recent versions
|
|
97
|
+
const recentVersions = semver.rsort(versions).slice(0, 10);
|
|
98
|
+
this.logger.log('');
|
|
99
|
+
this.logger.section(`Versions (latest 10 of ${versions.length})`);
|
|
100
|
+
this.logger.log(' ' + recentVersions.join(' '));
|
|
101
|
+
|
|
102
|
+
this.logger.log('');
|
|
103
|
+
this.logger.log(c.gray(`jpm install ${name}@${meta.version}`));
|
|
104
|
+
} catch (err) {
|
|
105
|
+
spinner.fail(`Package "${name}" not found: ${err.message}`);
|
|
106
|
+
throw err;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Formats author information into a readable string.
|
|
112
|
+
*
|
|
113
|
+
* @param {string|Object} author - Author data from package.json
|
|
114
|
+
* @returns {string} Formatted author string
|
|
115
|
+
* @private
|
|
116
|
+
*/
|
|
117
|
+
_formatAuthor(author) {
|
|
118
|
+
if (!author) return 'n/a';
|
|
119
|
+
if (typeof author === 'object') return author.name || 'n/a';
|
|
120
|
+
return author;
|
|
68
121
|
}
|
|
122
|
+
}
|
|
69
123
|
|
|
70
|
-
|
|
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(' '));
|
|
124
|
+
module.exports = InfoCommand;
|
|
75
125
|
|
|
76
|
-
logger.log('');
|
|
77
|
-
logger.log(c.gray(`jpm install ${name}@${meta.version}`));
|
|
78
|
-
};
|