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/src/commands/run.js
CHANGED
|
@@ -2,71 +2,105 @@
|
|
|
2
2
|
|
|
3
3
|
const { spawnSync } = require('node:child_process');
|
|
4
4
|
const path = require('node:path');
|
|
5
|
+
const BaseCommand = require('./base-command');
|
|
5
6
|
const PackageJSON = require('../core/package-json');
|
|
6
|
-
const logger = require('../utils/logger');
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
/**
|
|
9
|
+
* RunCommand handles the 'jpm run' command.
|
|
10
|
+
* It executes scripts defined in package.json, supporting pre/post hooks.
|
|
11
|
+
*/
|
|
12
|
+
class RunCommand extends BaseCommand {
|
|
13
|
+
constructor() {
|
|
14
|
+
super('run');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Executes a specified script from package.json.
|
|
19
|
+
*
|
|
20
|
+
* @param {string[]} args - Positional arguments (script name and optional parameters)
|
|
21
|
+
* @param {Object} flags - CLI flags
|
|
22
|
+
* @returns {Promise<void>}
|
|
23
|
+
*/
|
|
24
|
+
async run(args, flags) {
|
|
25
|
+
const [scriptName, ...scriptArgs] = args;
|
|
26
|
+
const cwd = process.cwd();
|
|
27
|
+
const pkgJson = PackageJSON.fromDir(cwd);
|
|
28
|
+
const scripts = pkgJson.scripts;
|
|
13
29
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
30
|
+
// If no script name provided, list all available scripts
|
|
31
|
+
if (!scriptName) {
|
|
32
|
+
this.logger.section('Available scripts:');
|
|
33
|
+
const scriptEntries = Object.entries(scripts);
|
|
34
|
+
if (!scriptEntries.length) {
|
|
35
|
+
this.logger.info('No scripts defined in package.json');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
for (const [name, cmd] of scriptEntries) {
|
|
39
|
+
this.logger.log(` ${this.logger.c.cyan(name.padEnd(20))} ${this.logger.c.gray(cmd)}`);
|
|
40
|
+
}
|
|
19
41
|
return;
|
|
20
42
|
}
|
|
21
|
-
for (const [name, cmd] of Object.entries(scripts)) {
|
|
22
|
-
logger.log(` ${logger.c.cyan(name.padEnd(20))} ${logger.c.gray(cmd)}`);
|
|
23
|
-
}
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
43
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
44
|
+
if (!scripts[scriptName]) {
|
|
45
|
+
this.logger.error(`Script "${scriptName}" not found in package.json`);
|
|
46
|
+
this.logger.info(`Available: ${Object.keys(scripts).join(', ')}`);
|
|
47
|
+
throw new Error(`Script "${scriptName}" not found.`);
|
|
48
|
+
}
|
|
32
49
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
50
|
+
// Execute pre-hook if it exists
|
|
51
|
+
const preHook = `pre${scriptName}`;
|
|
52
|
+
if (scripts[preHook]) {
|
|
53
|
+
this.logger.info(`> ${pkgJson.name} ${preHook}`);
|
|
54
|
+
this._executeScript(preHook, scripts, cwd, scriptArgs);
|
|
55
|
+
}
|
|
38
56
|
|
|
39
|
-
|
|
40
|
-
|
|
57
|
+
this.logger.info(`\n> ${pkgJson.name} ${scriptName}`);
|
|
58
|
+
this.logger.info(`> ${scripts[scriptName]}\n`);
|
|
41
59
|
|
|
42
|
-
|
|
60
|
+
const result = this._executeScript(scriptName, scripts, cwd, scriptArgs);
|
|
43
61
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
62
|
+
// Execute post-hook if it exists
|
|
63
|
+
const postHook = `post${scriptName}`;
|
|
64
|
+
if (scripts[postHook]) {
|
|
65
|
+
this.logger.info(`> ${pkgJson.name} ${postHook}`);
|
|
66
|
+
this._executeScript(postHook, scripts, cwd, scriptArgs);
|
|
67
|
+
}
|
|
49
68
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
69
|
+
if (result.status !== 0) {
|
|
70
|
+
const code = result.status || 1;
|
|
71
|
+
this.logger.error(`Script "${scriptName}" exited with code ${code}`);
|
|
72
|
+
process.exit(code);
|
|
73
|
+
}
|
|
53
74
|
}
|
|
54
|
-
};
|
|
55
75
|
|
|
56
|
-
|
|
57
|
-
|
|
76
|
+
/**
|
|
77
|
+
* Internal helper to spawn a process for a script.
|
|
78
|
+
* Adds node_modules/.bin to the PATH.
|
|
79
|
+
*
|
|
80
|
+
* @param {string} name - Script name to run
|
|
81
|
+
* @param {Object} scripts - Script definitions from package.json
|
|
82
|
+
* @param {string} cwd - Current working directory
|
|
83
|
+
* @param {string[]} extraArgs - Additional arguments to pass to the script
|
|
84
|
+
* @returns {import('node:child_process').SpawnSyncReturns<Buffer>}
|
|
85
|
+
* @private
|
|
86
|
+
*/
|
|
87
|
+
_executeScript(name, scripts, cwd, extraArgs) {
|
|
88
|
+
const cmd = scripts[name] + (extraArgs.length ? ' ' + extraArgs.join(' ') : '');
|
|
58
89
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
};
|
|
90
|
+
const binPath = path.join(cwd, 'node_modules', '.bin');
|
|
91
|
+
const env = {
|
|
92
|
+
...process.env,
|
|
93
|
+
PATH: `${binPath}${process.platform === 'win32' ? ';' : ':'}${process.env.PATH}`,
|
|
94
|
+
};
|
|
65
95
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
96
|
+
return spawnSync(cmd, {
|
|
97
|
+
cwd,
|
|
98
|
+
shell: true,
|
|
99
|
+
stdio: 'inherit',
|
|
100
|
+
env,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
72
103
|
}
|
|
104
|
+
|
|
105
|
+
module.exports = RunCommand;
|
|
106
|
+
|
package/src/commands/search.js
CHANGED
|
@@ -1,41 +1,65 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const BaseCommand = require('./base-command');
|
|
3
4
|
const registry = require('../core/registry');
|
|
4
5
|
const { Spinner } = require('../utils/progress');
|
|
5
|
-
const logger = require('../utils/logger');
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
/**
|
|
8
|
+
* SearchCommand handles the 'jpm search' and 'jpm find' commands.
|
|
9
|
+
* It queries the registry for packages matching a search term and displays results.
|
|
10
|
+
*/
|
|
11
|
+
class SearchCommand extends BaseCommand {
|
|
12
|
+
constructor() {
|
|
13
|
+
super('search');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Executes the package search.
|
|
18
|
+
*
|
|
19
|
+
* @param {string[]} args - Search query terms
|
|
20
|
+
* @param {Object} flags - CLI flags (e.g., --size)
|
|
21
|
+
* @returns {Promise<void>}
|
|
22
|
+
*/
|
|
23
|
+
async run(args, flags) {
|
|
24
|
+
const query = args.join(' ');
|
|
25
|
+
if (!query) {
|
|
26
|
+
this.logger.error('Usage: jpm search <query>');
|
|
27
|
+
throw new Error('No search query provided.');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const size = parseInt(flags.size || flags.n || '20', 10);
|
|
31
|
+
const spinner = new Spinner(`Searching for "${query}"...`).start();
|
|
10
32
|
|
|
11
|
-
|
|
12
|
-
|
|
33
|
+
try {
|
|
34
|
+
const results = await registry.search(query, { size });
|
|
35
|
+
spinner.succeed(`Found ${results.length} result(s) for "${query}"`);
|
|
13
36
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
37
|
+
if (!results.length) {
|
|
38
|
+
this.logger.info('No packages found.');
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
this.logger.log('');
|
|
43
|
+
const rows = results.map(r => ({
|
|
44
|
+
Name: r.package?.name || '',
|
|
45
|
+
Version: r.package?.version || '',
|
|
46
|
+
Description: (r.package?.description || '').slice(0, 55),
|
|
47
|
+
Downloads: r.downloads?.weekly != null
|
|
48
|
+
? r.downloads.weekly.toLocaleString()
|
|
49
|
+
: 'n/a',
|
|
50
|
+
Score: r.score?.final != null
|
|
51
|
+
? (r.score.final * 100).toFixed(0) + '%'
|
|
52
|
+
: 'n/a',
|
|
53
|
+
}));
|
|
54
|
+
|
|
55
|
+
this.logger.table(rows, ['Name', 'Version', 'Description', 'Downloads', 'Score']);
|
|
56
|
+
this.logger.log(`\n${this.logger.c.gray('Run `jpm info <package>` for details. `jpm install <package>` to install.')}`);
|
|
57
|
+
} catch (err) {
|
|
58
|
+
spinner.fail(`Search failed: ${err.message}`);
|
|
59
|
+
throw err;
|
|
60
|
+
}
|
|
20
61
|
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
module.exports = SearchCommand;
|
|
21
65
|
|
|
22
|
-
spinner.succeed(`Found ${results.length} result(s) for "${query}"`);
|
|
23
|
-
|
|
24
|
-
if (!results.length) { logger.info('No packages found.'); return; }
|
|
25
|
-
|
|
26
|
-
logger.log('');
|
|
27
|
-
const rows = results.map(r => ({
|
|
28
|
-
Name: r.package?.name || '',
|
|
29
|
-
Version: r.package?.version || '',
|
|
30
|
-
Description: (r.package?.description || '').slice(0, 55),
|
|
31
|
-
Downloads: r.downloads?.weekly != null
|
|
32
|
-
? r.downloads.weekly.toLocaleString()
|
|
33
|
-
: 'n/a',
|
|
34
|
-
Score: r.score?.final != null
|
|
35
|
-
? (r.score.final * 100).toFixed(0) + '%'
|
|
36
|
-
: 'n/a',
|
|
37
|
-
}));
|
|
38
|
-
logger.table(rows, ['Name', 'Version', 'Description', 'Downloads', 'Score']);
|
|
39
|
-
|
|
40
|
-
logger.log(`\n${logger.c.gray('Run `jpm info <package>` for details. `jpm install <package>` to install.')}`);
|
|
41
|
-
};
|
|
@@ -1,48 +1,65 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const BaseCommand = require('./base-command');
|
|
3
4
|
const PackageJSON = require('../core/package-json');
|
|
4
5
|
const Installer = require('../core/installer');
|
|
5
6
|
const Lockfile = require('../core/lockfile');
|
|
6
|
-
const Resolver = require('../core/resolver');
|
|
7
7
|
const { Spinner } = require('../utils/progress');
|
|
8
|
-
const logger = require('../utils/logger');
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
/**
|
|
10
|
+
* UninstallCommand handles the 'jpm uninstall' command and its aliases.
|
|
11
|
+
* It removes packages from node_modules and updates package.json and the lockfile.
|
|
12
|
+
*/
|
|
13
|
+
class UninstallCommand extends BaseCommand {
|
|
14
|
+
constructor() {
|
|
15
|
+
super('uninstall');
|
|
14
16
|
}
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Executes the uninstallation process for one or more packages.
|
|
20
|
+
*
|
|
21
|
+
* @param {string[]} args - Names of packages to uninstall
|
|
22
|
+
* @param {Object} flags - CLI flags
|
|
23
|
+
* @returns {Promise<void>}
|
|
24
|
+
*/
|
|
25
|
+
async run(args, flags) {
|
|
26
|
+
if (!args.length) {
|
|
27
|
+
this.logger.error('Usage: jpm uninstall <package> [package2 ...]');
|
|
28
|
+
throw new Error('No package names provided for uninstallation.');
|
|
29
|
+
}
|
|
20
30
|
|
|
21
|
-
|
|
22
|
-
const
|
|
31
|
+
const cwd = process.cwd();
|
|
32
|
+
const pkgJson = PackageJSON.fromDir(cwd);
|
|
33
|
+
const lockfile = new Lockfile(cwd);
|
|
34
|
+
const installer = new Installer(cwd);
|
|
23
35
|
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
36
|
+
for (const name of args) {
|
|
37
|
+
const spinner = new Spinner(`Removing ${name}...`).start();
|
|
38
|
+
|
|
39
|
+
const removed = await installer.uninstall(name);
|
|
40
|
+
if (!removed) {
|
|
41
|
+
spinner.warn(`${name} was not installed`);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
29
44
|
|
|
30
|
-
|
|
31
|
-
|
|
45
|
+
// Remove from package.json
|
|
46
|
+
pkgJson.removeDependency(name);
|
|
32
47
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
48
|
+
// Remove from lock file (remove all versions for this package name)
|
|
49
|
+
const lockedPackages = lockfile.allPackages();
|
|
50
|
+
for (const pkg of lockedPackages) {
|
|
51
|
+
if (pkg.name === name) {
|
|
52
|
+
lockfile.removePackage(pkg.name, pkg.version);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
37
55
|
|
|
38
|
-
|
|
39
|
-
lockfile.removePackage(n, v);
|
|
56
|
+
spinner.succeed(`Removed ${name}`);
|
|
40
57
|
}
|
|
41
58
|
|
|
42
|
-
|
|
59
|
+
pkgJson.save();
|
|
60
|
+
lockfile.save();
|
|
61
|
+
this.logger.success(`\nRemoved ${args.length} package(s)`);
|
|
43
62
|
}
|
|
63
|
+
}
|
|
44
64
|
|
|
45
|
-
|
|
46
|
-
lockfile.save();
|
|
47
|
-
logger.success(`\nRemoved ${args.length} package(s)`);
|
|
48
|
-
};
|
|
65
|
+
module.exports = UninstallCommand;
|
package/src/commands/update.js
CHANGED
|
@@ -1,63 +1,104 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const BaseCommand = require('./base-command');
|
|
3
4
|
const PackageJSON = require('../core/package-json');
|
|
4
5
|
const registry = require('../core/registry');
|
|
5
6
|
const semver = require('../utils/semver');
|
|
6
7
|
const { Spinner } = require('../utils/progress');
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* UpdateCommand handles the 'jpm update' and 'jpm outdated' commands.
|
|
11
|
+
* It checks for newer versions of installed packages and can perform updates.
|
|
12
|
+
*/
|
|
13
|
+
class UpdateCommand extends BaseCommand {
|
|
14
|
+
constructor() {
|
|
15
|
+
super('update');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Executes the update or outdated check.
|
|
20
|
+
*
|
|
21
|
+
* @param {string[]} args - Optional package names to limit the check
|
|
22
|
+
* @param {Object} flags - CLI flags
|
|
23
|
+
* @returns {Promise<void>}
|
|
24
|
+
*/
|
|
25
|
+
async run(args, flags) {
|
|
26
|
+
const cwd = process.cwd();
|
|
27
|
+
const pkgJson = PackageJSON.fromDir(cwd);
|
|
28
|
+
const allDeps = pkgJson.allDeps();
|
|
29
|
+
|
|
30
|
+
const targets = args.length
|
|
31
|
+
? args.reduce((acc, n) => { if (allDeps[n]) acc[n] = allDeps[n]; return acc; }, {})
|
|
32
|
+
: allDeps;
|
|
33
|
+
|
|
34
|
+
if (Object.keys(targets).length === 0) {
|
|
35
|
+
this.logger.info('No dependencies found to check.');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const spinner = new Spinner(`Checking ${Object.keys(targets).length} packages for updates...`).start();
|
|
40
|
+
const rows = [];
|
|
41
|
+
|
|
42
|
+
await Promise.all(
|
|
43
|
+
Object.entries(targets).map(async ([name, range]) => {
|
|
44
|
+
try {
|
|
45
|
+
const versions = await registry.getVersions(name);
|
|
46
|
+
const latest = await registry.getLatest(name);
|
|
47
|
+
const current = semver.maxSatisfying(versions, range);
|
|
48
|
+
const wanted = semver.maxSatisfying(versions, range);
|
|
49
|
+
const isOutdated = latest && current && semver.gt(latest, current);
|
|
50
|
+
|
|
51
|
+
rows.push({
|
|
52
|
+
Package: name,
|
|
53
|
+
Current: current || 'n/a',
|
|
54
|
+
Wanted: wanted || 'n/a',
|
|
55
|
+
Latest: latest || 'n/a',
|
|
56
|
+
Status: isOutdated
|
|
57
|
+
? this.logger.c.yellow('outdated')
|
|
58
|
+
: this.logger.c.green('up to date'),
|
|
59
|
+
});
|
|
60
|
+
} catch (err) {
|
|
61
|
+
rows.push({
|
|
62
|
+
Package: name,
|
|
63
|
+
Current: '?',
|
|
64
|
+
Wanted: '?',
|
|
65
|
+
Latest: '?',
|
|
66
|
+
Status: this.logger.c.red('error')
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
spinner.succeed('Done');
|
|
73
|
+
|
|
74
|
+
// Handle 'outdated' alias/mode
|
|
75
|
+
if (this.name === 'outdated' || flags.outdated) {
|
|
76
|
+
const outdated = rows.filter(r => r.Status.includes('outdated'));
|
|
77
|
+
if (!outdated.length) {
|
|
78
|
+
this.logger.success('All packages are up to date!');
|
|
79
|
+
return;
|
|
42
80
|
}
|
|
43
|
-
|
|
44
|
-
|
|
81
|
+
this.logger.table(outdated, ['Package', 'Current', 'Wanted', 'Latest', 'Status']);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Filter for packages that actually need updating
|
|
86
|
+
const outdatedPkgs = rows.filter(r => r.Status.includes('outdated'));
|
|
87
|
+
if (!outdatedPkgs.length) {
|
|
88
|
+
this.logger.success('All packages are up to date!');
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
this.logger.info(`Updating ${outdatedPkgs.length} package(s)...`);
|
|
45
93
|
|
|
46
|
-
|
|
94
|
+
// Use dynamic import/require to avoid circular dependency with InstallCommand
|
|
95
|
+
const InstallCommand = require('./install');
|
|
96
|
+
const installInstance = new InstallCommand();
|
|
47
97
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (!outdated.length) { logger.success('All packages are up to date!'); return; }
|
|
51
|
-
logger.table(outdated, ['Package', 'Current', 'Wanted', 'Latest', 'Status']);
|
|
52
|
-
return;
|
|
98
|
+
const pkgArgs = outdatedPkgs.map(p => `${p.Package}@${p.Latest}`);
|
|
99
|
+
await installInstance.run(pkgArgs, flags);
|
|
53
100
|
}
|
|
101
|
+
}
|
|
54
102
|
|
|
55
|
-
|
|
56
|
-
const outdatedPkgs = rows.filter(r => r.Status.includes('outdated'));
|
|
57
|
-
if (!outdatedPkgs.length) { logger.success('All packages are up to date!'); return; }
|
|
103
|
+
module.exports = UpdateCommand;
|
|
58
104
|
|
|
59
|
-
logger.info(`Updating ${outdatedPkgs.length} package(s)...`);
|
|
60
|
-
const installCmd = require('./install');
|
|
61
|
-
const pkgArgs = outdatedPkgs.map(p => `${p.Package}@${p.Latest}`);
|
|
62
|
-
await installCmd(pkgArgs, flags);
|
|
63
|
-
};
|