@illumine/npm-diff 0.0.1 → 0.2.0

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 ADDED
@@ -0,0 +1,26 @@
1
+ # @illumine/npm-diff
2
+
3
+ Diff two versions of an npm package in VS Code, unminified and formatted.
4
+
5
+ ## CLI
6
+
7
+ ```sh
8
+ npx @illumine/npm-diff react 18.2.0 18.3.0
9
+ ```
10
+
11
+ Omit the versions to pick them interactively. Opens VS Code with the
12
+ [compare-folders](https://marketplace.visualstudio.com/items?itemName=moshfeu.compare-folders)
13
+ extension, so a `code` CLI on your `PATH` is required.
14
+
15
+ ## Options
16
+
17
+ | Option | Description | Default |
18
+ | ----------------- | ---------------------------------------------- | ----------------------------- |
19
+ | `-r, --registry` | npm registry to query. | `https://registry.npmjs.org` |
20
+ | `-w, --workspace` | Directory to place the two versions. | a temp dir, removed on exit |
21
+ | `-u, --unminify` | Unminify sources before diffing. | off |
22
+ | `-p, --pattern` | Compare only files matching this glob (repeat for more). | all files |
23
+
24
+ ## License
25
+
26
+ MIT
package/lib/index.cjs CHANGED
@@ -5,15 +5,17 @@ var extraTypings = require('@commander-js/extra-typings');
5
5
  var zx = require('zx');
6
6
  var path = require('path');
7
7
  var semver = require('semver');
8
- var tar = require('tar');
9
8
  var prompts = require('@inquirer/prompts');
10
9
  var promises = require('fs/promises');
11
10
  var unminify = require('@illumine/unminify');
12
11
  var oxfmt = require('oxfmt');
12
+ var picomatch = require('picomatch');
13
+ var tar = require('tar');
13
14
 
14
15
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
15
16
 
16
17
  var semver__default = /*#__PURE__*/_interopDefault(semver);
18
+ var picomatch__default = /*#__PURE__*/_interopDefault(picomatch);
17
19
 
18
20
  // src/error.ts
19
21
  var CliError = class extends Error {
@@ -69,13 +71,34 @@ async function formatTree(dir) {
69
71
  try {
70
72
  const { code: formatted } = await oxfmt.format(path.basename(file), code, {
71
73
  tabWidth: 2,
72
- printWidth: 120
74
+ printWidth: 100
73
75
  });
74
76
  await promises.writeFile(file, formatted);
75
77
  } catch {
76
78
  }
77
79
  }
78
80
  }
81
+ async function unpack(tarball, dir, patterns) {
82
+ const isMatch = compilePattern(patterns);
83
+ const filter = isMatch ? (path) => {
84
+ const rel = path.split("/").slice(1).join("/");
85
+ return rel !== "" && isMatch(rel);
86
+ } : void 0;
87
+ await tar.extract({ file: tarball, cwd: dir, strip: 1, filter });
88
+ await promises.rm(tarball);
89
+ }
90
+ function compilePattern(patterns) {
91
+ if (patterns.length === 0) return void 0;
92
+ const include = [];
93
+ const ignore = [];
94
+ for (const pattern of patterns) {
95
+ const negated = pattern.startsWith("!");
96
+ const base = (negated ? pattern.slice(1) : pattern).replace(/\/+$/, "");
97
+ const target = negated ? ignore : include;
98
+ target.push(base, `${base}/**`);
99
+ }
100
+ return picomatch__default.default(include.length ? include : "**", { dot: true, ignore });
101
+ }
79
102
  var EXTENSION_ID = "moshfeu.compare-folders";
80
103
  async function ensureVscode() {
81
104
  const code = await zx.which("code", { nothrow: true });
@@ -105,32 +128,41 @@ async function launchDiff(left, right) {
105
128
 
106
129
  // src/npm-diff.ts
107
130
  var NpmDiff = class {
108
- constructor(pkg, registry = DEFAULT_REGISTRY) {
131
+ constructor(pkg, options = {}) {
109
132
  this.pkg = pkg;
110
- this.registry = registry;
133
+ this.registry = options.registry ?? DEFAULT_REGISTRY;
134
+ this.workdir = options.workspace ? path.resolve(options.workspace) : zx.tmpdir("npmdiff-");
135
+ this.clean = options.workspace === void 0;
136
+ this.unminify = options.unminify ?? false;
137
+ this.patterns = options.pattern ?? [];
111
138
  }
112
139
  pkg;
113
- registry;
114
140
  packument;
115
- workdir = zx.tmpdir("npmdiff-");
141
+ registry;
142
+ workdir;
143
+ clean;
144
+ unminify;
145
+ patterns;
116
146
  async run(specA, specB) {
117
147
  await ensureVscode();
118
148
  this.packument = await zx.spinner(
119
149
  `Fetching '${this.pkg}' from npm\u2026`,
120
150
  () => fetchPackument(this.pkg, this.registry)
121
151
  );
122
- const [left, right] = specA && specB ? sortVersions(this.resolve(specA), this.resolve(specB)) : await this.pickVersions(specA);
123
- if (left === right) {
124
- throw new CliError(`Both specs resolve to ${left} \u2014 there is nothing to diff.`);
152
+ const [versionA, versionB] = specA && specB ? sortVersions(this.resolve(specA), this.resolve(specB)) : await this.pickVersions(specA);
153
+ if (versionA === versionB) {
154
+ throw new CliError(`Both specs resolve to ${versionA} \u2014 there is nothing to diff.`);
125
155
  }
126
156
  try {
127
- await this.prepare(left);
128
- await this.prepare(right);
129
- console.log(zx.chalk.green(`Opening VS Code to diff ${this.pkg} ${left} \u2194 ${right}`));
130
- await launchDiff(path.join(this.workdir, left), path.join(this.workdir, right));
131
- console.log(zx.chalk.dim("VS Code closed \u2014 cleaning up temporary files."));
157
+ const left = await this.prepare(versionA);
158
+ const right = await this.prepare(versionB);
159
+ console.log(zx.chalk.green(`Opening VS Code to diff ${this.pkg} ${versionA} \u2194 ${versionB}`));
160
+ await launchDiff(left, right);
161
+ console.log(
162
+ this.clean ? zx.chalk.dim("VS Code closed \u2014 cleaning up temporary files.") : zx.chalk.dim(`VS Code closed \u2014 files kept in ${this.workdir}`)
163
+ );
132
164
  } finally {
133
- await zx.fs.remove(this.workdir);
165
+ if (this.clean) await zx.fs.remove(this.workdir);
134
166
  }
135
167
  }
136
168
  /** Resolve a version or dist-tag into a concrete published version. */
@@ -160,27 +192,30 @@ var NpmDiff = class {
160
192
  const dir = path.join(this.workdir, version);
161
193
  await zx.fs.mkdirp(dir);
162
194
  const tarball = await zx.spinner(`Downloading ${label}`, () => this.download(version, dir));
163
- await zx.spinner(`Unpacking ${label}`, () => this.unpack(tarball, dir));
164
- await zx.spinner(`Unminifying ${label}`, () => unminifyTree(dir));
195
+ await zx.spinner(`Unpacking ${label}`, () => unpack(tarball, dir, this.patterns));
196
+ if (this.unminify) await zx.spinner(`Unminifying ${label}`, () => unminifyTree(dir));
165
197
  await zx.spinner(`Formatting ${label}`, () => formatTree(dir));
198
+ return dir;
166
199
  }
167
200
  async download(version, dir) {
168
201
  const { stdout } = await zx.$`npm pack ${this.pkg}@${version} --registry ${this.registry} --pack-destination ${dir} --json`;
169
202
  const [{ filename }] = JSON.parse(stdout);
170
203
  return path.join(dir, filename);
171
204
  }
172
- async unpack(tarball, dir) {
173
- await tar.extract({ file: tarball, cwd: dir, strip: 1 });
174
- await zx.fs.remove(tarball);
175
- }
176
205
  };
177
206
 
178
207
  // src/index.ts
179
208
  zx.$.verbose = false;
180
209
  var program = new extraTypings.Command();
181
- program.name("npmdiff").description("Diff two versions of an npm package in VS Code, unminified and formatted").argument("<pkgname>", "npm package name (scoped names supported)").argument("[spec-a]", "a version or dist-tag").argument("[spec-b]", "a version or dist-tag").option("--registry <url>", "npm registry to query", DEFAULT_REGISTRY).action(
182
- (pkgname, specA, specB, options) => new NpmDiff(pkgname, options.registry).run(specA, specB)
183
- );
210
+ program.name("npmdiff").description("Diff two versions of an npm package in VS Code, unminified and formatted").argument("<pkgname>", "npm package name (scoped names supported)").argument("[spec-a]", "a version or dist-tag").argument("[spec-b]", "a version or dist-tag").option("-r, --registry <url>", "npm registry to query", DEFAULT_REGISTRY).option(
211
+ "-w, --workspace <dir>",
212
+ "directory to place the two versions; kept instead of auto-cleaned when set"
213
+ ).option("-u, --unminify", "unminify sources before diffing", false).option(
214
+ "-p, --pattern <glob>",
215
+ "compare only files matching this glob; repeat for more",
216
+ (value, previous) => [...previous, value],
217
+ []
218
+ ).action((pkgname, specA, specB, options) => new NpmDiff(pkgname, options).run(specA, specB));
184
219
  program.parseAsync().catch((error) => {
185
220
  if (error instanceof CliError) {
186
221
  console.error(zx.chalk.red(error.message));
package/lib/index.js CHANGED
@@ -1,13 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from '@commander-js/extra-typings';
3
3
  import { $, chalk, tmpdir, spinner, fs, which, glob } from 'zx';
4
- import { join, basename } from 'path';
4
+ import { resolve, join, basename } from 'path';
5
5
  import semver from 'semver';
6
- import { extract } from 'tar';
7
6
  import { confirm, search } from '@inquirer/prompts';
8
- import { readFile, writeFile } from 'fs/promises';
7
+ import { rm, readFile, writeFile } from 'fs/promises';
9
8
  import { unminify } from '@illumine/unminify';
10
9
  import { format } from 'oxfmt';
10
+ import picomatch from 'picomatch';
11
+ import { extract } from 'tar';
11
12
 
12
13
  // src/error.ts
13
14
  var CliError = class extends Error {
@@ -63,13 +64,34 @@ async function formatTree(dir) {
63
64
  try {
64
65
  const { code: formatted } = await format(basename(file), code, {
65
66
  tabWidth: 2,
66
- printWidth: 120
67
+ printWidth: 100
67
68
  });
68
69
  await writeFile(file, formatted);
69
70
  } catch {
70
71
  }
71
72
  }
72
73
  }
74
+ async function unpack(tarball, dir, patterns) {
75
+ const isMatch = compilePattern(patterns);
76
+ const filter = isMatch ? (path) => {
77
+ const rel = path.split("/").slice(1).join("/");
78
+ return rel !== "" && isMatch(rel);
79
+ } : void 0;
80
+ await extract({ file: tarball, cwd: dir, strip: 1, filter });
81
+ await rm(tarball);
82
+ }
83
+ function compilePattern(patterns) {
84
+ if (patterns.length === 0) return void 0;
85
+ const include = [];
86
+ const ignore = [];
87
+ for (const pattern of patterns) {
88
+ const negated = pattern.startsWith("!");
89
+ const base = (negated ? pattern.slice(1) : pattern).replace(/\/+$/, "");
90
+ const target = negated ? ignore : include;
91
+ target.push(base, `${base}/**`);
92
+ }
93
+ return picomatch(include.length ? include : "**", { dot: true, ignore });
94
+ }
73
95
  var EXTENSION_ID = "moshfeu.compare-folders";
74
96
  async function ensureVscode() {
75
97
  const code = await which("code", { nothrow: true });
@@ -99,32 +121,41 @@ async function launchDiff(left, right) {
99
121
 
100
122
  // src/npm-diff.ts
101
123
  var NpmDiff = class {
102
- constructor(pkg, registry = DEFAULT_REGISTRY) {
124
+ constructor(pkg, options = {}) {
103
125
  this.pkg = pkg;
104
- this.registry = registry;
126
+ this.registry = options.registry ?? DEFAULT_REGISTRY;
127
+ this.workdir = options.workspace ? resolve(options.workspace) : tmpdir("npmdiff-");
128
+ this.clean = options.workspace === void 0;
129
+ this.unminify = options.unminify ?? false;
130
+ this.patterns = options.pattern ?? [];
105
131
  }
106
132
  pkg;
107
- registry;
108
133
  packument;
109
- workdir = tmpdir("npmdiff-");
134
+ registry;
135
+ workdir;
136
+ clean;
137
+ unminify;
138
+ patterns;
110
139
  async run(specA, specB) {
111
140
  await ensureVscode();
112
141
  this.packument = await spinner(
113
142
  `Fetching '${this.pkg}' from npm\u2026`,
114
143
  () => fetchPackument(this.pkg, this.registry)
115
144
  );
116
- const [left, right] = specA && specB ? sortVersions(this.resolve(specA), this.resolve(specB)) : await this.pickVersions(specA);
117
- if (left === right) {
118
- throw new CliError(`Both specs resolve to ${left} \u2014 there is nothing to diff.`);
145
+ const [versionA, versionB] = specA && specB ? sortVersions(this.resolve(specA), this.resolve(specB)) : await this.pickVersions(specA);
146
+ if (versionA === versionB) {
147
+ throw new CliError(`Both specs resolve to ${versionA} \u2014 there is nothing to diff.`);
119
148
  }
120
149
  try {
121
- await this.prepare(left);
122
- await this.prepare(right);
123
- console.log(chalk.green(`Opening VS Code to diff ${this.pkg} ${left} \u2194 ${right}`));
124
- await launchDiff(join(this.workdir, left), join(this.workdir, right));
125
- console.log(chalk.dim("VS Code closed \u2014 cleaning up temporary files."));
150
+ const left = await this.prepare(versionA);
151
+ const right = await this.prepare(versionB);
152
+ console.log(chalk.green(`Opening VS Code to diff ${this.pkg} ${versionA} \u2194 ${versionB}`));
153
+ await launchDiff(left, right);
154
+ console.log(
155
+ this.clean ? chalk.dim("VS Code closed \u2014 cleaning up temporary files.") : chalk.dim(`VS Code closed \u2014 files kept in ${this.workdir}`)
156
+ );
126
157
  } finally {
127
- await fs.remove(this.workdir);
158
+ if (this.clean) await fs.remove(this.workdir);
128
159
  }
129
160
  }
130
161
  /** Resolve a version or dist-tag into a concrete published version. */
@@ -154,27 +185,30 @@ var NpmDiff = class {
154
185
  const dir = join(this.workdir, version);
155
186
  await fs.mkdirp(dir);
156
187
  const tarball = await spinner(`Downloading ${label}`, () => this.download(version, dir));
157
- await spinner(`Unpacking ${label}`, () => this.unpack(tarball, dir));
158
- await spinner(`Unminifying ${label}`, () => unminifyTree(dir));
188
+ await spinner(`Unpacking ${label}`, () => unpack(tarball, dir, this.patterns));
189
+ if (this.unminify) await spinner(`Unminifying ${label}`, () => unminifyTree(dir));
159
190
  await spinner(`Formatting ${label}`, () => formatTree(dir));
191
+ return dir;
160
192
  }
161
193
  async download(version, dir) {
162
194
  const { stdout } = await $`npm pack ${this.pkg}@${version} --registry ${this.registry} --pack-destination ${dir} --json`;
163
195
  const [{ filename }] = JSON.parse(stdout);
164
196
  return join(dir, filename);
165
197
  }
166
- async unpack(tarball, dir) {
167
- await extract({ file: tarball, cwd: dir, strip: 1 });
168
- await fs.remove(tarball);
169
- }
170
198
  };
171
199
 
172
200
  // src/index.ts
173
201
  $.verbose = false;
174
202
  var program = new Command();
175
- program.name("npmdiff").description("Diff two versions of an npm package in VS Code, unminified and formatted").argument("<pkgname>", "npm package name (scoped names supported)").argument("[spec-a]", "a version or dist-tag").argument("[spec-b]", "a version or dist-tag").option("--registry <url>", "npm registry to query", DEFAULT_REGISTRY).action(
176
- (pkgname, specA, specB, options) => new NpmDiff(pkgname, options.registry).run(specA, specB)
177
- );
203
+ program.name("npmdiff").description("Diff two versions of an npm package in VS Code, unminified and formatted").argument("<pkgname>", "npm package name (scoped names supported)").argument("[spec-a]", "a version or dist-tag").argument("[spec-b]", "a version or dist-tag").option("-r, --registry <url>", "npm registry to query", DEFAULT_REGISTRY).option(
204
+ "-w, --workspace <dir>",
205
+ "directory to place the two versions; kept instead of auto-cleaned when set"
206
+ ).option("-u, --unminify", "unminify sources before diffing", false).option(
207
+ "-p, --pattern <glob>",
208
+ "compare only files matching this glob; repeat for more",
209
+ (value, previous) => [...previous, value],
210
+ []
211
+ ).action((pkgname, specA, specB, options) => new NpmDiff(pkgname, options).run(specA, specB));
178
212
  program.parseAsync().catch((error) => {
179
213
  if (error instanceof CliError) {
180
214
  console.error(chalk.red(error.message));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@illumine/npm-diff",
3
- "version": "0.0.1",
3
+ "version": "0.2.0",
4
4
  "description": "Diff two versions of an npm package in VS Code, unminified and formatted",
5
5
  "type": "module",
6
6
  "bin": {
@@ -36,6 +36,7 @@
36
36
  "@commander-js/extra-typings": "^15.0.0",
37
37
  "commander": "^15.0.0",
38
38
  "oxfmt": "~0.53.0",
39
+ "picomatch": "^4.0.5",
39
40
  "semver": "^7.8.5",
40
41
  "tar": "^7.5.19",
41
42
  "zx": "^8.8.5",