@vincentt-xr/cli 0.16.0 → 0.18.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/MIGRATION.md +24 -0
- package/README.md +94 -1
- package/dist/cli/index.js +196 -87
- package/dist/cli/usage-text.js +32 -10
- package/dist/domain/api.d.ts +10 -8
- package/dist/domain/api.js +14 -9
- package/dist/domain/command.js +25 -16
- package/dist/domain/copy.d.ts +6 -3
- package/dist/domain/copy.js +10 -6
- package/dist/dx/command.d.ts +24 -4
- package/dist/dx/command.js +80 -25
- package/dist/dx/consent.js +6 -5
- package/dist/dx/journal.d.ts +17 -2
- package/dist/dx/journal.js +19 -3
- package/dist/dx/report.js +4 -3
- package/dist/dx/reportIo.js +6 -5
- package/dist/dx/send.js +6 -5
- package/dist/lib/backend.d.ts +0 -16
- package/dist/lib/backend.js +2 -26
- package/dist/lib/detect.d.ts +87 -0
- package/dist/lib/detect.js +867 -0
- package/dist/lib/diagnostics.js +2 -2
- package/dist/lib/feedback.js +1 -1
- package/dist/lib/feedbackCopy.js +1 -1
- package/dist/lib/initFrom.js +4 -4
- package/dist/lib/lifecycle.d.ts +2 -0
- package/dist/lib/lifecycle.js +9 -3
- package/dist/lib/listProjects.js +2 -1
- package/dist/lib/projects.d.ts +37 -2
- package/dist/lib/projects.js +86 -21
- package/dist/lib/settings.d.ts +113 -0
- package/dist/lib/settings.js +284 -0
- package/dist/lib/settingsCopy.d.ts +6 -0
- package/dist/lib/settingsCopy.js +99 -0
- package/dist/lib/settle.d.ts +61 -0
- package/dist/lib/settle.js +338 -0
- package/dist/lib/template.d.ts +19 -0
- package/dist/lib/template.js +73 -0
- package/dist/lib/terminalText.d.ts +3 -0
- package/dist/lib/terminalText.js +10 -0
- package/dist/lib/version.js +5 -11
- package/dist/lib/whoami.js +10 -3
- package/dist/library/assetsCommand.js +1 -1
- package/dist/library/client.js +8 -3
- package/dist/library/render.js +8 -6
- package/dist/login/authBackend.js +3 -2
- package/dist/login/ensure.d.ts +8 -2
- package/dist/login/ensure.js +21 -10
- package/dist/login/guard.js +5 -4
- package/dist/login/loginCommand.d.ts +2 -2
- package/dist/login/loginCommand.js +29 -5
- package/dist/login/pkce.js +1 -1
- package/dist/login/pkceLogin.js +1 -1
- package/dist/platform/outdated.js +2 -2
- package/dist/platform/upgradeReport.js +16 -12
- package/dist/preview/api.d.ts +24 -11
- package/dist/preview/api.js +42 -15
- package/dist/preview/command.d.ts +29 -0
- package/dist/preview/command.js +61 -1
- package/dist/preview/copy.d.ts +35 -3
- package/dist/preview/copy.js +102 -10
- package/dist/preview/proxy.d.ts +38 -1
- package/dist/preview/proxy.js +151 -2
- package/dist/preview/qrPage.js +1 -1
- package/dist/preview/runner.d.ts +25 -13
- package/dist/preview/runner.js +127 -26
- package/dist/preview/viewers.js +1 -1
- package/dist/publish/api.d.ts +39 -6
- package/dist/publish/api.js +80 -9
- package/dist/publish/command.d.ts +25 -0
- package/dist/publish/command.js +230 -32
- package/dist/publish/copy.d.ts +78 -5
- package/dist/publish/copy.js +271 -37
- package/dist/publish/fit.d.ts +15 -0
- package/dist/publish/fit.js +103 -0
- package/dist/publish/manifest.d.ts +44 -3
- package/dist/publish/manifest.js +172 -3
- package/dist/publish/prompt.d.ts +4 -0
- package/dist/publish/prompt.js +15 -1
- package/dist/publish/upload.d.ts +6 -0
- package/dist/publish/upload.js +11 -1
- package/dist/publish/visibility.d.ts +21 -0
- package/dist/publish/visibility.js +68 -0
- package/dist/relay/annotations.js +9 -4
- package/dist/relay/server.d.ts +21 -0
- package/dist/relay/server.js +35 -1
- package/dist/shared/authError.d.ts +27 -0
- package/dist/shared/authError.js +48 -4
- package/dist/shared/config.d.ts +21 -1
- package/dist/shared/config.js +39 -8
- package/dist/shared/countLimit.d.ts +114 -0
- package/dist/shared/countLimit.js +176 -0
- package/dist/shared/http.d.ts +23 -0
- package/dist/shared/http.js +58 -0
- package/package.json +7 -3
package/MIGRATION.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Migration notes
|
|
2
|
+
|
|
3
|
+
Per version, newest first. Read by the platform's own version surface and served to
|
|
4
|
+
creators' agents. Accurate or absent, never speculative (B-F5-2).
|
|
5
|
+
|
|
6
|
+
## 0.18.0
|
|
7
|
+
|
|
8
|
+
required: false
|
|
9
|
+
No action required for a project made from the Vincentt template. Behaviour that changed:
|
|
10
|
+
- An app not made from the template can be linked; `vincentt link` then writes a committed `vincentt.json` describing how it runs. `publish --dist <folder>` resolves against the linked project folder, not the current directory, and refuses a folder that is the project root, lies outside it, or holds a hidden file other than `.well-known` / `.nojekyll`.
|
|
11
|
+
- The preview proxy and the local relay listen on 127.0.0.1 only. The preview proxy gzips text responses (JavaScript, CSS, HTML, JSON, SVG) for clients that accept gzip.
|
|
12
|
+
- Every verb that needs an account now exits 4 (not signed in) with the `vincentt login` remedy, whether no token is stored or the API refuses the stored one; with no stored token `publish`, `preview`, `link`, `projects`, `whoami` and `dx` used to exit 1. A running preview whose heartbeat is refused ends and exits 4. Without a terminal, `link` and `projects` refuse at once instead of waiting on a browser sign-in. `create` in a folder that already holds an app still exits 1.
|
|
13
|
+
- `publish` on a project Vincentt has taken offline over its traffic allowance exits 1 before prompting and uploads nothing; it used to upload the whole build and be refused at the end. `publish` prints `Live at` only for an address that is serving. A published address that serves nothing shows a not-serving checklist, and an unrecognised state defaults its prompt to no.
|
|
14
|
+
- A preview ending names its real cause (for example stopped from another terminal, or abandoned) instead of always reporting a dropped connection. `preview --stop` exits 1 when there was no preview to stop. `domain attach` at the custom-domain limit states the real allowance; a plan with none is told custom domains are part of the paid plans.
|
|
15
|
+
- With no API configured, every verb uses https://api.vincentt.studio; `publish` and `preview` used to fall back to http://localhost:5051. Every API call and upload now gives up after a deadline instead of waiting forever.
|
|
16
|
+
- A publish whose final switch-over answer is lost retries up to three times, then says the version may or may not be live instead of crashing. Ctrl-D at a name or address prompt cancels instead of hanging. Output lines no longer use em-dashes (the preview ending lines are unchanged for now), so a script matching the old wording exactly needs updating.
|
|
17
|
+
- `publish` prints `Live at` only once the public address serves the new `index.html`, printing a `Propagating` line while it waits (up to 90 s). If the wait runs out it still exits 0 and says the version is published but not yet visible. `publish --json` (with `--yes`) prints one JSON object on stdout whose `state` is `live`, `committed_not_visible` or `failed`; the human output moves to stderr.
|
|
18
|
+
- `login` with a stored token checks it before reporting signed in. A refused token runs the sign-in again; when the account cannot be checked the token is kept and `login` exits 1 (it used to exit 0 and print "your account"). `init --from` re-asks for an address when the chosen one is taken, up to three attempts, instead of exiting at the first refusal.
|
|
19
|
+
- `vincentt dx` and `dx --send` look at the last 24 hours of runs by default, not the last 7 days. `--since <duration>` (for example `30m`, `2h`, `7d`) sets the window and the report names it; an unreadable duration exits 1. `--days <n>` still works. `feedback --wait` no longer skips an annotation when several arrive at once; every one is handed over in order.
|
|
20
|
+
|
|
21
|
+
## 0.17.0
|
|
22
|
+
|
|
23
|
+
required: false
|
|
24
|
+
No action required.
|
package/README.md
CHANGED
|
@@ -65,7 +65,10 @@ Project
|
|
|
65
65
|
if the folder is empty
|
|
66
66
|
[--name <s>] [--slug <s>] [--org <s>] [--preview]
|
|
67
67
|
[--force] [--no-install]
|
|
68
|
-
link <project-id> Link this directory to an existing project
|
|
68
|
+
link <project-id> Link this directory to an existing project; for an app not
|
|
69
|
+
made from the template, also writes vincentt.json
|
|
70
|
+
[--json] [--dev-command <cmd>] [--dev-port <n>]
|
|
71
|
+
[--build-command <cmd>] [--build-output <folder>]
|
|
69
72
|
unlink Detach this directory from its project
|
|
70
73
|
projects List the workspace's projects, their ids, and where each
|
|
71
74
|
one lives on this machine
|
|
@@ -73,8 +76,11 @@ Project
|
|
|
73
76
|
|
|
74
77
|
Dev loop
|
|
75
78
|
publish Upload the built dist; go live at <slug>.vincentt.app
|
|
79
|
+
[--dist <folder>] (relative to the project folder)
|
|
80
|
+
[--yes] [--json]
|
|
76
81
|
preview Serve + tunnel the app for on-device preview
|
|
77
82
|
(prints a desktop QR page to scan) [--stop]
|
|
83
|
+
[--port <n>] [--dev-command <cmd>]
|
|
78
84
|
|
|
79
85
|
Diagnostics (need a running preview)
|
|
80
86
|
logs [--errors] Console logs from the device [--json]
|
|
@@ -115,6 +121,14 @@ later verbs run without prompting. `init` never reaches that gate at all.
|
|
|
115
121
|
there. A count that cannot be read prints no line rather than a guess.
|
|
116
122
|
- `npx vincentt publish` — build (`pnpm build`), then upload the built `dist/` and
|
|
117
123
|
return the live `<slug>.vincentt.app` URL. Nothing builds server-side.
|
|
124
|
+
The CDN takes a few seconds to switch to a new version, so after the upload
|
|
125
|
+
`publish` prints a `→ Propagating` line and checks the address until it
|
|
126
|
+
serves the `index.html` just uploaded. Only then does it print `✓ Live at`. It
|
|
127
|
+
waits up to 90 seconds; past that it exits 0 and says the version is published
|
|
128
|
+
but not yet visible, because the publish did happen and publishing again would
|
|
129
|
+
not help. `--json` (with `--yes`) puts the human output on stderr and prints one
|
|
130
|
+
object on stdout whose `state` is `live` or `committed_not_visible` (with
|
|
131
|
+
`version`, `liveUrl` and `waitedMs`), or `failed` (with `exitCode`).
|
|
118
132
|
|
|
119
133
|
In a directory that is not linked to anything, `npx vincentt projects` lists the
|
|
120
134
|
workspace's projects with their ids, which is what `vincentt link <project-id>`
|
|
@@ -140,3 +154,82 @@ deleting `~/.vincentt/locations.ndjson` costs nothing but the shortcut.
|
|
|
140
154
|
|
|
141
155
|
`vincentt` is a project devDependency in the scaffold, so invoke it as
|
|
142
156
|
`npx vincentt <verb>` and it resolves to the local copy — no global install.
|
|
157
|
+
|
|
158
|
+
## An app that isn't built on the Vincentt template
|
|
159
|
+
|
|
160
|
+
Any app with a local dev server and a static build output can join: create the
|
|
161
|
+
project in the console, then run `vincentt link <project-id>` in the app's folder.
|
|
162
|
+
(`vincentt create` also registers a non-empty folder whose `package.json` already
|
|
163
|
+
lists a `@vincentt-xr/*` package, and does the same.)
|
|
164
|
+
|
|
165
|
+
Besides the gitignored binding, `link` reads how the app runs from
|
|
166
|
+
`package.json`, the lockfile's name and at most one bundler config. **It runs
|
|
167
|
+
nothing**: no script, no config evaluation, no install. It writes what it found to
|
|
168
|
+
`vincentt.json` at the project root:
|
|
169
|
+
|
|
170
|
+
```json
|
|
171
|
+
{
|
|
172
|
+
"$schema": "https://vincentt.studio/schema/vincentt.json",
|
|
173
|
+
"dev": { "command": "npm run serve", "port": 8080 },
|
|
174
|
+
"build": { "command": "npm run build", "output": "dist" }
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
- `dev.command` is what `vincentt preview` starts, and `dev.port` is the port it
|
|
179
|
+
forwards to. `build.output` is the folder `vincentt publish` uploads.
|
|
180
|
+
`build.command` is recorded and printed, never run: publish uploads a folder
|
|
181
|
+
that is already built.
|
|
182
|
+
- **Commit it**, so every clone previews and publishes the same way. It holds no
|
|
183
|
+
project id, account, URL or credential.
|
|
184
|
+
- **A present file is the whole truth.** A field missing from it is refused by
|
|
185
|
+
name (`vincentt.json has no dev.port.`); defaults apply only when there is no
|
|
186
|
+
file at all. A flag (`--port`, `--dev-command`, `--dist`) overrides one field
|
|
187
|
+
for one run.
|
|
188
|
+
- `vincentt.json` runs a command on every collaborator's machine at `preview`,
|
|
189
|
+
like a `package.json` script. Review changes to it in pull requests. The
|
|
190
|
+
command runs without the CLI's access token in its environment.
|
|
191
|
+
- When detection cannot find a field, `link` still succeeds and prints the
|
|
192
|
+
`vincentt.json` edit and the one-run flag. In a terminal it asks for each value
|
|
193
|
+
first. `vincentt link <id> --json` prints the result for a program to read, and
|
|
194
|
+
never asks.
|
|
195
|
+
- `--dist` and `build.output` are relative to the project folder (the one that
|
|
196
|
+
holds `.vincentt/project.json`), not the directory the command runs in.
|
|
197
|
+
- A project made from the Vincentt template is recognised and gets no
|
|
198
|
+
`vincentt.json`: its defaults are `npm run dev`, port 5173 and `dist`.
|
|
199
|
+
|
|
200
|
+
### Diagnostics for an app that isn't built on the Vincentt template
|
|
201
|
+
|
|
202
|
+
`vincentt preview` works without this. Adding it makes `vincentt logs`, `network` and
|
|
203
|
+
`trace` report what happens on the phone, and lets the console reach the preview.
|
|
204
|
+
|
|
205
|
+
1. Install the harness:
|
|
206
|
+
|
|
207
|
+
```sh
|
|
208
|
+
npm i @vincentt-xr/harness
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
2. Add this line at the top of your entry file, before anything else runs:
|
|
212
|
+
|
|
213
|
+
```js
|
|
214
|
+
import "@vincentt-xr/harness/auto";
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
That is the whole change. It needs no React and no build setting.
|
|
218
|
+
|
|
219
|
+
The line switches itself on only on a Vincentt preview address (`*.vincentt.dev`). Everywhere
|
|
220
|
+
else, including your published address, GitHub Pages, Vercel, Netlify and `localhost`, it does
|
|
221
|
+
nothing at all: no network request, no page change, no console output.
|
|
222
|
+
|
|
223
|
+
Do not add it to a project made from the Vincentt template; the template already includes the
|
|
224
|
+
harness.
|
|
225
|
+
|
|
226
|
+
### What publish checks first
|
|
227
|
+
|
|
228
|
+
Before any upload, `vincentt publish` refuses an output that is the project
|
|
229
|
+
folder itself or lies outside it, holds any name starting with `.` (other than
|
|
230
|
+
`.well-known/` and `.nojekyll` at its root), holds a symlink that resolves outside
|
|
231
|
+
the project or onto a hidden path, or holds Git LFS pointer files instead of the
|
|
232
|
+
assets (`git lfs pull`, then rebuild). A `<script src>`, stylesheet, image or media
|
|
233
|
+
reference that starts with `/` and is not in the output is reported as a warning:
|
|
234
|
+
Vincentt serves the app at the root of its address, so a build made for a subpath
|
|
235
|
+
(a GitHub Pages project site) will not find those files.
|
package/dist/cli/index.js
CHANGED
|
@@ -18,10 +18,11 @@ import { runPkceLogin } from "../login/pkceLogin.js";
|
|
|
18
18
|
import { httpAuthBackend } from "../login/authBackend.js";
|
|
19
19
|
import { loginCommand } from "../login/loginCommand.js";
|
|
20
20
|
import { ensureLoggedIn } from "../login/ensure.js";
|
|
21
|
-
import { clearMachineToken, loadMachineConfig, locateProjectBinding, resolveConfig, writeDxEnablement, MACHINE_CONFIG_PATH, } from "../shared/config.js";
|
|
21
|
+
import { clearMachineToken, loadMachineConfig, locateProjectBinding, machineApiUrl, resolveConfig, writeDxEnablement, MACHINE_CONFIG_PATH, } from "../shared/config.js";
|
|
22
22
|
import { fileLocationStore } from "../shared/locationStore.js";
|
|
23
23
|
import { getMe, listProjects, ProjectUnreachableError } from "../lib/backend.js";
|
|
24
24
|
import { EXIT } from "../lib/exitCodes.js";
|
|
25
|
+
import { AuthExpiredError, authExpiredLines, isAuthRefused, NOT_SIGNED_IN_LINES, NotSignedInError, } from "../shared/authError.js";
|
|
25
26
|
import { listProjectsCommand } from "../lib/listProjects.js";
|
|
26
27
|
import { initCommand } from "../lib/initCommand.js";
|
|
27
28
|
import { linkCommand, printUnreachable, unlinkCommand } from "../lib/projects.js";
|
|
@@ -36,13 +37,18 @@ import { USAGE_TEXT } from "./usage-text.js";
|
|
|
36
37
|
import { PreviewUi } from "../preview/command.js";
|
|
37
38
|
import { httpRelayClient } from "../lib/diagnostics.js";
|
|
38
39
|
import { readLivePreview, relayInfoDir } from "../lib/relayInfo.js";
|
|
39
|
-
import { attachedToRunning, mintRateLimited, notSignedIn, previewConflict, startBlock, } from "../preview/copy.js";
|
|
40
|
+
import { attachedToRunning, mintRateLimited, noDiagnosticsStatic, notSignedIn, previewConflict, settingsBanner, startBlock, } from "../preview/copy.js";
|
|
41
|
+
import { isPortListening } from "../preview/net.js";
|
|
42
|
+
import { readSettings, resolveSettings, SettingsError, TEMPLATE_DEFAULTS, validateField, } from "../lib/settings.js";
|
|
43
|
+
import { settingsRefusal, sourceWords } from "../lib/settingsCopy.js";
|
|
44
|
+
import { listsHarness } from "../lib/template.js";
|
|
40
45
|
import { renderTerminalQr, shouldRenderQr } from "../preview/qr.js";
|
|
41
|
-
import { PreviewConflictError, PreviewRateLimitedError } from "../preview/api.js";
|
|
46
|
+
import { PreviewConflictError, PreviewLimitError, PreviewRateLimitedError, } from "../preview/api.js";
|
|
47
|
+
import { previewLimitRefused, resolveCountLimit, } from "../shared/countLimit.js";
|
|
42
48
|
import { fetchNext, readCursor, writeCursor } from "../lib/feedback.js";
|
|
43
49
|
import * as feedbackCopy from "../lib/feedbackCopy.js";
|
|
44
50
|
import { record as recordInvocation } from "../dx/journal.js";
|
|
45
|
-
import { dxDisableCommand, dxEnableCommand, dxSendCommand, parseDxOptions, runDx, } from "../dx/command.js";
|
|
51
|
+
import { dxDisableCommand, dxEnableCommand, dxSendCommand, parseDxOptions, runDx, sinceUsageLines, } from "../dx/command.js";
|
|
46
52
|
import { httpConsentBackend, runConsentInBrowser } from "../dx/consent.js";
|
|
47
53
|
import { DX_UNATTENDED_SUNSET, loadEnablement } from "../dx/enablement.js";
|
|
48
54
|
import { journalEnabled, journalPath, readJournal } from "../dx/journal.js";
|
|
@@ -52,14 +58,13 @@ import { FETCH_TIMEOUT_MS, isFresh, updateNotice } from "../dx/updateCheck.js";
|
|
|
52
58
|
import { fetchLatest, loadCache, saveCache } from "../dx/updateCheckIo.js";
|
|
53
59
|
import { verbOf } from "../dx/skeleton.js";
|
|
54
60
|
import { outdatedCommand } from "../platform/command.js";
|
|
55
|
-
import { behindFooterLines, needsRefresh, refreshReleaseCache } from "../platform/footer.js";
|
|
61
|
+
import { behindFooterLines, needsRefresh, refreshReleaseCache, } from "../platform/footer.js";
|
|
56
62
|
import { loadReleaseCache } from "../platform/releaseCache.js";
|
|
57
63
|
import { resolveBuiltWith } from "../platform/resolve.js";
|
|
58
64
|
import { createDirCollision, createSourceFlagRefusal, fromFlagConflict, isHelpInvocation, isVersionInvocation, positionalArgs, refusedFlag, } from "./args.js";
|
|
59
65
|
import { parseSourceRef } from "../lib/sourceRef.js";
|
|
60
66
|
import { printCreateSourceFlagRefusal, printFlagConflict, printSourceRefFailure, } from "../lib/sourceRefCopy.js";
|
|
61
|
-
import {
|
|
62
|
-
import { registerProject } from "../lib/projects.js";
|
|
67
|
+
import { registerClonedProject } from "../lib/projects.js";
|
|
63
68
|
import { sanitizeAppName } from "../lib/initCommand.js";
|
|
64
69
|
import { resolveOrgForCommand } from "../lib/lifecycle.js";
|
|
65
70
|
// The console origin serves /cli-auth (the browser target for `vincentt login`). VINCENTT_
|
|
@@ -67,7 +72,6 @@ import { resolveOrgForCommand } from "../lib/lifecycle.js";
|
|
|
67
72
|
// This is the prod console; vincentt.app is the published-PROJECT apex, not the console, so a
|
|
68
73
|
// first-run `vincentt login` (no machine config yet) must default here or it opens a dead page.
|
|
69
74
|
const DEFAULT_CONSOLE_URL = "https://console.vincentt.studio";
|
|
70
|
-
const DEFAULT_API_URL = "https://api.vincentt.studio";
|
|
71
75
|
// ---- arg helpers ------------------------------------------------------------
|
|
72
76
|
const argv = process.argv.slice(2);
|
|
73
77
|
function flag(name) {
|
|
@@ -107,8 +111,12 @@ async function resolveAuthUrls() {
|
|
|
107
111
|
process.env.VINCENTT_EDITOR_URL ??
|
|
108
112
|
machine.consoleUrl ??
|
|
109
113
|
DEFAULT_CONSOLE_URL;
|
|
110
|
-
|
|
111
|
-
|
|
114
|
+
return { consoleUrl, apiUrl: machineApiUrl(machine, flag("--api")) };
|
|
115
|
+
}
|
|
116
|
+
/** The machine's own host and token, never the folder's binding. Undefined when signed out. */
|
|
117
|
+
function machineCredentials(machine) {
|
|
118
|
+
const pat = process.env.VINCENTT_PAT ?? machine.pat;
|
|
119
|
+
return pat ? { apiUrl: machineApiUrl(machine), pat } : undefined;
|
|
112
120
|
}
|
|
113
121
|
/** The PKCE loopback + code-exchange login (f1). The raw PAT never rides a redirect query. */
|
|
114
122
|
async function runLoginFlow() {
|
|
@@ -128,13 +136,8 @@ async function login() {
|
|
|
128
136
|
whoAmI: async () => {
|
|
129
137
|
const pat = await currentToken();
|
|
130
138
|
if (!pat)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
return await httpAuthBackend(apiUrl).me(pat);
|
|
134
|
-
}
|
|
135
|
-
catch {
|
|
136
|
-
return undefined;
|
|
137
|
-
}
|
|
139
|
+
throw new NotSignedInError();
|
|
140
|
+
return httpAuthBackend(apiUrl).me(pat);
|
|
138
141
|
},
|
|
139
142
|
runLogin: runLoginFlow,
|
|
140
143
|
clearToken: async () => {
|
|
@@ -149,13 +152,20 @@ async function login() {
|
|
|
149
152
|
async function currentToken() {
|
|
150
153
|
return process.env.VINCENTT_PAT ?? (await loadMachineConfig()).pat;
|
|
151
154
|
}
|
|
152
|
-
/**
|
|
155
|
+
/**
|
|
156
|
+
* Guard an auth-requiring verb: log in inline at a terminal, refuse at once
|
|
157
|
+
* without one. False means the caller must stop; the exit code is already set.
|
|
158
|
+
*/
|
|
153
159
|
async function requireLogin() {
|
|
154
|
-
await ensureLoggedIn({
|
|
160
|
+
const ok = await ensureLoggedIn({
|
|
155
161
|
currentToken,
|
|
156
162
|
runLogin: login,
|
|
157
163
|
notify: (m) => console.error(m),
|
|
164
|
+
interactive: Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY),
|
|
158
165
|
});
|
|
166
|
+
if (!ok)
|
|
167
|
+
process.exitCode = EXIT.NOT_AUTHENTICATED;
|
|
168
|
+
return ok;
|
|
159
169
|
}
|
|
160
170
|
async function logout() {
|
|
161
171
|
const { path: p, hadToken } = await clearMachineToken();
|
|
@@ -170,14 +180,7 @@ async function whoami() {
|
|
|
170
180
|
const machine = await loadMachineConfig();
|
|
171
181
|
process.exitCode = await whoamiCommand({
|
|
172
182
|
print: (l) => console.log(l),
|
|
173
|
-
config: async () =>
|
|
174
|
-
const apiUrl = process.env.VINCENTT_API_URL ?? machine.apiUrl ?? DEFAULT_API_URL;
|
|
175
|
-
const pat = process.env.VINCENTT_PAT ?? machine.pat;
|
|
176
|
-
if (!pat)
|
|
177
|
-
return undefined;
|
|
178
|
-
const cfg = { apiUrl: apiUrl.replace(/\/$/, ""), pat };
|
|
179
|
-
return cfg;
|
|
180
|
-
},
|
|
183
|
+
config: async () => machineCredentials(machine),
|
|
181
184
|
getMe,
|
|
182
185
|
org: {
|
|
183
186
|
flag: flag("--org"),
|
|
@@ -219,35 +222,17 @@ async function init() {
|
|
|
219
222
|
ask: interactive ? stdinAsk() : undefined,
|
|
220
223
|
confirm: interactive ? stdinConfirm() : undefined,
|
|
221
224
|
register: from
|
|
222
|
-
? (rd, o) =>
|
|
223
|
-
|
|
225
|
+
? (rd, o) => registerClonedProject({
|
|
226
|
+
config: (dir) => resolveConfig(dir, { warn: print }),
|
|
227
|
+
org: async (dir) => resolveOrgForCommand(await resolveConfig(dir, { warn: print }), {
|
|
228
|
+
flag: flag("--org"),
|
|
229
|
+
}),
|
|
224
230
|
currentToken,
|
|
225
231
|
runLogin: async () => {
|
|
226
232
|
await login();
|
|
227
233
|
},
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
const resolved = await resolveOrgForCommand(cfg, {
|
|
231
|
-
flag: flag("--org"),
|
|
232
|
-
});
|
|
233
|
-
if (!resolved.ok)
|
|
234
|
-
return resolved;
|
|
235
|
-
return {
|
|
236
|
-
ok: true,
|
|
237
|
-
name: resolved.org.name,
|
|
238
|
-
source: orgSource(),
|
|
239
|
-
};
|
|
240
|
-
},
|
|
241
|
-
// registerProject, NOT createCommand: createCommand is now the
|
|
242
|
-
// whole two-half verb, so calling it here would classify and
|
|
243
|
-
// scaffold a second time inside the half that already cloned.
|
|
244
|
-
create: async ({ name, slug }) => registerProject({
|
|
245
|
-
print,
|
|
246
|
-
cwd: rd.targetDir,
|
|
247
|
-
config: () => resolveConfig(rd.targetDir, { warn: print }),
|
|
248
|
-
org: async () => resolveOrgForCommand(await resolveConfig(rd.targetDir, { warn: print }), { flag: flag("--org") }),
|
|
249
|
-
}, { name, slug }),
|
|
250
|
-
}, o)
|
|
234
|
+
orgSource,
|
|
235
|
+
}, rd, o)
|
|
251
236
|
: undefined,
|
|
252
237
|
}, {
|
|
253
238
|
dir,
|
|
@@ -343,6 +328,7 @@ async function create() {
|
|
|
343
328
|
await login();
|
|
344
329
|
},
|
|
345
330
|
orgSource,
|
|
331
|
+
consoleUrl: (await resolveAuthUrls()).consoleUrl,
|
|
346
332
|
onLine: (l) => console.log(l),
|
|
347
333
|
});
|
|
348
334
|
if (result.exitCode !== 0) {
|
|
@@ -356,16 +342,30 @@ async function create() {
|
|
|
356
342
|
}
|
|
357
343
|
}
|
|
358
344
|
async function link() {
|
|
359
|
-
await requireLogin()
|
|
345
|
+
if (!(await requireLogin()))
|
|
346
|
+
return;
|
|
360
347
|
const cwd = projectDir();
|
|
348
|
+
const json = has("--json");
|
|
349
|
+
const interactive = Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY);
|
|
350
|
+
const value = (name) => flag(name) ?? valueOfEqualsFlag(argv, name);
|
|
361
351
|
process.exitCode = await linkCommand({
|
|
362
352
|
print: (l) => console.log(l),
|
|
363
353
|
cwd,
|
|
364
354
|
config: () => resolveConfig(cwd, {
|
|
365
355
|
allowCustomApi: has("--allow-custom-api"),
|
|
366
|
-
|
|
356
|
+
// Under --json stdout carries the one JSON object only.
|
|
357
|
+
warn: (l) => (json ? console.error(l) : console.log(l)),
|
|
367
358
|
}),
|
|
368
|
-
}, positional(1)
|
|
359
|
+
}, positional(1), {
|
|
360
|
+
json,
|
|
361
|
+
ask: interactive && !json ? stdinAsk() : undefined,
|
|
362
|
+
flags: {
|
|
363
|
+
devCommand: value("--dev-command"),
|
|
364
|
+
devPort: value("--dev-port"),
|
|
365
|
+
buildCommand: value("--build-command"),
|
|
366
|
+
buildOutput: value("--build-output"),
|
|
367
|
+
},
|
|
368
|
+
});
|
|
369
369
|
}
|
|
370
370
|
async function unlink() {
|
|
371
371
|
const cwd = projectDir();
|
|
@@ -385,13 +385,10 @@ async function unlink() {
|
|
|
385
385
|
* BECAUSE they are in an unbound folder depend on that folder having a binding.
|
|
386
386
|
*/
|
|
387
387
|
async function projects() {
|
|
388
|
-
await requireLogin()
|
|
388
|
+
if (!(await requireLogin()))
|
|
389
|
+
return;
|
|
389
390
|
const machine = await loadMachineConfig();
|
|
390
|
-
const config = async () =>
|
|
391
|
-
const apiUrl = process.env.VINCENTT_API_URL ?? machine.apiUrl ?? DEFAULT_API_URL;
|
|
392
|
-
const pat = process.env.VINCENTT_PAT ?? machine.pat;
|
|
393
|
-
return pat ? { apiUrl: apiUrl.replace(/\/$/, ""), pat } : undefined;
|
|
394
|
-
};
|
|
391
|
+
const config = async () => machineCredentials(machine);
|
|
395
392
|
process.exitCode = await listProjectsCommand({
|
|
396
393
|
print: (l) => console.log(l),
|
|
397
394
|
config,
|
|
@@ -419,7 +416,7 @@ async function outdated() {
|
|
|
419
416
|
print: (l) => console.log(l),
|
|
420
417
|
cwd,
|
|
421
418
|
now: () => Date.now(),
|
|
422
|
-
apiUrl: async () =>
|
|
419
|
+
apiUrl: async () => machineApiUrl(await loadMachineConfig()),
|
|
423
420
|
inProject: async () => (await locateProjectBinding(cwd)) !== undefined,
|
|
424
421
|
projectLine: async () => {
|
|
425
422
|
const name = await projectDisplayName(cwd);
|
|
@@ -446,29 +443,38 @@ async function publish() {
|
|
|
446
443
|
return;
|
|
447
444
|
}
|
|
448
445
|
const cwd = projectDir();
|
|
446
|
+
// Under --json stdout carries the one JSON object only; everything else goes to stderr.
|
|
447
|
+
const json = has("--json");
|
|
448
|
+
const say = (l) => (json ? console.error(l) : console.log(l));
|
|
449
449
|
process.exitCode = await publishCommand({
|
|
450
|
-
print:
|
|
451
|
-
write: (s) => process.stdout.write(s),
|
|
450
|
+
print: say,
|
|
451
|
+
write: (s) => (json ? process.stderr : process.stdout).write(s),
|
|
452
|
+
emit: (l) => console.log(l),
|
|
452
453
|
confirm: stdinConfirm(),
|
|
453
454
|
cwd,
|
|
454
455
|
config: () => resolveConfig(cwd, {
|
|
455
456
|
allowCustomApi: has("--allow-custom-api"),
|
|
456
|
-
warn:
|
|
457
|
+
warn: say,
|
|
457
458
|
}),
|
|
458
459
|
binding: async () => {
|
|
459
460
|
const located = await locateProjectBinding(cwd);
|
|
460
461
|
return located
|
|
461
|
-
? {
|
|
462
|
+
? {
|
|
463
|
+
projectId: located.binding.projectId,
|
|
464
|
+
slug: located.binding.slug,
|
|
465
|
+
dir: located.dir,
|
|
466
|
+
}
|
|
462
467
|
: undefined;
|
|
463
468
|
},
|
|
464
469
|
token: currentToken,
|
|
465
|
-
|
|
470
|
+
// A prompt would land in the JSON document, so --json needs --yes.
|
|
471
|
+
isTTY: !json && Boolean(process.stdout.isTTY) && Boolean(process.stdin.isTTY),
|
|
466
472
|
now: () => Date.now(),
|
|
467
473
|
sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
|
|
468
474
|
orgName: activeOrgName,
|
|
469
475
|
orgSlug: async () => (await activeOrgName()) || undefined,
|
|
470
476
|
consoleUrl: flag("--console") ?? process.env.VINCENTT_CONSOLE_URL,
|
|
471
|
-
}, { distDir: flag("--dist"), note: flag("--note"), yes: has("--yes") });
|
|
477
|
+
}, { distDir: flag("--dist"), note: flag("--note"), yes: has("--yes"), json });
|
|
472
478
|
}
|
|
473
479
|
/** Flow L30 — the six verbs that are console actions, answered rather than rejected. */
|
|
474
480
|
async function consoleOnly(verb) {
|
|
@@ -491,16 +497,61 @@ async function consoleOnly(verb) {
|
|
|
491
497
|
async function preview() {
|
|
492
498
|
const cwd = projectDir();
|
|
493
499
|
if (has("--stop")) {
|
|
494
|
-
const { message } = await previewStopSignal(cwd);
|
|
500
|
+
const { stopped, message } = await previewStopSignal(cwd);
|
|
495
501
|
console.log(message);
|
|
502
|
+
// Non-zero when nothing was stopped, so a script can tell "stopped" from "there was nothing".
|
|
503
|
+
process.exitCode = stopped ? EXIT.OK : EXIT.FAILED;
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
// Settings resolve first and locally: a refusal touches nothing, and the
|
|
507
|
+
// banner is the disclosure of the dev command before anything runs.
|
|
508
|
+
const settingsRoot = (await locateProjectBinding(cwd))?.dir ?? cwd;
|
|
509
|
+
const portFlag = flag("--port") ?? valueOfEqualsFlag(argv, "--port");
|
|
510
|
+
const devCommandFlag = flag("--dev-command") ?? valueOfEqualsFlag(argv, "--dev-command");
|
|
511
|
+
const flagRefusal = previewFlagRefusal(portFlag, devCommandFlag);
|
|
512
|
+
if (flagRefusal) {
|
|
513
|
+
for (const l of flagRefusal)
|
|
514
|
+
console.log(l);
|
|
515
|
+
process.exitCode = 1;
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
let settings;
|
|
519
|
+
try {
|
|
520
|
+
settings = resolveSettings({
|
|
521
|
+
devCommand: devCommandFlag,
|
|
522
|
+
devPort: portFlag !== undefined ? Number(portFlag) : undefined,
|
|
523
|
+
}, await readSettings(settingsRoot), TEMPLATE_DEFAULTS, ["dev"]);
|
|
524
|
+
}
|
|
525
|
+
catch (err) {
|
|
526
|
+
if (!(err instanceof SettingsError))
|
|
527
|
+
throw err;
|
|
528
|
+
for (const l of settingsRefusal(err, "preview"))
|
|
529
|
+
console.log(l);
|
|
530
|
+
process.exitCode = 1;
|
|
496
531
|
return;
|
|
497
532
|
}
|
|
533
|
+
const devCommand = settings.devCommand.value;
|
|
534
|
+
const devPort = settings.devPort.value;
|
|
535
|
+
const localUrl = `http://localhost:${devPort}`;
|
|
536
|
+
for (const l of settingsBanner({
|
|
537
|
+
devCommand,
|
|
538
|
+
devCommandSource: sourceWords(settings.devCommand),
|
|
539
|
+
devPort,
|
|
540
|
+
devPortSource: sourceWords(settings.devPort),
|
|
541
|
+
reused: await isPortListening(devPort),
|
|
542
|
+
})) {
|
|
543
|
+
console.log(l);
|
|
544
|
+
}
|
|
545
|
+
const harnessListed = await listsHarness(settingsRoot);
|
|
546
|
+
if (!harnessListed)
|
|
547
|
+
for (const l of noDiagnosticsStatic())
|
|
548
|
+
console.log(l);
|
|
498
549
|
// The gate is LOCAL and NOTHING IS ATTEMPTED: no spinner, no partial mint, no
|
|
499
550
|
// session left in `pending`. The command stops before it touches the network.
|
|
500
551
|
if (!(await currentToken())) {
|
|
501
|
-
for (const l of notSignedIn())
|
|
552
|
+
for (const l of notSignedIn(devCommand, localUrl))
|
|
502
553
|
console.log(l);
|
|
503
|
-
process.exitCode =
|
|
554
|
+
process.exitCode = EXIT.NOT_AUTHENTICATED;
|
|
504
555
|
return;
|
|
505
556
|
}
|
|
506
557
|
const filterViewer = flag("--viewer");
|
|
@@ -509,10 +560,13 @@ async function preview() {
|
|
|
509
560
|
const ui = new PreviewUi({
|
|
510
561
|
print: (l) => console.log(l),
|
|
511
562
|
now: () => Date.now(),
|
|
512
|
-
targetUrl:
|
|
563
|
+
targetUrl: localUrl,
|
|
513
564
|
color,
|
|
514
565
|
filterTester,
|
|
515
566
|
filterViewer,
|
|
567
|
+
devCommand,
|
|
568
|
+
portSource: settings.devPort.source,
|
|
569
|
+
harnessListed,
|
|
516
570
|
});
|
|
517
571
|
// A preview already running for this folder — in ANOTHER process — is
|
|
518
572
|
// FOLLOWED, not superseded. `vincentt preview` in a folder with a live
|
|
@@ -552,7 +606,9 @@ async function preview() {
|
|
|
552
606
|
let running;
|
|
553
607
|
try {
|
|
554
608
|
const started = await previewStart(cwd, (m) => console.error(`[preview] ${m}`), {
|
|
555
|
-
appPort:
|
|
609
|
+
appPort: devPort,
|
|
610
|
+
devCommand,
|
|
611
|
+
onHiddenPath: (p) => ui.hiddenPath(p),
|
|
556
612
|
appStdio: has("--verbose") ? "inherit" : "ignore",
|
|
557
613
|
onViewer: (label, uaClass) => ui.viewerSeen(label, uaClass),
|
|
558
614
|
onViewerGone: (label) => ui.viewerGone(label),
|
|
@@ -584,9 +640,13 @@ async function preview() {
|
|
|
584
640
|
}
|
|
585
641
|
}
|
|
586
642
|
catch (err) {
|
|
587
|
-
|
|
643
|
+
// The local org is the FALLBACK only. A count refusal names the org that
|
|
644
|
+
// owns this project, and the wire's name wins over this one wherever both
|
|
645
|
+
// exist — see resolveCountLimit.
|
|
646
|
+
const localOrg = await localOrgIdentity();
|
|
647
|
+
for (const l of previewFailureLines(err, localOrg))
|
|
588
648
|
console.log(l);
|
|
589
|
-
process.exitCode = 1;
|
|
649
|
+
process.exitCode = err instanceof AuthExpiredError ? EXIT.NOT_AUTHENTICATED : 1;
|
|
590
650
|
return;
|
|
591
651
|
}
|
|
592
652
|
const qr = shouldRenderQr({
|
|
@@ -627,9 +687,24 @@ async function preview() {
|
|
|
627
687
|
if (ending !== null) {
|
|
628
688
|
ui.endedBy(ending, (await activeOrgName()) || "your workspace");
|
|
629
689
|
await previewStop();
|
|
630
|
-
process.exitCode = 1;
|
|
690
|
+
process.exitCode = ending === "auth_expired" ? EXIT.NOT_AUTHENTICATED : 1;
|
|
631
691
|
}
|
|
632
692
|
}
|
|
693
|
+
/** A `--port` or `--dev-command` value the schema refuses, before anything runs. */
|
|
694
|
+
function previewFlagRefusal(port, devCommand) {
|
|
695
|
+
const portMessage = port === undefined
|
|
696
|
+
? undefined
|
|
697
|
+
: validateField("dev.port", /^\d+$/.test(port) ? Number(port) : Number.NaN);
|
|
698
|
+
const commandMessage = devCommand === undefined ? undefined : validateField("dev.command", devCommand);
|
|
699
|
+
const bad = portMessage
|
|
700
|
+
? `--port ${portMessage}`
|
|
701
|
+
: commandMessage
|
|
702
|
+
? `--dev-command ${commandMessage}`
|
|
703
|
+
: undefined;
|
|
704
|
+
if (!bad)
|
|
705
|
+
return undefined;
|
|
706
|
+
return ["", `✗ ${bad}.`, "", " Nothing was started.", ""];
|
|
707
|
+
}
|
|
633
708
|
/**
|
|
634
709
|
* Flow L16 — follow a preview running in another terminal.
|
|
635
710
|
*
|
|
@@ -652,7 +727,7 @@ async function followPreview(info, ui, filterTester, filterViewer) {
|
|
|
652
727
|
console.log("✓ Following this preview");
|
|
653
728
|
}
|
|
654
729
|
console.log("");
|
|
655
|
-
const relay = httpRelayClient(`http://
|
|
730
|
+
const relay = httpRelayClient(`http://127.0.0.1:${info.relayPort}`);
|
|
656
731
|
let since = -1;
|
|
657
732
|
let warnedMissing = false;
|
|
658
733
|
let stop = false;
|
|
@@ -706,6 +781,18 @@ async function activeOrgName() {
|
|
|
706
781
|
(await loadMachineConfig()).activeOrg ??
|
|
707
782
|
"");
|
|
708
783
|
}
|
|
784
|
+
/**
|
|
785
|
+
* The org this machine is pointed at, for a refusal whose body named none.
|
|
786
|
+
*
|
|
787
|
+
* `activeOrg` is a SLUG, so it fills the slug slot and not the name slot. The
|
|
788
|
+
* display name is the API's to give; inventing one from the slug would put a
|
|
789
|
+
* fabricated workspace name on a refusal.
|
|
790
|
+
*/
|
|
791
|
+
async function localOrgIdentity() {
|
|
792
|
+
const slug = await activeOrgName();
|
|
793
|
+
const { consoleUrl } = await resolveAuthUrls();
|
|
794
|
+
return { slug: slug || undefined, consoleUrl };
|
|
795
|
+
}
|
|
709
796
|
/** The binding's name is a HINT for display only; the API is the authority. */
|
|
710
797
|
async function projectDisplayName(cwd) {
|
|
711
798
|
const located = await locateProjectBinding(cwd);
|
|
@@ -744,9 +831,17 @@ async function projectConsoleUrl(cwd, sessionId) {
|
|
|
744
831
|
* creator never has to read a second, and none of them names the edge, the API
|
|
745
832
|
* or an outage — a creator cannot act on that.
|
|
746
833
|
*/
|
|
747
|
-
function previewFailureLines(err) {
|
|
834
|
+
function previewFailureLines(err, local) {
|
|
835
|
+
if (err instanceof AuthExpiredError)
|
|
836
|
+
return authExpiredLines();
|
|
748
837
|
if (err instanceof PreviewRateLimitedError)
|
|
749
838
|
return mintRateLimited(err.retryAfterSec);
|
|
839
|
+
// The ceiling is the second 409 on the mint route, and it is a DIFFERENT
|
|
840
|
+
// creator situation from the conflict below: nothing is running, nothing was
|
|
841
|
+
// taken down, and the organization is full.
|
|
842
|
+
if (err instanceof PreviewLimitError) {
|
|
843
|
+
return previewLimitRefused(resolveCountLimit(err.detail, local));
|
|
844
|
+
}
|
|
750
845
|
if (err instanceof PreviewConflictError)
|
|
751
846
|
return previewConflict();
|
|
752
847
|
if (err instanceof ProjectUnreachableError) {
|
|
@@ -1034,6 +1129,12 @@ async function dx() {
|
|
|
1034
1129
|
: await dxDisableCommand(deps);
|
|
1035
1130
|
return;
|
|
1036
1131
|
}
|
|
1132
|
+
if (options.sinceInvalid !== undefined) {
|
|
1133
|
+
for (const l of sinceUsageLines(options.sinceInvalid))
|
|
1134
|
+
console.error(l);
|
|
1135
|
+
process.exitCode = EXIT.FAILED;
|
|
1136
|
+
return;
|
|
1137
|
+
}
|
|
1037
1138
|
// Read strictly and independently of auth: a truncated config must disable the
|
|
1038
1139
|
// relaxation, not throw through it.
|
|
1039
1140
|
const enablement = await loadEnablement(now);
|
|
@@ -1199,7 +1300,7 @@ async function assets() {
|
|
|
1199
1300
|
* the act does not exist: certificates renew on their own.
|
|
1200
1301
|
*/
|
|
1201
1302
|
async function domain() {
|
|
1202
|
-
const { domainCommand, resolveSub
|
|
1303
|
+
const { domainCommand, resolveSub } = await import("../domain/command.js");
|
|
1203
1304
|
const domainCopy = await import("../domain/copy.js");
|
|
1204
1305
|
const api = await import("../domain/api.js");
|
|
1205
1306
|
const sub = positional(1);
|
|
@@ -1223,11 +1324,7 @@ async function domain() {
|
|
|
1223
1324
|
}
|
|
1224
1325
|
const cwd = projectDir();
|
|
1225
1326
|
const machine = await loadMachineConfig();
|
|
1226
|
-
const config = async () =>
|
|
1227
|
-
const apiUrl = process.env.VINCENTT_API_URL ?? machine.apiUrl ?? DEFAULT_API_URL;
|
|
1228
|
-
const pat = process.env.VINCENTT_PAT ?? machine.pat;
|
|
1229
|
-
return pat ? { apiUrl: apiUrl.replace(/\/$/, ""), pat } : undefined;
|
|
1230
|
-
};
|
|
1327
|
+
const config = async () => machineCredentials(machine);
|
|
1231
1328
|
process.exitCode = await domainCommand({
|
|
1232
1329
|
print: (l) => console.log(l),
|
|
1233
1330
|
config,
|
|
@@ -1445,9 +1542,7 @@ async function announceBehind() {
|
|
|
1445
1542
|
}
|
|
1446
1543
|
if (needsRefresh(loadReleaseCache(), Date.now())) {
|
|
1447
1544
|
refreshReleaseCache({
|
|
1448
|
-
apiUrl:
|
|
1449
|
-
(await loadMachineConfig()).apiUrl ??
|
|
1450
|
-
DEFAULT_API_URL,
|
|
1545
|
+
apiUrl: machineApiUrl(await loadMachineConfig()),
|
|
1451
1546
|
now: () => Date.now(),
|
|
1452
1547
|
});
|
|
1453
1548
|
}
|
|
@@ -1483,6 +1578,20 @@ main()
|
|
|
1483
1578
|
journal(Number(process.exitCode ?? 0));
|
|
1484
1579
|
})
|
|
1485
1580
|
.catch((err) => {
|
|
1581
|
+
// A signed-out or refused token that no verb handled still exits with the
|
|
1582
|
+
// not-signed-in code and the login remedy, so an agent has one code to branch on.
|
|
1583
|
+
if (err instanceof NotSignedInError) {
|
|
1584
|
+
for (const l of NOT_SIGNED_IN_LINES)
|
|
1585
|
+
console.error(l);
|
|
1586
|
+
journal(EXIT.NOT_AUTHENTICATED, err);
|
|
1587
|
+
process.exit(EXIT.NOT_AUTHENTICATED);
|
|
1588
|
+
}
|
|
1589
|
+
if (isAuthRefused(err)) {
|
|
1590
|
+
for (const l of authExpiredLines())
|
|
1591
|
+
console.error(l);
|
|
1592
|
+
journal(EXIT.NOT_AUTHENTICATED, err);
|
|
1593
|
+
process.exit(EXIT.NOT_AUTHENTICATED);
|
|
1594
|
+
}
|
|
1486
1595
|
console.error(`\n✗ ${err instanceof Error ? err.message : String(err)}`);
|
|
1487
1596
|
journal(1, err);
|
|
1488
1597
|
process.exit(1);
|