node-power-user 2.0.1 → 2.1.1

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/README.md CHANGED
@@ -73,10 +73,37 @@ List all global packages for all versions of Node.js on your machine (you **must
73
73
  npu global
74
74
  ```
75
75
 
76
+ ### Install Packages
77
+ Install packages with supply chain protection via [Socket](https://socket.dev/). Every install is wrapped with Socket to detect malicious or compromised packages — including transitive dependencies — before they're added to your project. After install, a full `socket npm audit` runs against your entire dependency tree.
78
+
79
+ ```shell
80
+ npu install
81
+ npu i <package>
82
+ npu i <package> --save-dev
83
+ npu i <package> --save-exact
84
+ ```
85
+
86
+ **If Socket CLI is not installed, `npu install` will refuse to run.** Install it globally to enable protection:
87
+ ```shell
88
+ npm install -g @socketsecurity/cli --save-exact
89
+ ```
90
+
91
+ Use `--force` to bypass Socket protection (not recommended):
92
+ ```shell
93
+ npu i <package> --force
94
+ ```
95
+
96
+ ### Audit
97
+ Run a Socket supply chain audit on your current dependency tree.
98
+ ```shell
99
+ npu audit
100
+ ```
101
+
76
102
  ### Outdated Packages
77
- Compare the versions of installed modules to those in your package.json
103
+ Compare the versions of installed modules to those in your package.json. When you choose to update, the install step and a full post-install audit are both wrapped with Socket for supply chain protection.
78
104
  ```shell
79
105
  npu outdated
106
+ npu out --force # bypass Socket protection
80
107
  ```
81
108
 
82
109
  ### List Packages
@@ -112,6 +139,19 @@ npu wait <ms>
112
139
  ### Global flags
113
140
  * `--debug`: Log the commands and flags before they are executed
114
141
 
142
+ ## 🛠️ Development
143
+ To test commands locally while developing:
144
+ ```shell
145
+ npm start -- <command> [options]
146
+ ```
147
+
148
+ For example:
149
+ ```shell
150
+ npm start -- outdated
151
+ npm start -- bump patch
152
+ npm start -- -v
153
+ ```
154
+
115
155
  ## 🗨️ Final Words
116
156
  If you are still having difficulty, we would love for you to post a question to [the Node Power User issues page](https://github.com/itw-creative-works/node-power-user/issues). It is much easier to answer questions that include your code and relevant files! So if you can provide them, we'd be extremely grateful (and more likely to help you find the answer!)
117
157
 
package/dist/cli.js CHANGED
@@ -8,6 +8,8 @@ const ALIASES = {
8
8
  bump: ['-b', '--bump'],
9
9
  clean: ['-c', '--clean'],
10
10
  global: ['-g', '--global'],
11
+ audit: ['--audit'],
12
+ install: ['-i', '--install', 'i'],
11
13
  open: ['--open', 'repo', '--repo'],
12
14
  outdated: ['-o', 'out', '--outdated', '-u', '--update', 'up', 'update'],
13
15
  packages: ['-p', 'pack', '--packages'],
@@ -64,6 +66,12 @@ Main.prototype.process = async function (options) {
64
66
  const Command = require(commandFile);
65
67
  return await Command(options);
66
68
  } catch (e) {
69
+ // Exit cleanly on Ctrl+C
70
+ if (e.name === 'ExitPromptError' || e.message?.includes('SIGINT')) {
71
+ console.log('\nExited.');
72
+ process.exit(0);
73
+ }
74
+
67
75
  console.error(`Error executing command "${command}": ${e.message}`);
68
76
 
69
77
  // Exit with error
@@ -0,0 +1,18 @@
1
+ // Libraries
2
+ const logger = new (require('../lib/logger'))('node-power-user');
3
+ const socket = require('../lib/socket');
4
+
5
+ // Module
6
+ module.exports = async function (options) {
7
+ // Check socket status upfront (blocks if not installed unless --force)
8
+ await socket.check({ force: options.force });
9
+
10
+ // Run audit
11
+ logger.log('Running Socket audit on current dependency tree...');
12
+
13
+ try {
14
+ await socket.audit({ force: options.force });
15
+ } catch (e) {
16
+ logger.error(e.message);
17
+ }
18
+ };
@@ -0,0 +1,62 @@
1
+ // Libraries
2
+ const logger = new (require('../lib/logger'))('node-power-user');
3
+ const socket = require('../lib/socket');
4
+
5
+ // Module
6
+ module.exports = async function (options) {
7
+ // Get packages from positional args (everything after "install" / "i")
8
+ const packages = (options._ || []).slice(1);
9
+
10
+ // Build the npm command
11
+ const flags = [];
12
+
13
+ if (options.D || options['save-dev']) {
14
+ flags.push('--save-dev');
15
+ }
16
+
17
+ if (options.E || options['save-exact']) {
18
+ flags.push('--save-exact');
19
+ }
20
+
21
+ if (options.g || options.global) {
22
+ flags.push('--global');
23
+ }
24
+
25
+ const command = packages.length > 0
26
+ ? `npm install ${packages.join(' ')} ${flags.join(' ')}`.trim()
27
+ : `npm install ${flags.join(' ')}`.trim();
28
+
29
+ // Check socket status upfront (blocks if not installed unless --force)
30
+ await socket.check({ force: options.force });
31
+
32
+ // Log
33
+ logger.log(`Running: ${logger.format.cyan(command)}`);
34
+
35
+ // Wrap with socket
36
+ try {
37
+ await socket.wrap(command, { force: options.force });
38
+ } catch (e) {
39
+ const flaggedPackages = e.flaggedPackages || [];
40
+
41
+ if (flaggedPackages.length > 0) {
42
+ logger.log('');
43
+ logger.error('Socket flagged the following dependencies:');
44
+ flaggedPackages.forEach(pkg => logger.error(` • ${pkg}`));
45
+ }
46
+
47
+ logger.log('');
48
+ logger.log('To retry with Socket protection bypassed:');
49
+ logger.log(logger.format.cyan(` npu i ${packages.join(' ')} ${flags.join(' ')} --force`.trim()));
50
+ return;
51
+ }
52
+
53
+ // Run full audit after install
54
+ try {
55
+ await socket.audit({ force: options.force });
56
+ } catch (e) {
57
+ logger.error(`Audit warning: ${e.message}`);
58
+ }
59
+
60
+ // Done
61
+ logger.log(logger.format.green('Install complete.'));
62
+ };
@@ -7,6 +7,7 @@ const path = require('path');
7
7
  const version = require('wonderful-version');
8
8
  const inquirer = require('@inquirer/prompts');
9
9
  const ncu = require('npm-check-updates');
10
+ const socket = require('../lib/socket');
10
11
  const { execute } = require('node-powertools');
11
12
 
12
13
  // Load package.json
@@ -23,13 +24,31 @@ module.exports = async function (options) {
23
24
  return {};
24
25
  }
25
26
 
27
+ // Check socket status upfront (blocks if not installed unless --force)
28
+ await socket.check({ force: options.force });
29
+
30
+ if (options.force) {
31
+ logger.warn(chalk.red('--force flag detected — Socket supply chain protection is bypassed!'));
32
+ }
33
+
26
34
  // Log start
27
35
  logger.log(`Checking packages for ${logger.format.bold(projectJson.name || 'Unknown Project')}...`);
28
36
 
37
+ // Parse --ignore flag (comma-separated list of package names to skip)
38
+ const ignoreList = (options.ignore || '')
39
+ .split(',')
40
+ .map(s => s.trim())
41
+ .filter(Boolean);
42
+
43
+ if (ignoreList.length > 0) {
44
+ logger.log(chalk.dim(`Ignoring: ${ignoreList.join(', ')}`));
45
+ }
46
+
29
47
  // Run ncu for patch, minor, and latest (include peer dependencies)
30
48
  const ncuOptions = {
31
49
  packageFile: packageJsonPath,
32
50
  dep: 'prod,dev,peer,optional',
51
+ ...(ignoreList.length > 0 ? { reject: ignoreList } : {}),
33
52
  };
34
53
 
35
54
  const [patchUpgrades, minorUpgrades, latestUpgrades] = await Promise.all([
@@ -49,7 +68,7 @@ module.exports = async function (options) {
49
68
  // Build unified package data
50
69
  const allPackages = new Map();
51
70
 
52
- for (const dep of Object.keys(allDependencies)) {
71
+ for (const dep of Object.keys(allDependencies).filter(d => !ignoreList.includes(d))) {
53
72
  const packageVersion = version.clean(allDependencies[dep]);
54
73
  const installedPackagePath = path.join(projectPath, 'node_modules', dep, 'package.json');
55
74
 
@@ -225,6 +244,9 @@ module.exports = async function (options) {
225
244
  : action === 'minor' ? minorUpgrades
226
245
  : latestUpgrades;
227
246
 
247
+ // Back up package.json before modifying
248
+ const packageJsonBackup = jetpack.read(packageJsonPath);
249
+
228
250
  await ncu.run({
229
251
  packageFile: packageJsonPath,
230
252
  target: action,
@@ -233,9 +255,80 @@ module.exports = async function (options) {
233
255
 
234
256
  logger.log(logger.format.green(`\nUpdated ${Object.keys(upgrades).length} package(s) in package.json.`));
235
257
 
236
- // Run npm install
237
- logger.log(logger.format.cyan('\nRunning npm install...'));
238
- await execute('npm install', { log: true });
258
+ // Install the specific upgraded packages so npm actually pulls them in
259
+ // (plain `npm install` won't upgrade packages that already satisfy the range)
260
+ const packageNames = Object.keys(upgrades);
261
+ const installCmd = `npm install ${packageNames.map(name => `${name}@${version.clean(upgrades[name])}`).join(' ')}`;
262
+ logger.log(logger.format.cyan(`\nRunning ${installCmd}...`));
263
+
264
+ try {
265
+ await socket.wrap(installCmd, { force: options.force });
266
+ } catch (e) {
267
+ const flaggedPackages = e.flaggedPackages || [];
268
+
269
+ // Restore package.json since the bulk install failed
270
+ jetpack.write(packageJsonPath, packageJsonBackup);
271
+ logger.log('package.json has been restored to its original state.');
272
+
273
+ // Trace which of the requested packages bring in the flagged deps
274
+ const riskyParents = new Set();
275
+
276
+ if (flaggedPackages.length > 0) {
277
+ logger.log('');
278
+ logger.error('Socket flagged the following transitive dependencies:');
279
+
280
+ for (const flagged of flaggedPackages) {
281
+ const flaggedName = flagged.replace(/@[^@]+$/, ''); // strip version
282
+ let parentChain = '';
283
+
284
+ try {
285
+ const lsOutput = await execute(`npm ls ${flaggedName} --json`, { log: false });
286
+ const tree = JSON.parse(lsOutput);
287
+
288
+ // Find which top-level deps depend on the flagged package
289
+ const parents = [];
290
+ for (const [dep, info] of Object.entries(tree.dependencies || {})) {
291
+ if (dep === flaggedName || JSON.stringify(info).includes(`"${flaggedName}"`)) {
292
+ parents.push(dep);
293
+ riskyParents.add(dep);
294
+ }
295
+ }
296
+
297
+ if (parents.length > 0) {
298
+ parentChain = chalk.dim(` (from ${parents.join(', ')})`);
299
+ }
300
+ } catch (_) {
301
+ // npm ls may fail, just skip the trace
302
+ }
303
+
304
+ logger.error(` • ${flagged}${parentChain}`);
305
+ }
306
+ }
307
+
308
+ // Suggest retry commands
309
+ if (riskyParents.size > 0) {
310
+ const ignoreArg = [...riskyParents].join(',');
311
+ logger.log('');
312
+ logger.log('To skip the risky packages and update the rest:');
313
+ logger.log(logger.format.cyan(` npu out --ignore ${ignoreArg}`));
314
+ }
315
+
316
+ logger.log('');
317
+ logger.log('To retry with Socket protection bypassed:');
318
+ logger.log(logger.format.cyan(` npu out --force`));
319
+ logger.log('');
320
+ logger.log('To bypass Socket for this install only:');
321
+ logger.log(logger.format.cyan(` SOCKET_CLI_ACCEPT_RISKS=1 npm install ${packageNames.map(name => `${name}@${version.clean(upgrades[name])}`).join(' ')}`));
322
+
323
+ return { allPackages, updated: false, target: action };
324
+ }
325
+
326
+ // Run full audit after install
327
+ try {
328
+ await socket.audit({ force: options.force });
329
+ } catch (e) {
330
+ logger.error(`Audit warning: ${e.message}`);
331
+ }
239
332
 
240
333
  return { allPackages, updated: true, target: action };
241
334
  };
@@ -0,0 +1,159 @@
1
+ // Libraries
2
+ const { execute } = require('node-powertools');
3
+ const logger = new (require('./logger'))('node-power-user');
4
+
5
+ // Check if socket CLI is installed
6
+ let _socketVersion = undefined;
7
+ async function isSocketAvailable() {
8
+ if (_socketVersion !== undefined) {
9
+ return !!_socketVersion;
10
+ }
11
+
12
+ try {
13
+ _socketVersion = (await execute('socket --version', { log: false })).trim();
14
+ } catch (e) {
15
+ _socketVersion = null;
16
+ }
17
+
18
+ return !!_socketVersion;
19
+ }
20
+
21
+ // Log socket status upfront (call at the start of commands)
22
+ async function check(options) {
23
+ options = options || {};
24
+
25
+ const available = await isSocketAvailable();
26
+
27
+ if (available) {
28
+ logger.log(logger.format.green(`Socket v${_socketVersion} — supply chain protection enabled.`));
29
+ } else {
30
+ logger.error('Socket CLI is not installed. Your installs are NOT protected against supply chain attacks.');
31
+ logger.error(`Install it with: ${logger.format.cyan('npm install -g @socketsecurity/cli --save-exact')}`);
32
+
33
+ if (!options.force) {
34
+ logger.error('Refusing to proceed without Socket protection. Use --force to bypass.');
35
+ throw new Error('Socket CLI is not installed.');
36
+ }
37
+
38
+ logger.warn('Bypassing Socket protection (--force flag detected).');
39
+ }
40
+
41
+ return available;
42
+ }
43
+
44
+ // Wrap an npm command with socket if available
45
+ async function wrap(command, options) {
46
+ options = options || {};
47
+
48
+ const available = await isSocketAvailable();
49
+
50
+ // If socket is not installed, fall back to plain npm
51
+ if (!available) {
52
+ await execute(command, { log: true });
53
+ return;
54
+ }
55
+
56
+ // Run with socket and capture output to check for risks
57
+ // When --force is used, set SOCKET_CLI_ACCEPT_RISKS so Socket actually installs
58
+ const env = options.force ? 'SOCKET_CLI_ACCEPT_RISKS=1 ' : '';
59
+ let output;
60
+ let exitedWithError = false;
61
+
62
+ try {
63
+ output = await execute(`${env}socket ${command}`, { log: false });
64
+ } catch (e) {
65
+ // Socket exits non-zero when it detects risks
66
+ output = e.message || '';
67
+ exitedWithError = true;
68
+ }
69
+
70
+ // Print the output
71
+ if (output) {
72
+ console.log(output);
73
+ }
74
+
75
+ // Check for risk warnings in output
76
+ const hasRisks = exitedWithError
77
+ || (/new risk|warning|alert|socket found|exiting due to risks/i.test(output)
78
+ && !/no new risks/i.test(output));
79
+
80
+ if (!hasRisks) {
81
+ return;
82
+ }
83
+
84
+ // Parse flagged package names from Socket output (e.g. "serialize-javascript@6.0.2")
85
+ const flaggedPackages = [];
86
+ const flaggedRegex = /^(\S+@\S+)\s/gm;
87
+ let match;
88
+ while ((match = flaggedRegex.exec(output)) !== null) {
89
+ flaggedPackages.push(match[1]);
90
+ }
91
+
92
+ // Risks detected — block unless --force flag is passed
93
+ logger.error('Socket detected supply chain risks in the packages above.');
94
+
95
+ if (!options.force) {
96
+ logger.error('Refusing to install. Review the risks above, then use --force to bypass.');
97
+ const err = new Error('Socket detected supply chain risks.');
98
+ err.flaggedPackages = flaggedPackages;
99
+ throw err;
100
+ }
101
+
102
+ logger.warn('Bypassing Socket risk warnings (--force flag detected).');
103
+ }
104
+
105
+ // Run a full socket audit on the current project
106
+ async function audit(options) {
107
+ options = options || {};
108
+
109
+ const available = await isSocketAvailable();
110
+
111
+ if (!available) {
112
+ logger.warn('Skipping post-install audit — Socket CLI is not installed.');
113
+ return;
114
+ }
115
+
116
+ logger.log(logger.format.cyan('\nRunning post-install Socket audit...'));
117
+
118
+ let output;
119
+ let exitedWithError = false;
120
+
121
+ try {
122
+ output = await execute('socket npm audit', { log: false });
123
+ } catch (e) {
124
+ output = e.message || '';
125
+ exitedWithError = true;
126
+ }
127
+
128
+ // Print the output
129
+ if (output) {
130
+ console.log(output);
131
+ }
132
+
133
+ // Check for risk warnings in output
134
+ const hasRisks = exitedWithError
135
+ || (/new risk|warning|alert|socket found|exiting due to risks/i.test(output)
136
+ && !/no new risks/i.test(output));
137
+
138
+ if (!hasRisks) {
139
+ logger.log(logger.format.green('Socket audit passed — no risks detected.'));
140
+ return;
141
+ }
142
+
143
+ logger.error('Socket audit found supply chain risks in your dependencies.');
144
+
145
+ if (!options.force) {
146
+ logger.error('Review the risks above. Use --force to bypass.');
147
+ throw new Error('Socket audit detected supply chain risks.');
148
+ }
149
+
150
+ logger.warn('Bypassing Socket audit warnings (--force flag detected).');
151
+ }
152
+
153
+ // Export
154
+ module.exports = {
155
+ isAvailable: isSocketAvailable,
156
+ check,
157
+ wrap,
158
+ audit,
159
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-power-user",
3
- "version": "2.0.1",
3
+ "version": "2.1.1",
4
4
  "description": "Easy tools for every Node.js developer!",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -15,6 +15,15 @@
15
15
  "node-power-user": "bin/node-power-user",
16
16
  "nodepoweruser": "bin/node-power-user"
17
17
  },
18
+ "files": [
19
+ "dist"
20
+ ],
21
+ "preparePackage": {
22
+ "input": "./src",
23
+ "output": "./dist",
24
+ "replace": {},
25
+ "type": "copy"
26
+ },
18
27
  "repository": {
19
28
  "type": "git",
20
29
  "url": "git+https://github.com/itw-creative-works/node-power-user.git"
@@ -30,26 +39,21 @@
30
39
  "url": "https://github.com/itw-creative-works/node-power-user/issues"
31
40
  },
32
41
  "homepage": "https://itwcreativeworks.com",
33
- "preparePackage": {
34
- "input": "./src",
35
- "output": "./dist",
36
- "replace": {}
37
- },
38
42
  "dependencies": {
39
- "@inquirer/prompts": "^8.3.0",
43
+ "@inquirer/prompts": "^8.3.2",
40
44
  "chalk": "^5.6.2",
41
45
  "cli-progress": "^3.12.0",
42
46
  "fs-jetpack": "^5.1.0",
43
47
  "itwcw-package-analytics": "^1.0.8",
44
- "node-powertools": "^2.3.2",
48
+ "node-powertools": "^3.0.0",
45
49
  "npm-api": "^1.0.1",
46
- "npm-check-updates": "^19.5.0",
50
+ "npm-check-updates": "^20.0.0",
47
51
  "table": "^6.9.0",
48
52
  "wonderful-version": "^1.3.2",
49
53
  "yargs": "^18.0.0"
50
54
  },
51
55
  "devDependencies": {
52
- "mocha": "^8.4.0",
53
- "prepare-package": "^1.2.6"
56
+ "mocha": "^11.7.5",
57
+ "prepare-package": "^2.0.7"
54
58
  }
55
59
  }
package/CHANGELOG.md DELETED
@@ -1,33 +0,0 @@
1
- # CHANGELOG
2
-
3
- All notable changes to this project will be documented in this file.
4
-
5
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
6
-
7
- ## Changelog Categories
8
-
9
- - `BREAKING` for breaking changes.
10
- - `Added` for new features.
11
- - `Changed` for changes in existing functionality.
12
- - `Deprecated` for soon-to-be removed features.
13
- - `Removed` for now removed features.
14
- - `Fixed` for any bug fixes.
15
- - `Security` in case of vulnerabilities.
16
-
17
- ---
18
- ## [2.0.0] - 2026-02-24
19
- ### BREAKING
20
- - Upgraded yargs v16 to v18: updated CLI entry point to use `parseSync()` API
21
- - Upgraded chalk v4 to v5: requires Node 22+ for ESM `require()` support
22
- - Upgraded @inquirer/prompts v7 to v8
23
-
24
- ### Changed
25
- - Updated all `require('chalk')` to `require('chalk').default` for chalk v5 ESM compatibility
26
- - Improved outdated command menu: Minor/Major options only show when relevant updates exist
27
- - Made breaking changes legend conditional on actual major updates being present
28
- - Updated npm-check-updates to v19.5 and itwcw-package-analytics to v1.0.8
29
-
30
- ---
31
- ## [1.0.0] - 2024-06-19
32
- ### Added
33
- - Initial release of the project 🚀
package/TODO.md DELETED
@@ -1,7 +0,0 @@
1
- Use "keychain" for something?
2
-
3
- Interesting - the git status shows your branch is up to date now. However, the error you encountered was about needing to configure how to handle divergent branches in the future. The issue is that git needs to know your preference for handling situations where branches have diverged. You have three options:
4
- Merge (git config pull.rebase false) - Creates merge commits
5
- Rebase (git config pull.rebase true) - Replays your local commits on top of remote changes (cleaner history)
6
- Fast-forward only (git config pull.ff only) - Only pulls if it can fast-forward (safest, but fails if branches diverged)
7
- Which strategy would you prefer for handling divergent branches during pulls? I'd recommend rebase for a cleaner history, but I can set it to whichever you prefer. Would you like me to configure this globally for all your repositories or just for this one?