requestshield 0.1.0 → 0.1.2
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 +189 -52
- package/package.json +11 -11
- package/src/args.mjs +54 -7
- package/src/cli.mjs +13 -2
- package/src/commands/update-check.mjs +139 -0
- package/src/main.mjs +0 -0
package/README.md
CHANGED
|
@@ -1,96 +1,233 @@
|
|
|
1
1
|
# RequestShield CLI
|
|
2
2
|
|
|
3
|
-
Customer-facing
|
|
4
|
-
|
|
3
|
+
Customer-facing command-line tool for RequestShield. The CLI lets a customer
|
|
4
|
+
sign in, rotate application credentials, install the RequestShield Skill for
|
|
5
|
+
Codex, and check for a newer npm release.
|
|
6
|
+
|
|
7
|
+
Current package version: `0.1.2`.
|
|
5
8
|
|
|
6
9
|
## Requirements
|
|
7
10
|
|
|
8
|
-
- Node.js 22.13 or newer.
|
|
9
|
-
-
|
|
11
|
+
- Node.js 22.13 or newer when running from source, npm, or npx.
|
|
12
|
+
- pnpm for local development and release builds.
|
|
13
|
+
- A RequestShield customer management API.
|
|
14
|
+
- Windows x64 and Inno Setup 6 when building the Windows installer.
|
|
10
15
|
|
|
11
|
-
The API defaults to `https://api.intellifend.ai`.
|
|
12
|
-
`REQUESTSHIELD_API_URL` to
|
|
16
|
+
The management API defaults to `https://api.intellifend.ai`. During local
|
|
17
|
+
development, set `REQUESTSHIELD_API_URL` to another API origin when needed.
|
|
13
18
|
|
|
14
|
-
##
|
|
19
|
+
## Implemented commands
|
|
15
20
|
|
|
16
21
|
```console
|
|
17
22
|
requestshield signin
|
|
23
|
+
requestshield keys create [--yes]
|
|
24
|
+
requestshield agent setup --agent codex [--force]
|
|
25
|
+
requestshield update check
|
|
26
|
+
requestshield --help
|
|
27
|
+
requestshield --version
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The full command catalog shown by `requestshield --help` includes planned
|
|
31
|
+
commands. Only the commands listed above are currently implemented.
|
|
32
|
+
|
|
33
|
+
### Sign in
|
|
34
|
+
|
|
35
|
+
```console
|
|
36
|
+
requestshield signin
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Starts the device authorization flow. The CLI prints the verification URL and
|
|
40
|
+
user code but does not open a browser automatically. After authorization, the
|
|
41
|
+
session is saved in the current user's application-data directory.
|
|
42
|
+
|
|
43
|
+
For CI or automation, `REQUESTSHIELD_ACCESS_TOKEN` may provide the access token.
|
|
44
|
+
An environment token is never persisted by the CLI.
|
|
45
|
+
|
|
46
|
+
### Create new keys
|
|
47
|
+
|
|
48
|
+
```console
|
|
18
49
|
requestshield keys create
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Asks the customer to type `ROTATE`, then atomically deactivates the previous
|
|
53
|
+
credentials and creates a new `appKey` and `apiSecret`.
|
|
54
|
+
|
|
55
|
+
- `appKey` identifies the application and may be used by the client integration.
|
|
56
|
+
- `apiSecret` is a backend-only secret, is displayed once, and is not saved by
|
|
57
|
+
the CLI.
|
|
58
|
+
- `--yes` skips the interactive confirmation and is intended for automation.
|
|
59
|
+
|
|
60
|
+
Store the Secret Key in a backend secret manager immediately after creation.
|
|
61
|
+
|
|
62
|
+
### Install the Codex Skill
|
|
63
|
+
|
|
64
|
+
```console
|
|
19
65
|
requestshield agent setup --agent codex
|
|
20
66
|
```
|
|
21
67
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
instead provide `REQUESTSHIELD_ACCESS_TOKEN`; environment tokens are never
|
|
26
|
-
persisted.
|
|
68
|
+
Installs the bundled RequestShield Skill into
|
|
69
|
+
`$CODEX_HOME/skills/requestshield`, or `~/.codex/skills/requestshield` when
|
|
70
|
+
`CODEX_HOME` is not set. Restart Codex or start a new task after installation.
|
|
27
71
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
app key and the backend-only secret are printed after success. The secret is
|
|
31
|
-
not stored by the CLI and cannot be retrieved again.
|
|
72
|
+
If that Skill already exists, the command preserves it. Use `--force` to
|
|
73
|
+
replace the existing copy:
|
|
32
74
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
75
|
+
```console
|
|
76
|
+
requestshield agent setup --agent codex --force
|
|
77
|
+
```
|
|
36
78
|
|
|
37
|
-
|
|
79
|
+
### Check for updates
|
|
38
80
|
|
|
39
|
-
|
|
81
|
+
```console
|
|
82
|
+
requestshield update check
|
|
83
|
+
```
|
|
40
84
|
|
|
41
|
-
`
|
|
85
|
+
Reads the latest `requestshield` version from the npm registry. If a newer
|
|
86
|
+
version exists, the CLI displays the current and latest versions, asks the user
|
|
87
|
+
to confirm with `y`, then runs:
|
|
42
88
|
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
"client": "requestshield-cli"
|
|
46
|
-
}
|
|
89
|
+
```console
|
|
90
|
+
npm install --global requestshield@<latest-version>
|
|
47
91
|
```
|
|
48
92
|
|
|
49
|
-
|
|
50
|
-
|
|
93
|
+
This updater is for npm global installations and requires `node` and `npm` on
|
|
94
|
+
the customer's machine. It does not replace an executable installed by the
|
|
95
|
+
Windows Inno Setup installer.
|
|
51
96
|
|
|
52
|
-
|
|
97
|
+
## Install and run with npm
|
|
53
98
|
|
|
54
|
-
|
|
99
|
+
Install globally so `requestshield` is available from any directory:
|
|
55
100
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
101
|
+
```console
|
|
102
|
+
npm install --global requestshield
|
|
103
|
+
requestshield --help
|
|
104
|
+
```
|
|
59
105
|
|
|
60
|
-
|
|
106
|
+
Run without a global installation:
|
|
61
107
|
|
|
62
|
-
|
|
63
|
-
|
|
108
|
+
```console
|
|
109
|
+
npx --yes requestshield@0.1.2 --help
|
|
110
|
+
npx --yes requestshield@0.1.2 signin
|
|
111
|
+
```
|
|
64
112
|
|
|
65
|
-
##
|
|
113
|
+
## Local development
|
|
114
|
+
|
|
115
|
+
From the `requestshield-cli` directory:
|
|
66
116
|
|
|
67
117
|
```console
|
|
68
|
-
pnpm
|
|
69
|
-
pnpm
|
|
70
|
-
pnpm
|
|
71
|
-
pnpm
|
|
118
|
+
pnpm install
|
|
119
|
+
pnpm lint
|
|
120
|
+
pnpm typecheck
|
|
121
|
+
pnpm test
|
|
122
|
+
pnpm build
|
|
123
|
+
node src/main.mjs --help
|
|
124
|
+
node src/main.mjs --version
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
`build` currently validates the syntax of all `.mjs` files. It does not create
|
|
128
|
+
the standalone Windows executable.
|
|
129
|
+
|
|
130
|
+
To test the update interaction without publishing or installing anything:
|
|
131
|
+
|
|
132
|
+
```powershell
|
|
133
|
+
$env:REQUESTSHIELD_MOCK_LATEST_VERSION = "0.1.3"
|
|
134
|
+
pnpm test:update-check:manual
|
|
135
|
+
Remove-Item Env:REQUESTSHIELD_MOCK_LATEST_VERSION
|
|
72
136
|
```
|
|
73
|
-
## Quick start
|
|
74
137
|
|
|
75
|
-
|
|
138
|
+
The manual test prints `[MOCK] Would install ...`; it intentionally does not
|
|
139
|
+
change the installed CLI version.
|
|
140
|
+
|
|
141
|
+
## Build the standalone Windows executable
|
|
142
|
+
|
|
143
|
+
The project uses Node.js Single Executable Applications (SEA). Run these steps
|
|
144
|
+
from the `requestshield-cli` directory in PowerShell:
|
|
145
|
+
|
|
146
|
+
```powershell
|
|
147
|
+
New-Item -ItemType Directory -Force -Path .\dist | Out-Null
|
|
148
|
+
pnpm exec esbuild .\src\main.mjs --bundle --platform=node --format=cjs --target=node22 --outfile=.\dist\requestshield.cjs
|
|
149
|
+
node --experimental-sea-config .\sea-config.json
|
|
150
|
+
Copy-Item (Get-Command node.exe).Source .\dist\requestshield.exe -Force
|
|
151
|
+
pnpm exec postject .\dist\requestshield.exe NODE_SEA_BLOB .\dist\requestshield.blob --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2
|
|
152
|
+
.\dist\requestshield.exe --help
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
The generated file is `dist\requestshield.exe`. It contains Node.js and does
|
|
156
|
+
not require Node.js to be installed on the customer's machine.
|
|
157
|
+
|
|
158
|
+
## Build the Windows installer
|
|
76
159
|
|
|
77
|
-
|
|
160
|
+
Install Inno Setup 6, then compile the installer and pass the current package
|
|
161
|
+
version explicitly:
|
|
162
|
+
|
|
163
|
+
```powershell
|
|
164
|
+
New-Item -ItemType Directory -Force -Path .\release | Out-Null
|
|
165
|
+
& "$env:LOCALAPPDATA\Programs\Inno Setup 6\ISCC.exe" "/DAppVersion=0.1.2" "/FRequestShieldSetup-0.1.2-windows-x64" ".\installer\requestshield.iss"
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
The installer is written to `release\RequestShieldSetup-0.1.2-windows-x64.exe`.
|
|
169
|
+
It installs `requestshield.exe` under the current user's local application-data
|
|
170
|
+
directory and adds that directory to the user `PATH`. The customer must open a
|
|
171
|
+
new terminal before running:
|
|
78
172
|
|
|
79
173
|
```console
|
|
80
|
-
|
|
81
|
-
npx requestshield signin
|
|
82
|
-
npx requestshield keys create
|
|
83
|
-
npx requestshield agent setup --agent codex
|
|
174
|
+
requestshield --help
|
|
84
175
|
```
|
|
85
176
|
|
|
86
|
-
|
|
177
|
+
## Publish to npm
|
|
178
|
+
|
|
179
|
+
Before publishing, update `version` in `package.json`, run the checks, and
|
|
180
|
+
inspect the package contents:
|
|
87
181
|
|
|
88
182
|
```console
|
|
89
|
-
|
|
183
|
+
pnpm lint
|
|
184
|
+
pnpm typecheck
|
|
185
|
+
pnpm test
|
|
186
|
+
pnpm pack --dry-run
|
|
187
|
+
pnpm publish --access public --no-git-checks
|
|
90
188
|
```
|
|
91
189
|
|
|
92
|
-
|
|
190
|
+
npm may ask for a one-time password in the interactive terminal. After
|
|
191
|
+
publishing, verify the registry state:
|
|
93
192
|
|
|
94
193
|
```console
|
|
95
|
-
|
|
194
|
+
npm view requestshield version
|
|
195
|
+
npm dist-tag ls requestshield
|
|
196
|
+
npx --yes requestshield@0.1.2 --version
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Management API contract
|
|
200
|
+
|
|
201
|
+
### Start sign-in
|
|
202
|
+
|
|
203
|
+
`POST /v1/cli/signin`
|
|
204
|
+
|
|
205
|
+
```json
|
|
206
|
+
{
|
|
207
|
+
"client": "requestshield-cli"
|
|
208
|
+
}
|
|
96
209
|
```
|
|
210
|
+
|
|
211
|
+
The response contains `deviceCode`, `userCode`, `verificationUri`, `expiresIn`,
|
|
212
|
+
and `interval`.
|
|
213
|
+
|
|
214
|
+
### Poll sign-in
|
|
215
|
+
|
|
216
|
+
`POST /v1/cli/signin/token` with `{ "deviceCode": "..." }`.
|
|
217
|
+
|
|
218
|
+
Pending responses use HTTP 400 with `code` set to `authorization_pending` or
|
|
219
|
+
`slow_down`. A successful response contains `accessToken` and optional account
|
|
220
|
+
metadata.
|
|
221
|
+
|
|
222
|
+
### Rotate keys
|
|
223
|
+
|
|
224
|
+
`POST /v1/cli/keys/rotate` with Bearer authentication and an empty JSON body.
|
|
225
|
+
The response contains `appKey` and the one-time `apiSecret`.
|
|
226
|
+
|
|
227
|
+
## Security notes
|
|
228
|
+
|
|
229
|
+
- Never place an access token or Secret Key in URLs, command arguments, logs,
|
|
230
|
+
fixtures, or committed files.
|
|
231
|
+
- The CLI does not persist the Secret Key returned by `keys create`.
|
|
232
|
+
- Sign the Windows executable and installer before distributing them to
|
|
233
|
+
customers, and publish a checksum through a trusted release channel.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "requestshield",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Customer CLI for
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Customer CLI for RequestShield.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"requestshield": "src/main.mjs"
|
|
@@ -14,18 +14,18 @@
|
|
|
14
14
|
"engines": {
|
|
15
15
|
"node": ">=22.13"
|
|
16
16
|
},
|
|
17
|
-
"scripts": {
|
|
18
|
-
"build": "node scripts/check-package.mjs",
|
|
19
|
-
"lint": "node scripts/check-package.mjs",
|
|
20
|
-
"test": "node --test",
|
|
21
|
-
"typecheck": "tsc -p jsconfig.json --noEmit",
|
|
22
|
-
"prepublishOnly": "npm run lint && npm run typecheck && npm test"
|
|
23
|
-
},
|
|
24
17
|
"devDependencies": {
|
|
25
18
|
"@types/node": "26.1.1",
|
|
26
19
|
"esbuild": "^0.28.1",
|
|
27
20
|
"postject": "1.0.0-alpha.6",
|
|
28
21
|
"typescript": "^6.0.3"
|
|
29
22
|
},
|
|
30
|
-
"license": "UNLICENSED"
|
|
31
|
-
|
|
23
|
+
"license": "UNLICENSED",
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "node scripts/check-package.mjs",
|
|
26
|
+
"lint": "node scripts/check-package.mjs",
|
|
27
|
+
"test": "node --test",
|
|
28
|
+
"test:update-check:manual": "node scripts/manual-update-check.mjs",
|
|
29
|
+
"typecheck": "tsc -p jsconfig.json --noEmit"
|
|
30
|
+
}
|
|
31
|
+
}
|
package/src/args.mjs
CHANGED
|
@@ -2,18 +2,61 @@
|
|
|
2
2
|
|
|
3
3
|
import { CliError } from "./errors.mjs";
|
|
4
4
|
|
|
5
|
-
const HELP = `RequestShield
|
|
5
|
+
const HELP = `RequestShield CLI
|
|
6
6
|
|
|
7
7
|
Usage:
|
|
8
|
-
requestshield
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
requestshield [command] [options]
|
|
9
|
+
|
|
10
|
+
Commands:
|
|
11
|
+
update
|
|
12
|
+
check Check for and install the latest CLI version
|
|
13
|
+
|
|
14
|
+
auth
|
|
15
|
+
signin Sign in to RequestShield
|
|
16
|
+
status Show authentication status
|
|
17
|
+
|
|
18
|
+
keys
|
|
19
|
+
create Create a new application and key pair
|
|
20
|
+
deactivate Deactivate application credentials
|
|
21
|
+
rotate Rotate the Secret Key for an application
|
|
22
|
+
|
|
23
|
+
agent
|
|
24
|
+
setup Install the RequestShield Agent Skill
|
|
25
|
+
setup --agent codex Install the Skill for Codex
|
|
26
|
+
setup --agent claude Install the Skill for Claude
|
|
27
|
+
setup --status Show Agent Skill installation status
|
|
28
|
+
|
|
29
|
+
contract
|
|
30
|
+
get Get the RequestShield integration contract
|
|
31
|
+
|
|
32
|
+
apps
|
|
33
|
+
list List accessible applications
|
|
34
|
+
get <app-key> Get application information
|
|
35
|
+
readiness <app-key> Check application readiness
|
|
36
|
+
|
|
37
|
+
credentials
|
|
38
|
+
status <app-key> Show credential status without exposing secrets
|
|
39
|
+
|
|
40
|
+
challenge
|
|
41
|
+
volume <app-key> Get challenge volume for an application
|
|
42
|
+
|
|
43
|
+
billing
|
|
44
|
+
get <app-key> Get billing and plan information
|
|
45
|
+
|
|
46
|
+
protect
|
|
47
|
+
list <app-key> List protected endpoints
|
|
48
|
+
add Add a protected endpoint
|
|
49
|
+
remove Remove a protected endpoint
|
|
50
|
+
|
|
51
|
+
Options:
|
|
52
|
+
-h, --help Show help
|
|
53
|
+
-v, --version Show CLI version
|
|
54
|
+
--json Output machine-readable JSON`;
|
|
13
55
|
|
|
14
56
|
/**
|
|
15
57
|
* @typedef {{ command: "help", help: string }
|
|
16
58
|
* | { command: "version" }
|
|
59
|
+
* | { command: "update-check" }
|
|
17
60
|
* | { command: "signin" }
|
|
18
61
|
* | { command: "keys-create", yes: boolean }
|
|
19
62
|
* | { command: "agent-setup", agent: "codex", force: boolean }} ParsedArgs
|
|
@@ -24,9 +67,13 @@ export function parseArgs(argv) {
|
|
|
24
67
|
if (argv.length === 0 || argv.includes("--help") || argv.includes("-h")) {
|
|
25
68
|
return { command: "help", help: HELP };
|
|
26
69
|
}
|
|
27
|
-
if (argv.length === 1 && (argv[0] === "--version" || argv[0] === "-V")) {
|
|
70
|
+
if (argv.length === 1 && (argv[0] === "--version" || argv[0] === "-v" || argv[0] === "-V")) {
|
|
28
71
|
return { command: "version" };
|
|
29
72
|
}
|
|
73
|
+
if (argv[0] === "update" && argv[1] === "check") {
|
|
74
|
+
assertOnly(argv.slice(2), new Set());
|
|
75
|
+
return { command: "update-check" };
|
|
76
|
+
}
|
|
30
77
|
if (argv[0] === "signin") {
|
|
31
78
|
assertOnly(argv.slice(1), new Set());
|
|
32
79
|
return { command: "signin" };
|
package/src/cli.mjs
CHANGED
|
@@ -7,17 +7,28 @@ import { SessionStore } from "./session-store.mjs";
|
|
|
7
7
|
import { signin } from "./commands/signin.mjs";
|
|
8
8
|
import { createKeys } from "./commands/keys-create.mjs";
|
|
9
9
|
import { setupAgent } from "./commands/agent-setup.mjs";
|
|
10
|
+
import { checkForUpdate } from "./commands/update-check.mjs";
|
|
10
11
|
import packageJson from "../package.json" with { type: "json" };
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* @param {string[]} argv
|
|
14
|
-
* @param {{ log?: (message: string) => void, api?: ManagementApiClient, sessions?: SessionStore, env?: NodeJS.ProcessEnv, homeDir?: string, sourceDir?: string, wait?: (milliseconds: number) => Promise<unknown>, confirm?: () => Promise<boolean> }} [deps]
|
|
15
|
+
* @param {{ log?: (message: string) => void, api?: ManagementApiClient, sessions?: SessionStore, env?: NodeJS.ProcessEnv, homeDir?: string, sourceDir?: string, wait?: (milliseconds: number) => Promise<unknown>, confirm?: () => Promise<boolean>, fetchImpl?: typeof fetch, confirmUpdate?: () => Promise<boolean>, installLatest?: (packageName: string, version: string) => Promise<void> }} [deps]
|
|
15
16
|
*/
|
|
16
17
|
export async function run(argv, deps = {}) {
|
|
17
18
|
const parsed = parseArgs(argv);
|
|
18
19
|
const log = deps.log ?? ((message) => console.log(message));
|
|
19
20
|
if (parsed.command === "help") return log(parsed.help);
|
|
20
|
-
if (parsed.command === "version") return log(packageJson.version);
|
|
21
|
+
if (parsed.command === "version") return log("requestshield " + packageJson.version);
|
|
22
|
+
if (parsed.command === "update-check") {
|
|
23
|
+
return checkForUpdate({
|
|
24
|
+
currentVersion: packageJson.version,
|
|
25
|
+
packageName: packageJson.name,
|
|
26
|
+
log,
|
|
27
|
+
fetchImpl: deps.fetchImpl,
|
|
28
|
+
confirm: deps.confirmUpdate,
|
|
29
|
+
install: deps.installLatest,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
21
32
|
|
|
22
33
|
if (parsed.command === "agent-setup") {
|
|
23
34
|
return setupAgent(parsed, { log, ...deps });
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import readline from "node:readline/promises";
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import { stdin, stdout } from "node:process";
|
|
6
|
+
import { CliError } from "../errors.mjs";
|
|
7
|
+
|
|
8
|
+
const DEFAULT_REGISTRY = "https://registry.npmjs.org";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {{ currentVersion: string, packageName: string, log: (message: string) => void, fetchImpl?: typeof fetch, confirm?: () => Promise<boolean>, install?: (packageName: string, version: string) => Promise<void> }} options
|
|
12
|
+
*/
|
|
13
|
+
export async function checkForUpdate(options) {
|
|
14
|
+
const latestVersion = await fetchLatestVersion(
|
|
15
|
+
options.packageName,
|
|
16
|
+
options.fetchImpl ?? fetch,
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
options.log(`Current version: ${options.currentVersion}`);
|
|
20
|
+
options.log(`Latest version: ${latestVersion}`);
|
|
21
|
+
|
|
22
|
+
if (compareVersions(latestVersion, options.currentVersion) <= 0) {
|
|
23
|
+
options.log("RequestShield is already up to date.");
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const accepted = await (options.confirm ?? confirmUpdate)(latestVersion);
|
|
28
|
+
if (!accepted) {
|
|
29
|
+
options.log("Update cancelled.");
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
options.log(`Updating RequestShield to ${latestVersion}...`);
|
|
34
|
+
await (options.install ?? installWithNpm)(options.packageName, latestVersion);
|
|
35
|
+
options.log(`RequestShield ${latestVersion} was installed successfully.`);
|
|
36
|
+
options.log("Open a new terminal before running RequestShield again.");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** @param {string} packageName @param {typeof fetch} fetchImpl */
|
|
40
|
+
async function fetchLatestVersion(packageName, fetchImpl) {
|
|
41
|
+
const url = `${DEFAULT_REGISTRY}/${encodeURIComponent(packageName)}/latest`;
|
|
42
|
+
let response;
|
|
43
|
+
try {
|
|
44
|
+
response = await fetchImpl(url, { signal: AbortSignal.timeout(15_000) });
|
|
45
|
+
} catch (error) {
|
|
46
|
+
throw new CliError(`Could not check the latest RequestShield version: ${messageOf(error)}`, {
|
|
47
|
+
code: "UPDATE_CHECK_FAILED",
|
|
48
|
+
exitCode: 7,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!response.ok) {
|
|
53
|
+
throw new CliError(`Could not check the latest RequestShield version: HTTP ${response.status}`, {
|
|
54
|
+
code: "UPDATE_CHECK_FAILED",
|
|
55
|
+
exitCode: 7,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const body = await response.json();
|
|
60
|
+
const version = body && typeof body === "object" ? Reflect.get(body, "version") : undefined;
|
|
61
|
+
if (typeof version !== "string" || !parseVersion(version)) {
|
|
62
|
+
throw new CliError("The npm registry returned an invalid RequestShield version", {
|
|
63
|
+
code: "INVALID_UPDATE_RESPONSE",
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return version;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** @param {string} latestVersion */
|
|
70
|
+
async function confirmUpdate(latestVersion) {
|
|
71
|
+
if (!stdin.isTTY || !stdout.isTTY) {
|
|
72
|
+
throw new CliError(`RequestShield ${latestVersion} is available, but confirmation requires an interactive terminal`, {
|
|
73
|
+
code: "UPDATE_CONFIRMATION_REQUIRED",
|
|
74
|
+
exitCode: 2,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
const prompt = readline.createInterface({ input: stdin, output: stdout });
|
|
78
|
+
try {
|
|
79
|
+
const answer = await prompt.question(`Update to RequestShield ${latestVersion}? (y/N): `);
|
|
80
|
+
return answer.trim().toLowerCase() === "y";
|
|
81
|
+
} finally {
|
|
82
|
+
prompt.close();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** @param {string} packageName @param {string} version */
|
|
87
|
+
async function installWithNpm(packageName, version) {
|
|
88
|
+
await new Promise((resolve, reject) => {
|
|
89
|
+
const child = spawn("npm", ["install", "--global", `${packageName}@${version}`], {
|
|
90
|
+
shell: process.platform === "win32",
|
|
91
|
+
stdio: "inherit",
|
|
92
|
+
windowsHide: true,
|
|
93
|
+
});
|
|
94
|
+
child.once("error", (error) => {
|
|
95
|
+
reject(new CliError(`Could not start npm: ${error.message}`, {
|
|
96
|
+
code: "UPDATE_FAILED",
|
|
97
|
+
exitCode: 1,
|
|
98
|
+
}));
|
|
99
|
+
});
|
|
100
|
+
child.once("exit", (code) => {
|
|
101
|
+
if (code === 0) resolve(undefined);
|
|
102
|
+
else reject(new CliError(`npm update failed with exit code ${code ?? "unknown"}`, {
|
|
103
|
+
code: "UPDATE_FAILED",
|
|
104
|
+
exitCode: 1,
|
|
105
|
+
}));
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** @param {string} left @param {string} right */
|
|
111
|
+
function compareVersions(left, right) {
|
|
112
|
+
const a = parseVersion(left);
|
|
113
|
+
const b = parseVersion(right);
|
|
114
|
+
if (!a || !b) throw new CliError("RequestShield has an invalid semantic version");
|
|
115
|
+
for (let index = 0; index < 3; index++) {
|
|
116
|
+
if (a.numbers[index] !== b.numbers[index]) return a.numbers[index] - b.numbers[index];
|
|
117
|
+
}
|
|
118
|
+
if (a.prerelease === b.prerelease) return 0;
|
|
119
|
+
if (a.prerelease === undefined) return 1;
|
|
120
|
+
if (b.prerelease === undefined) return -1;
|
|
121
|
+
return a.prerelease.localeCompare(b.prerelease, "en", { numeric: true });
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** @param {string} value */
|
|
125
|
+
function parseVersion(value) {
|
|
126
|
+
const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(value);
|
|
127
|
+
if (!match) return undefined;
|
|
128
|
+
return {
|
|
129
|
+
numbers: [Number(match[1]), Number(match[2]), Number(match[3])],
|
|
130
|
+
prerelease: match[4],
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** @param {unknown} error */
|
|
135
|
+
function messageOf(error) {
|
|
136
|
+
return error instanceof Error ? error.message : String(error);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export { compareVersions };
|
package/src/main.mjs
CHANGED
|
File without changes
|