node-power-user 2.1.3 → 2.1.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/README.md CHANGED
@@ -106,6 +106,10 @@ npu outdated
106
106
  npu out --force # bypass Socket protection
107
107
  ```
108
108
 
109
+ When discrepancies are found between `package.json` and `node_modules`, the menu offers context-aware actions:
110
+ - **Sync** — when `node_modules` is *behind* `package.json`, installs packages to match what `package.json` declares.
111
+ - **Reconcile** — when `node_modules` is *ahead* of `package.json`, updates `package.json` to match installed versions.
112
+
109
113
  ### List Packages
110
114
  List all packages in your project.
111
115
  ```shell
@@ -137,6 +141,7 @@ npu wait <ms>
137
141
  ```
138
142
 
139
143
  ### Global flags
144
+ * `-C <dir>`, `--cwd <dir>`: Run as if invoked from `<dir>` (e.g. `npu -C /path/to/project out`)
140
145
  * `--debug`: Log the commands and flags before they are executed
141
146
 
142
147
  ## 🛠️ Development
@@ -1,6 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  const { hideBin } = require('yargs/helpers');
3
3
  const argv = require('yargs')(hideBin(process.argv)).parseSync();
4
+
5
+ // --cwd / -C: run as if invoked from a different directory
6
+ const targetDir = argv.cwd || argv.C;
7
+ if (targetDir) {
8
+ process.chdir(targetDir);
9
+ }
10
+
4
11
  const cli = new (require('../dist/cli.js'))();
5
12
  (async function() {
6
13
  'use strict';
@@ -1,6 +1,8 @@
1
1
  // Libraries
2
2
  const logger = new (require('../lib/logger'))('node-power-user');
3
3
  const socket = require('../lib/socket');
4
+ const jetpack = require('fs-jetpack');
5
+ const path = require('path');
4
6
 
5
7
  // Module
6
8
  module.exports = async function (options) {
@@ -22,6 +24,26 @@ module.exports = async function (options) {
22
24
  flags.push('--global');
23
25
  }
24
26
 
27
+ // When installing specific packages with a version/tag (e.g. @latest, @^2.0.0),
28
+ // remove existing node_modules copies first so npm actually re-fetches them
29
+ // instead of reporting "up to date" with a stale cached version.
30
+ if (packages.length > 0 && !flags.includes('--global')) {
31
+ for (const pkg of packages) {
32
+ // For scoped packages like @scope/name@version, skip the leading @
33
+ const searchFrom = pkg.startsWith('@') ? 1 : 0;
34
+ const versionIdx = pkg.indexOf('@', searchFrom);
35
+ if (versionIdx <= 0) {
36
+ continue;
37
+ }
38
+
39
+ const pkgName = pkg.substring(0, versionIdx);
40
+ const pkgDir = path.join(process.cwd(), 'node_modules', pkgName);
41
+ if (jetpack.exists(pkgDir)) {
42
+ jetpack.remove(pkgDir);
43
+ }
44
+ }
45
+ }
46
+
25
47
  const command = packages.length > 0
26
48
  ? `npm install ${packages.join(' ')} ${flags.join(' ')}`.trim()
27
49
  : `npm install ${flags.join(' ')}`.trim();
@@ -143,8 +143,19 @@ module.exports = async function (options) {
143
143
  logger.log(chalk.dim('Legend: ') + chalk.magenta('⚠ = major version (breaking changes)'));
144
144
  }
145
145
 
146
- // Get counts for menu (only show a tier if it offers upgrades beyond the tier below)
146
+ // Separate discrepancies into two categories:
147
+ // - "behind": node_modules has an OLDER version than package.json wants (needs npm install)
148
+ // - "ahead": node_modules has a NEWER version than package.json specifies (reconcile package.json)
147
149
  const discrepancies = [...allPackages.values()].filter(pkg => pkg.hasDiscrepancy);
150
+ const behindPackages = discrepancies.filter(pkg =>
151
+ pkg.installedVersion !== '?' && version.is(pkg.installedVersion, '<', pkg.packageVersion)
152
+ );
153
+ const aheadPackages = discrepancies.filter(pkg =>
154
+ pkg.installedVersion !== '?' && version.is(pkg.installedVersion, '>', pkg.packageVersion)
155
+ );
156
+ const unknownPackages = discrepancies.filter(pkg => pkg.installedVersion === '?');
157
+
158
+ // Get counts for menu (only show a tier if it offers upgrades beyond the tier below)
148
159
  const patchCount = Object.keys(patchUpgrades).length;
149
160
  const minorCount = Object.keys(minorUpgrades).length;
150
161
  const majorCount = Object.keys(latestUpgrades).length;
@@ -180,9 +191,19 @@ module.exports = async function (options) {
180
191
  if (!action) {
181
192
  const choices = [];
182
193
 
183
- if (discrepancies.length > 0) {
194
+ // "Sync" installs packages where node_modules is behind package.json
195
+ const syncCount = behindPackages.length + unknownPackages.length;
196
+ if (syncCount > 0) {
197
+ choices.push({
198
+ name: `Sync (${syncCount}) - install packages to match package.json`,
199
+ value: 'sync',
200
+ });
201
+ }
202
+
203
+ // "Reconcile" updates package.json where node_modules is ahead
204
+ if (aheadPackages.length > 0) {
184
205
  choices.push({
185
- name: `Reconcile (${discrepancies.length}) - sync package.json to installed versions`,
206
+ name: `Reconcile (${aheadPackages.length}) - sync package.json to installed versions`,
186
207
  value: 'reconcile',
187
208
  });
188
209
  }
@@ -220,12 +241,39 @@ module.exports = async function (options) {
220
241
  return { allPackages };
221
242
  }
222
243
 
223
- // Handle reconcile
244
+ // Handle sync — run npm install for packages where node_modules is behind package.json
245
+ if (action === 'sync') {
246
+ const toSync = [...behindPackages, ...unknownPackages];
247
+ const installCmd = `npm install ${toSync.map(pkg => `${pkg.name}@${pkg.packageVersion}`).join(' ')}`;
248
+ logger.log(logger.format.cyan(`\nRunning ${installCmd}...`));
249
+
250
+ try {
251
+ await socket.wrap(installCmd, { force: options.force });
252
+ } catch (e) {
253
+ if (e.reason === 'npm-failed') {
254
+ logger.log('');
255
+ logger.log('Fix the npm error above and retry.');
256
+ return { allPackages, updated: false };
257
+ }
258
+ throw e;
259
+ }
260
+
261
+ try {
262
+ await socket.audit({ force: options.force });
263
+ } catch (e) {
264
+ logger.error(`Audit warning: ${e.message}`);
265
+ }
266
+
267
+ logger.log(logger.format.green(`\nSynced ${toSync.length} package(s) to match package.json.`));
268
+ return { allPackages, synced: true };
269
+ }
270
+
271
+ // Handle reconcile — update package.json for packages where node_modules is ahead
224
272
  if (action === 'reconcile') {
225
- // Re-read in case it changed
226
273
  projectJson = jetpack.read(packageJsonPath, 'json');
227
274
 
228
- for (const pkg of discrepancies) {
275
+ const toReconcile = aheadPackages.length > 0 ? aheadPackages : discrepancies;
276
+ for (const pkg of toReconcile) {
229
277
  const depType = pkg.type === 'dev' ? 'devDependencies'
230
278
  : pkg.type === 'peer' ? 'peerDependencies'
231
279
  : 'dependencies';
@@ -234,7 +282,7 @@ module.exports = async function (options) {
234
282
  }
235
283
 
236
284
  jetpack.write(packageJsonPath, projectJson);
237
- logger.log(logger.format.green(`\nReconciled ${discrepancies.length} package(s) in package.json.`));
285
+ logger.log(logger.format.green(`\nReconciled ${toReconcile.length} package(s) in package.json.`));
238
286
 
239
287
  return { allPackages, reconciled: true };
240
288
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-power-user",
3
- "version": "2.1.3",
3
+ "version": "2.1.4",
4
4
  "description": "Easy tools for every Node.js developer!",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {