postman-cli-completion 1.71.0 → 1.74.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 +72 -8
- package/bin/postman-completion.mjs +36 -2
- package/completions/bash/postman.bash +1102 -1005
- package/completions/fish/postman.fish +1111 -1054
- package/completions/zsh/_postman +1676 -1571
- package/lib/postinstall.mjs +79 -0
- package/lib/version-check.mjs +261 -0
- package/package.json +3 -1
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Postinstall version-drift notice.
|
|
3
|
+
//
|
|
4
|
+
// Runs once, right after `npm i -g postman-cli-completion`, and tells the user
|
|
5
|
+
// if their installed Postman CLI does not match the version these completions
|
|
6
|
+
// were generated for. That moment is the useful one: the user is already doing
|
|
7
|
+
// package management, so an "upgrade this" message is actionable.
|
|
8
|
+
//
|
|
9
|
+
// Hard rules for this file, because postinstall scripts are bad neighbours:
|
|
10
|
+
//
|
|
11
|
+
// 1. NEVER fail the install. Every path exits 0, including unexpected throws.
|
|
12
|
+
// 2. NEVER touch the network. A registry lookup here is exactly the kind of
|
|
13
|
+
// postinstall behaviour people (rightly) disable; the offline comparison
|
|
14
|
+
// already catches the common case. `doctor` does the online check on
|
|
15
|
+
// demand.
|
|
16
|
+
// 3. Say nothing when there is nothing to say, and stay quiet in CI and other
|
|
17
|
+
// non-interactive installs.
|
|
18
|
+
//
|
|
19
|
+
// The online "is my CLI older than npm latest?" check lives in `doctor`.
|
|
20
|
+
|
|
21
|
+
import { resolve, dirname } from "node:path";
|
|
22
|
+
import { fileURLToPath } from "node:url";
|
|
23
|
+
import { checkVersions } from "./version-check.mjs";
|
|
24
|
+
|
|
25
|
+
const PKG_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Should we stay silent regardless of what the versions say?
|
|
29
|
+
*
|
|
30
|
+
* Suppressed for:
|
|
31
|
+
* - an explicit opt-out
|
|
32
|
+
* - CI (the notice is for humans; it would just be log noise)
|
|
33
|
+
* - a non-TTY stderr, which also covers piped/scripted installs
|
|
34
|
+
* - local (non-global) installs: this package is a global CLI helper, so a
|
|
35
|
+
* local install is usually a dependency graph artifact, not a user choice
|
|
36
|
+
*/
|
|
37
|
+
export function shouldSuppress(env = process.env, isTTY = process.stderr.isTTY) {
|
|
38
|
+
if (env.POSTMAN_COMPLETION_NO_VERSION_CHECK === "1") return true;
|
|
39
|
+
if (env.CI && env.CI !== "false") return true;
|
|
40
|
+
if (env.npm_config_global !== "true") return true;
|
|
41
|
+
return !isTTY;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Wrap the drift lines in a visually distinct block so it is not lost in npm's output. */
|
|
45
|
+
export function formatNotice(lines) {
|
|
46
|
+
const body = lines.map((l) => ` ${l}`).join("\n");
|
|
47
|
+
return `\npostman-cli-completion:\n${body}\n\n Silence this check with POSTMAN_COMPLETION_NO_VERSION_CHECK=1\n`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function main() {
|
|
51
|
+
if (shouldSuppress()) return;
|
|
52
|
+
|
|
53
|
+
const report = await checkVersions({
|
|
54
|
+
specPath: resolve(PKG_ROOT, "spec/commands.json"),
|
|
55
|
+
packageJsonPath: resolve(PKG_ROOT, "package.json"),
|
|
56
|
+
offline: true, // rule 2: no network during install
|
|
57
|
+
timeoutMs: 3000, // keep the install snappy even if the CLI hangs
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Only speak up about real, actionable drift. A matching version, a missing
|
|
61
|
+
// CLI, or an undeterminable one are all silent: a fresh `npm i -g` of this
|
|
62
|
+
// package should not lecture someone who has not installed the CLI yet.
|
|
63
|
+
if (!report.drifted) return;
|
|
64
|
+
|
|
65
|
+
process.stderr.write(formatNotice(report.lines));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Only run when invoked as the postinstall script, not when imported by tests.
|
|
69
|
+
// (Matches the isMainModule guard in scripts/generate.mjs.)
|
|
70
|
+
const isMainModule = import.meta.url === `file://${process.argv[1]}`;
|
|
71
|
+
if (isMainModule) {
|
|
72
|
+
// Rule 1: an install must never fail because of this notice.
|
|
73
|
+
try {
|
|
74
|
+
await main();
|
|
75
|
+
} catch {
|
|
76
|
+
// Intentionally silent — a broken notice is not worth a failed install.
|
|
77
|
+
}
|
|
78
|
+
process.exit(0);
|
|
79
|
+
}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
// Version drift detection for the Postman CLI.
|
|
2
|
+
//
|
|
3
|
+
// Two independent comparisons, because "outdated" has two meanings here:
|
|
4
|
+
//
|
|
5
|
+
// 1. installed CLI vs. the version these completions were generated against
|
|
6
|
+
// (spec/commands.json.postmanCliVersion). A mismatch means completions may
|
|
7
|
+
// offer commands the user's CLI lacks, or miss ones it has. Offline, free.
|
|
8
|
+
// 2. installed CLI vs. the latest published postman-cli on npm. A mismatch
|
|
9
|
+
// means the user should upgrade. Requires the network.
|
|
10
|
+
//
|
|
11
|
+
// Everything that decides *what* to say is a pure function so it can be tested
|
|
12
|
+
// without a CLI, a network, or a filesystem.
|
|
13
|
+
|
|
14
|
+
import { execFile } from "node:child_process";
|
|
15
|
+
import { readFileSync } from "node:fs";
|
|
16
|
+
|
|
17
|
+
export const REGISTRY_URL = "https://registry.npmjs.org/postman-cli/latest";
|
|
18
|
+
export const UPGRADE_COMMAND = "npm install -g postman-cli@latest";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Parse a semver-ish string into comparable parts.
|
|
22
|
+
* Tolerates a leading "v", trailing prerelease/build metadata, and surrounding
|
|
23
|
+
* whitespace. Returns null for anything we cannot order numerically.
|
|
24
|
+
*/
|
|
25
|
+
export function parseVersion(raw) {
|
|
26
|
+
if (typeof raw !== "string") return null;
|
|
27
|
+
const m = /^\s*v?(\d+)\.(\d+)\.(\d+)(?:[-+]([0-9A-Za-z.-]+))?\s*$/.exec(raw);
|
|
28
|
+
if (!m) return null;
|
|
29
|
+
return {
|
|
30
|
+
major: Number(m[1]),
|
|
31
|
+
minor: Number(m[2]),
|
|
32
|
+
patch: Number(m[3]),
|
|
33
|
+
prerelease: m[4] ?? null,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Compare two version strings. Returns -1 / 0 / 1 (a<b / a==b / a>b), or null
|
|
39
|
+
* if either side is unparseable — callers must treat null as "unknown", never
|
|
40
|
+
* as "equal", so we don't warn on garbage input.
|
|
41
|
+
*
|
|
42
|
+
* A prerelease sorts below the same x.y.z release (1.2.3-beta < 1.2.3), which
|
|
43
|
+
* matches semver. We deliberately do not order two different prereleases of the
|
|
44
|
+
* same release against each other beyond a plain string compare.
|
|
45
|
+
*/
|
|
46
|
+
export function compareVersions(a, b) {
|
|
47
|
+
const pa = parseVersion(a);
|
|
48
|
+
const pb = parseVersion(b);
|
|
49
|
+
if (!pa || !pb) return null;
|
|
50
|
+
|
|
51
|
+
for (const part of ["major", "minor", "patch"]) {
|
|
52
|
+
if (pa[part] !== pb[part]) return pa[part] < pb[part] ? -1 : 1;
|
|
53
|
+
}
|
|
54
|
+
if (pa.prerelease === pb.prerelease) return 0;
|
|
55
|
+
if (pa.prerelease === null) return 1; // release > prerelease
|
|
56
|
+
if (pb.prerelease === null) return -1;
|
|
57
|
+
return pa.prerelease < pb.prerelease ? -1 : 1;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Read the installed Postman CLI version by shelling out to `postman --version`,
|
|
62
|
+
* which prints a bare "1.73.0".
|
|
63
|
+
*
|
|
64
|
+
* Never throws: a missing CLI, a non-zero exit, or a hang are all normal states
|
|
65
|
+
* for this tool (the completions package is installed independently of the CLI).
|
|
66
|
+
* Resolves { version, error } where exactly one side is set.
|
|
67
|
+
*/
|
|
68
|
+
export function detectInstalledVersion({ command = "postman", timeoutMs = 5000 } = {}) {
|
|
69
|
+
return new Promise((resolvePromise) => {
|
|
70
|
+
execFile(command, ["--version"], { timeout: timeoutMs }, (err, stdout) => {
|
|
71
|
+
if (err) {
|
|
72
|
+
const reason =
|
|
73
|
+
err.code === "ENOENT"
|
|
74
|
+
? `"${command}" not found on PATH`
|
|
75
|
+
: err.killed
|
|
76
|
+
? `"${command} --version" timed out`
|
|
77
|
+
: `"${command} --version" failed: ${err.message.trim()}`;
|
|
78
|
+
resolvePromise({ version: null, error: reason });
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
// Some builds print the version among other noise; take the first
|
|
82
|
+
// semver-looking token so we degrade gracefully rather than fail.
|
|
83
|
+
const match = /v?\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?/.exec(stdout ?? "");
|
|
84
|
+
if (!match) {
|
|
85
|
+
resolvePromise({
|
|
86
|
+
version: null,
|
|
87
|
+
error: `could not parse a version from "${command} --version" output`,
|
|
88
|
+
});
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
resolvePromise({ version: match[0].replace(/^v/, ""), error: null });
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Version of the Postman CLI that the committed spec (and therefore the shipped
|
|
98
|
+
* completion scripts) was generated against.
|
|
99
|
+
*/
|
|
100
|
+
export function readSpecVersion(specPath) {
|
|
101
|
+
try {
|
|
102
|
+
const v = JSON.parse(readFileSync(specPath, "utf8")).postmanCliVersion;
|
|
103
|
+
return typeof v === "string" ? v : null;
|
|
104
|
+
} catch {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* The CLI version these completions target.
|
|
111
|
+
*
|
|
112
|
+
* Two sources, in order:
|
|
113
|
+
* 1. spec/commands.json — present in a git checkout, the real source of truth.
|
|
114
|
+
* 2. this package's own version — release.yml runs `npm version <spec
|
|
115
|
+
* version>` before publishing, so an installed copy's version *is* the
|
|
116
|
+
* targeted Postman CLI version. This keeps the 348 kB spec out of the
|
|
117
|
+
* published tarball.
|
|
118
|
+
* In a git checkout package.json holds the placeholder
|
|
119
|
+
* "0.0.0-managed-by-spec". That is technically valid semver (a prerelease of
|
|
120
|
+
* 0.0.0), so it must be rejected explicitly — otherwise every real CLI would
|
|
121
|
+
* compare as "newer" and trigger a bogus drift warning.
|
|
122
|
+
*/
|
|
123
|
+
export function resolveTargetVersion({ specPath, packageJsonPath }) {
|
|
124
|
+
const fromSpec = readSpecVersion(specPath);
|
|
125
|
+
if (fromSpec) return fromSpec;
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
const v = JSON.parse(readFileSync(packageJsonPath, "utf8")).version;
|
|
129
|
+
const parsed = parseVersion(v);
|
|
130
|
+
if (!parsed) return null;
|
|
131
|
+
// Never trust a 0.0.0 placeholder as a real target version.
|
|
132
|
+
if (parsed.major === 0 && parsed.minor === 0 && parsed.patch === 0) return null;
|
|
133
|
+
return v;
|
|
134
|
+
} catch {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Ask npm for the latest published postman-cli version.
|
|
141
|
+
* Never throws — offline is an expected state, so failure is reported as data.
|
|
142
|
+
*/
|
|
143
|
+
export async function fetchLatestVersion({
|
|
144
|
+
url = REGISTRY_URL,
|
|
145
|
+
timeoutMs = 5000,
|
|
146
|
+
fetchImpl = globalThis.fetch,
|
|
147
|
+
} = {}) {
|
|
148
|
+
const signal = AbortSignal.timeout(timeoutMs);
|
|
149
|
+
try {
|
|
150
|
+
const res = await fetchImpl(url, {
|
|
151
|
+
headers: { Accept: "application/json" },
|
|
152
|
+
signal,
|
|
153
|
+
});
|
|
154
|
+
if (!res.ok) return { version: null, error: `registry request failed: ${res.status}` };
|
|
155
|
+
const body = await res.json();
|
|
156
|
+
const version = typeof body?.version === "string" ? body.version : null;
|
|
157
|
+
return version
|
|
158
|
+
? { version, error: null }
|
|
159
|
+
: { version: null, error: "registry response had no version field" };
|
|
160
|
+
} catch (err) {
|
|
161
|
+
const offline = err?.name === "TimeoutError" || err?.name === "AbortError";
|
|
162
|
+
return {
|
|
163
|
+
version: null,
|
|
164
|
+
error: offline ? "registry request timed out" : `registry request failed: ${err.message}`,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Decide what to report, given the three versions. Pure.
|
|
171
|
+
*
|
|
172
|
+
* `installed` / `spec` / `latest` may each be null (unknown). Returns a report:
|
|
173
|
+
* { level, lines, outdated, drifted }
|
|
174
|
+
* where level is "ok" | "notice" | "warn" and lines are ready to print.
|
|
175
|
+
*/
|
|
176
|
+
export function buildReport({ installed, spec, latest }) {
|
|
177
|
+
const lines = [];
|
|
178
|
+
let level = "ok";
|
|
179
|
+
const bump = (next) => {
|
|
180
|
+
const rank = { ok: 0, notice: 1, warn: 2 };
|
|
181
|
+
if (rank[next] > rank[level]) level = next;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
if (!installed) {
|
|
185
|
+
return {
|
|
186
|
+
level: "notice",
|
|
187
|
+
outdated: false,
|
|
188
|
+
drifted: false,
|
|
189
|
+
lines: [
|
|
190
|
+
"Postman CLI not detected — install it with: npm install -g postman-cli",
|
|
191
|
+
"(this package only provides shell completions; it does not include the CLI)",
|
|
192
|
+
],
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// 1. installed vs. latest published — the upgrade suggestion.
|
|
197
|
+
const vsLatest = compareVersions(installed, latest);
|
|
198
|
+
const outdated = vsLatest === -1;
|
|
199
|
+
if (outdated) {
|
|
200
|
+
bump("warn");
|
|
201
|
+
lines.push(
|
|
202
|
+
`Your Postman CLI is outdated: installed ${installed}, latest ${latest}.`,
|
|
203
|
+
` Upgrade with: ${UPGRADE_COMMAND}`,
|
|
204
|
+
);
|
|
205
|
+
} else if (vsLatest === 1) {
|
|
206
|
+
lines.push(`Installed Postman CLI ${installed} is newer than npm latest ${latest}.`);
|
|
207
|
+
} else if (vsLatest === 0) {
|
|
208
|
+
lines.push(`Postman CLI ${installed} is up to date.`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// 2. installed vs. the spec these completions were built from — the drift
|
|
212
|
+
// warning. Reported separately: being on the latest CLI does not guarantee
|
|
213
|
+
// the completions were regenerated for it yet, and vice versa.
|
|
214
|
+
const vsSpec = compareVersions(installed, spec);
|
|
215
|
+
const drifted = vsSpec !== null && vsSpec !== 0;
|
|
216
|
+
if (drifted) {
|
|
217
|
+
bump("warn");
|
|
218
|
+
lines.push(
|
|
219
|
+
`These completions were generated for Postman CLI ${spec}, but ${installed} is installed.`,
|
|
220
|
+
vsSpec === -1
|
|
221
|
+
? " Completions may offer commands your CLI does not have."
|
|
222
|
+
: " Completions may be missing commands your CLI supports.",
|
|
223
|
+
" Update them with: npm install -g postman-cli-completion@latest",
|
|
224
|
+
);
|
|
225
|
+
} else if (vsSpec === 0) {
|
|
226
|
+
lines.push(`Completions match your installed CLI (${installed}).`);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return { level, lines, outdated, drifted };
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Run both comparisons and build the report.
|
|
234
|
+
* `offline: true` skips the registry lookup entirely (no network, no delay).
|
|
235
|
+
*/
|
|
236
|
+
export async function checkVersions({
|
|
237
|
+
specPath,
|
|
238
|
+
packageJsonPath,
|
|
239
|
+
offline = false,
|
|
240
|
+
command = "postman",
|
|
241
|
+
timeoutMs = 5000,
|
|
242
|
+
fetchImpl = globalThis.fetch,
|
|
243
|
+
} = {}) {
|
|
244
|
+
const spec = resolveTargetVersion({ specPath, packageJsonPath });
|
|
245
|
+
const [{ version: installed, error: installError }, latestResult] = await Promise.all([
|
|
246
|
+
detectInstalledVersion({ command, timeoutMs }),
|
|
247
|
+
offline
|
|
248
|
+
? Promise.resolve({ version: null, error: "skipped (offline)" })
|
|
249
|
+
: fetchLatestVersion({ timeoutMs, fetchImpl }),
|
|
250
|
+
]);
|
|
251
|
+
|
|
252
|
+
const report = buildReport({ installed, spec, latest: latestResult.version });
|
|
253
|
+
return {
|
|
254
|
+
...report,
|
|
255
|
+
installed,
|
|
256
|
+
spec,
|
|
257
|
+
latest: latestResult.version,
|
|
258
|
+
installError,
|
|
259
|
+
latestError: latestResult.error,
|
|
260
|
+
};
|
|
261
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "postman-cli-completion",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.74.0",
|
|
4
4
|
"description": "Shell completions for the Postman CLI (zsh, bash, fish)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"bin/",
|
|
11
|
+
"lib/",
|
|
11
12
|
"completions/zsh/_postman",
|
|
12
13
|
"completions/bash/postman.bash",
|
|
13
14
|
"completions/fish/postman.fish",
|
|
@@ -19,6 +20,7 @@
|
|
|
19
20
|
"generate": "node scripts/generate.mjs",
|
|
20
21
|
"generate:check": "node scripts/generate.mjs --check",
|
|
21
22
|
"check-version": "node scripts/check-version.mjs",
|
|
23
|
+
"postinstall": "node lib/postinstall.mjs",
|
|
22
24
|
"test": "node --test tests/*.test.mjs",
|
|
23
25
|
"prepublishOnly": "node scripts/generate.mjs --check && node --test tests/*.test.mjs"
|
|
24
26
|
},
|