@vincentt-xr/cli 0.17.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 +170 -84
- 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 +5 -11
- package/dist/preview/api.js +13 -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 +22 -5
- package/dist/publish/api.js +80 -9
- package/dist/publish/command.d.ts +25 -0
- package/dist/publish/command.js +211 -35
- 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 +7 -2
- package/dist/shared/countLimit.js +12 -5
- 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,14 +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
46
|
import { PreviewConflictError, PreviewLimitError, PreviewRateLimitedError, } from "../preview/api.js";
|
|
42
47
|
import { previewLimitRefused, resolveCountLimit, } from "../shared/countLimit.js";
|
|
43
48
|
import { fetchNext, readCursor, writeCursor } from "../lib/feedback.js";
|
|
44
49
|
import * as feedbackCopy from "../lib/feedbackCopy.js";
|
|
45
50
|
import { record as recordInvocation } from "../dx/journal.js";
|
|
46
|
-
import { dxDisableCommand, dxEnableCommand, dxSendCommand, parseDxOptions, runDx, } from "../dx/command.js";
|
|
51
|
+
import { dxDisableCommand, dxEnableCommand, dxSendCommand, parseDxOptions, runDx, sinceUsageLines, } from "../dx/command.js";
|
|
47
52
|
import { httpConsentBackend, runConsentInBrowser } from "../dx/consent.js";
|
|
48
53
|
import { DX_UNATTENDED_SUNSET, loadEnablement } from "../dx/enablement.js";
|
|
49
54
|
import { journalEnabled, journalPath, readJournal } from "../dx/journal.js";
|
|
@@ -53,14 +58,13 @@ import { FETCH_TIMEOUT_MS, isFresh, updateNotice } from "../dx/updateCheck.js";
|
|
|
53
58
|
import { fetchLatest, loadCache, saveCache } from "../dx/updateCheckIo.js";
|
|
54
59
|
import { verbOf } from "../dx/skeleton.js";
|
|
55
60
|
import { outdatedCommand } from "../platform/command.js";
|
|
56
|
-
import { behindFooterLines, needsRefresh, refreshReleaseCache } from "../platform/footer.js";
|
|
61
|
+
import { behindFooterLines, needsRefresh, refreshReleaseCache, } from "../platform/footer.js";
|
|
57
62
|
import { loadReleaseCache } from "../platform/releaseCache.js";
|
|
58
63
|
import { resolveBuiltWith } from "../platform/resolve.js";
|
|
59
64
|
import { createDirCollision, createSourceFlagRefusal, fromFlagConflict, isHelpInvocation, isVersionInvocation, positionalArgs, refusedFlag, } from "./args.js";
|
|
60
65
|
import { parseSourceRef } from "../lib/sourceRef.js";
|
|
61
66
|
import { printCreateSourceFlagRefusal, printFlagConflict, printSourceRefFailure, } from "../lib/sourceRefCopy.js";
|
|
62
|
-
import {
|
|
63
|
-
import { registerProject } from "../lib/projects.js";
|
|
67
|
+
import { registerClonedProject } from "../lib/projects.js";
|
|
64
68
|
import { sanitizeAppName } from "../lib/initCommand.js";
|
|
65
69
|
import { resolveOrgForCommand } from "../lib/lifecycle.js";
|
|
66
70
|
// The console origin serves /cli-auth (the browser target for `vincentt login`). VINCENTT_
|
|
@@ -68,7 +72,6 @@ import { resolveOrgForCommand } from "../lib/lifecycle.js";
|
|
|
68
72
|
// This is the prod console; vincentt.app is the published-PROJECT apex, not the console, so a
|
|
69
73
|
// first-run `vincentt login` (no machine config yet) must default here or it opens a dead page.
|
|
70
74
|
const DEFAULT_CONSOLE_URL = "https://console.vincentt.studio";
|
|
71
|
-
const DEFAULT_API_URL = "https://api.vincentt.studio";
|
|
72
75
|
// ---- arg helpers ------------------------------------------------------------
|
|
73
76
|
const argv = process.argv.slice(2);
|
|
74
77
|
function flag(name) {
|
|
@@ -108,8 +111,12 @@ async function resolveAuthUrls() {
|
|
|
108
111
|
process.env.VINCENTT_EDITOR_URL ??
|
|
109
112
|
machine.consoleUrl ??
|
|
110
113
|
DEFAULT_CONSOLE_URL;
|
|
111
|
-
|
|
112
|
-
|
|
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;
|
|
113
120
|
}
|
|
114
121
|
/** The PKCE loopback + code-exchange login (f1). The raw PAT never rides a redirect query. */
|
|
115
122
|
async function runLoginFlow() {
|
|
@@ -129,13 +136,8 @@ async function login() {
|
|
|
129
136
|
whoAmI: async () => {
|
|
130
137
|
const pat = await currentToken();
|
|
131
138
|
if (!pat)
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
return await httpAuthBackend(apiUrl).me(pat);
|
|
135
|
-
}
|
|
136
|
-
catch {
|
|
137
|
-
return undefined;
|
|
138
|
-
}
|
|
139
|
+
throw new NotSignedInError();
|
|
140
|
+
return httpAuthBackend(apiUrl).me(pat);
|
|
139
141
|
},
|
|
140
142
|
runLogin: runLoginFlow,
|
|
141
143
|
clearToken: async () => {
|
|
@@ -150,13 +152,20 @@ async function login() {
|
|
|
150
152
|
async function currentToken() {
|
|
151
153
|
return process.env.VINCENTT_PAT ?? (await loadMachineConfig()).pat;
|
|
152
154
|
}
|
|
153
|
-
/**
|
|
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
|
+
*/
|
|
154
159
|
async function requireLogin() {
|
|
155
|
-
await ensureLoggedIn({
|
|
160
|
+
const ok = await ensureLoggedIn({
|
|
156
161
|
currentToken,
|
|
157
162
|
runLogin: login,
|
|
158
163
|
notify: (m) => console.error(m),
|
|
164
|
+
interactive: Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY),
|
|
159
165
|
});
|
|
166
|
+
if (!ok)
|
|
167
|
+
process.exitCode = EXIT.NOT_AUTHENTICATED;
|
|
168
|
+
return ok;
|
|
160
169
|
}
|
|
161
170
|
async function logout() {
|
|
162
171
|
const { path: p, hadToken } = await clearMachineToken();
|
|
@@ -171,14 +180,7 @@ async function whoami() {
|
|
|
171
180
|
const machine = await loadMachineConfig();
|
|
172
181
|
process.exitCode = await whoamiCommand({
|
|
173
182
|
print: (l) => console.log(l),
|
|
174
|
-
config: async () =>
|
|
175
|
-
const apiUrl = process.env.VINCENTT_API_URL ?? machine.apiUrl ?? DEFAULT_API_URL;
|
|
176
|
-
const pat = process.env.VINCENTT_PAT ?? machine.pat;
|
|
177
|
-
if (!pat)
|
|
178
|
-
return undefined;
|
|
179
|
-
const cfg = { apiUrl: apiUrl.replace(/\/$/, ""), pat };
|
|
180
|
-
return cfg;
|
|
181
|
-
},
|
|
183
|
+
config: async () => machineCredentials(machine),
|
|
182
184
|
getMe,
|
|
183
185
|
org: {
|
|
184
186
|
flag: flag("--org"),
|
|
@@ -220,35 +222,17 @@ async function init() {
|
|
|
220
222
|
ask: interactive ? stdinAsk() : undefined,
|
|
221
223
|
confirm: interactive ? stdinConfirm() : undefined,
|
|
222
224
|
register: from
|
|
223
|
-
? (rd, o) =>
|
|
224
|
-
|
|
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
|
+
}),
|
|
225
230
|
currentToken,
|
|
226
231
|
runLogin: async () => {
|
|
227
232
|
await login();
|
|
228
233
|
},
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const resolved = await resolveOrgForCommand(cfg, {
|
|
232
|
-
flag: flag("--org"),
|
|
233
|
-
});
|
|
234
|
-
if (!resolved.ok)
|
|
235
|
-
return resolved;
|
|
236
|
-
return {
|
|
237
|
-
ok: true,
|
|
238
|
-
name: resolved.org.name,
|
|
239
|
-
source: orgSource(),
|
|
240
|
-
};
|
|
241
|
-
},
|
|
242
|
-
// registerProject, NOT createCommand: createCommand is now the
|
|
243
|
-
// whole two-half verb, so calling it here would classify and
|
|
244
|
-
// scaffold a second time inside the half that already cloned.
|
|
245
|
-
create: async ({ name, slug }) => registerProject({
|
|
246
|
-
print,
|
|
247
|
-
cwd: rd.targetDir,
|
|
248
|
-
config: () => resolveConfig(rd.targetDir, { warn: print }),
|
|
249
|
-
org: async () => resolveOrgForCommand(await resolveConfig(rd.targetDir, { warn: print }), { flag: flag("--org") }),
|
|
250
|
-
}, { name, slug }),
|
|
251
|
-
}, o)
|
|
234
|
+
orgSource,
|
|
235
|
+
}, rd, o)
|
|
252
236
|
: undefined,
|
|
253
237
|
}, {
|
|
254
238
|
dir,
|
|
@@ -344,6 +328,7 @@ async function create() {
|
|
|
344
328
|
await login();
|
|
345
329
|
},
|
|
346
330
|
orgSource,
|
|
331
|
+
consoleUrl: (await resolveAuthUrls()).consoleUrl,
|
|
347
332
|
onLine: (l) => console.log(l),
|
|
348
333
|
});
|
|
349
334
|
if (result.exitCode !== 0) {
|
|
@@ -357,16 +342,30 @@ async function create() {
|
|
|
357
342
|
}
|
|
358
343
|
}
|
|
359
344
|
async function link() {
|
|
360
|
-
await requireLogin()
|
|
345
|
+
if (!(await requireLogin()))
|
|
346
|
+
return;
|
|
361
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);
|
|
362
351
|
process.exitCode = await linkCommand({
|
|
363
352
|
print: (l) => console.log(l),
|
|
364
353
|
cwd,
|
|
365
354
|
config: () => resolveConfig(cwd, {
|
|
366
355
|
allowCustomApi: has("--allow-custom-api"),
|
|
367
|
-
|
|
356
|
+
// Under --json stdout carries the one JSON object only.
|
|
357
|
+
warn: (l) => (json ? console.error(l) : console.log(l)),
|
|
368
358
|
}),
|
|
369
|
-
}, 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
|
+
});
|
|
370
369
|
}
|
|
371
370
|
async function unlink() {
|
|
372
371
|
const cwd = projectDir();
|
|
@@ -386,13 +385,10 @@ async function unlink() {
|
|
|
386
385
|
* BECAUSE they are in an unbound folder depend on that folder having a binding.
|
|
387
386
|
*/
|
|
388
387
|
async function projects() {
|
|
389
|
-
await requireLogin()
|
|
388
|
+
if (!(await requireLogin()))
|
|
389
|
+
return;
|
|
390
390
|
const machine = await loadMachineConfig();
|
|
391
|
-
const config = async () =>
|
|
392
|
-
const apiUrl = process.env.VINCENTT_API_URL ?? machine.apiUrl ?? DEFAULT_API_URL;
|
|
393
|
-
const pat = process.env.VINCENTT_PAT ?? machine.pat;
|
|
394
|
-
return pat ? { apiUrl: apiUrl.replace(/\/$/, ""), pat } : undefined;
|
|
395
|
-
};
|
|
391
|
+
const config = async () => machineCredentials(machine);
|
|
396
392
|
process.exitCode = await listProjectsCommand({
|
|
397
393
|
print: (l) => console.log(l),
|
|
398
394
|
config,
|
|
@@ -420,7 +416,7 @@ async function outdated() {
|
|
|
420
416
|
print: (l) => console.log(l),
|
|
421
417
|
cwd,
|
|
422
418
|
now: () => Date.now(),
|
|
423
|
-
apiUrl: async () =>
|
|
419
|
+
apiUrl: async () => machineApiUrl(await loadMachineConfig()),
|
|
424
420
|
inProject: async () => (await locateProjectBinding(cwd)) !== undefined,
|
|
425
421
|
projectLine: async () => {
|
|
426
422
|
const name = await projectDisplayName(cwd);
|
|
@@ -447,29 +443,38 @@ async function publish() {
|
|
|
447
443
|
return;
|
|
448
444
|
}
|
|
449
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));
|
|
450
449
|
process.exitCode = await publishCommand({
|
|
451
|
-
print:
|
|
452
|
-
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),
|
|
453
453
|
confirm: stdinConfirm(),
|
|
454
454
|
cwd,
|
|
455
455
|
config: () => resolveConfig(cwd, {
|
|
456
456
|
allowCustomApi: has("--allow-custom-api"),
|
|
457
|
-
warn:
|
|
457
|
+
warn: say,
|
|
458
458
|
}),
|
|
459
459
|
binding: async () => {
|
|
460
460
|
const located = await locateProjectBinding(cwd);
|
|
461
461
|
return located
|
|
462
|
-
? {
|
|
462
|
+
? {
|
|
463
|
+
projectId: located.binding.projectId,
|
|
464
|
+
slug: located.binding.slug,
|
|
465
|
+
dir: located.dir,
|
|
466
|
+
}
|
|
463
467
|
: undefined;
|
|
464
468
|
},
|
|
465
469
|
token: currentToken,
|
|
466
|
-
|
|
470
|
+
// A prompt would land in the JSON document, so --json needs --yes.
|
|
471
|
+
isTTY: !json && Boolean(process.stdout.isTTY) && Boolean(process.stdin.isTTY),
|
|
467
472
|
now: () => Date.now(),
|
|
468
473
|
sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
|
|
469
474
|
orgName: activeOrgName,
|
|
470
475
|
orgSlug: async () => (await activeOrgName()) || undefined,
|
|
471
476
|
consoleUrl: flag("--console") ?? process.env.VINCENTT_CONSOLE_URL,
|
|
472
|
-
}, { distDir: flag("--dist"), note: flag("--note"), yes: has("--yes") });
|
|
477
|
+
}, { distDir: flag("--dist"), note: flag("--note"), yes: has("--yes"), json });
|
|
473
478
|
}
|
|
474
479
|
/** Flow L30 — the six verbs that are console actions, answered rather than rejected. */
|
|
475
480
|
async function consoleOnly(verb) {
|
|
@@ -492,16 +497,61 @@ async function consoleOnly(verb) {
|
|
|
492
497
|
async function preview() {
|
|
493
498
|
const cwd = projectDir();
|
|
494
499
|
if (has("--stop")) {
|
|
495
|
-
const { message } = await previewStopSignal(cwd);
|
|
500
|
+
const { stopped, message } = await previewStopSignal(cwd);
|
|
496
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;
|
|
497
504
|
return;
|
|
498
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;
|
|
531
|
+
return;
|
|
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);
|
|
499
549
|
// The gate is LOCAL and NOTHING IS ATTEMPTED: no spinner, no partial mint, no
|
|
500
550
|
// session left in `pending`. The command stops before it touches the network.
|
|
501
551
|
if (!(await currentToken())) {
|
|
502
|
-
for (const l of notSignedIn())
|
|
552
|
+
for (const l of notSignedIn(devCommand, localUrl))
|
|
503
553
|
console.log(l);
|
|
504
|
-
process.exitCode =
|
|
554
|
+
process.exitCode = EXIT.NOT_AUTHENTICATED;
|
|
505
555
|
return;
|
|
506
556
|
}
|
|
507
557
|
const filterViewer = flag("--viewer");
|
|
@@ -510,10 +560,13 @@ async function preview() {
|
|
|
510
560
|
const ui = new PreviewUi({
|
|
511
561
|
print: (l) => console.log(l),
|
|
512
562
|
now: () => Date.now(),
|
|
513
|
-
targetUrl:
|
|
563
|
+
targetUrl: localUrl,
|
|
514
564
|
color,
|
|
515
565
|
filterTester,
|
|
516
566
|
filterViewer,
|
|
567
|
+
devCommand,
|
|
568
|
+
portSource: settings.devPort.source,
|
|
569
|
+
harnessListed,
|
|
517
570
|
});
|
|
518
571
|
// A preview already running for this folder — in ANOTHER process — is
|
|
519
572
|
// FOLLOWED, not superseded. `vincentt preview` in a folder with a live
|
|
@@ -553,7 +606,9 @@ async function preview() {
|
|
|
553
606
|
let running;
|
|
554
607
|
try {
|
|
555
608
|
const started = await previewStart(cwd, (m) => console.error(`[preview] ${m}`), {
|
|
556
|
-
appPort:
|
|
609
|
+
appPort: devPort,
|
|
610
|
+
devCommand,
|
|
611
|
+
onHiddenPath: (p) => ui.hiddenPath(p),
|
|
557
612
|
appStdio: has("--verbose") ? "inherit" : "ignore",
|
|
558
613
|
onViewer: (label, uaClass) => ui.viewerSeen(label, uaClass),
|
|
559
614
|
onViewerGone: (label) => ui.viewerGone(label),
|
|
@@ -591,7 +646,7 @@ async function preview() {
|
|
|
591
646
|
const localOrg = await localOrgIdentity();
|
|
592
647
|
for (const l of previewFailureLines(err, localOrg))
|
|
593
648
|
console.log(l);
|
|
594
|
-
process.exitCode = 1;
|
|
649
|
+
process.exitCode = err instanceof AuthExpiredError ? EXIT.NOT_AUTHENTICATED : 1;
|
|
595
650
|
return;
|
|
596
651
|
}
|
|
597
652
|
const qr = shouldRenderQr({
|
|
@@ -632,9 +687,24 @@ async function preview() {
|
|
|
632
687
|
if (ending !== null) {
|
|
633
688
|
ui.endedBy(ending, (await activeOrgName()) || "your workspace");
|
|
634
689
|
await previewStop();
|
|
635
|
-
process.exitCode = 1;
|
|
690
|
+
process.exitCode = ending === "auth_expired" ? EXIT.NOT_AUTHENTICATED : 1;
|
|
636
691
|
}
|
|
637
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
|
+
}
|
|
638
708
|
/**
|
|
639
709
|
* Flow L16 — follow a preview running in another terminal.
|
|
640
710
|
*
|
|
@@ -657,7 +727,7 @@ async function followPreview(info, ui, filterTester, filterViewer) {
|
|
|
657
727
|
console.log("✓ Following this preview");
|
|
658
728
|
}
|
|
659
729
|
console.log("");
|
|
660
|
-
const relay = httpRelayClient(`http://
|
|
730
|
+
const relay = httpRelayClient(`http://127.0.0.1:${info.relayPort}`);
|
|
661
731
|
let since = -1;
|
|
662
732
|
let warnedMissing = false;
|
|
663
733
|
let stop = false;
|
|
@@ -762,6 +832,8 @@ async function projectConsoleUrl(cwd, sessionId) {
|
|
|
762
832
|
* or an outage — a creator cannot act on that.
|
|
763
833
|
*/
|
|
764
834
|
function previewFailureLines(err, local) {
|
|
835
|
+
if (err instanceof AuthExpiredError)
|
|
836
|
+
return authExpiredLines();
|
|
765
837
|
if (err instanceof PreviewRateLimitedError)
|
|
766
838
|
return mintRateLimited(err.retryAfterSec);
|
|
767
839
|
// The ceiling is the second 409 on the mint route, and it is a DIFFERENT
|
|
@@ -1057,6 +1129,12 @@ async function dx() {
|
|
|
1057
1129
|
: await dxDisableCommand(deps);
|
|
1058
1130
|
return;
|
|
1059
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
|
+
}
|
|
1060
1138
|
// Read strictly and independently of auth: a truncated config must disable the
|
|
1061
1139
|
// relaxation, not throw through it.
|
|
1062
1140
|
const enablement = await loadEnablement(now);
|
|
@@ -1222,7 +1300,7 @@ async function assets() {
|
|
|
1222
1300
|
* the act does not exist: certificates renew on their own.
|
|
1223
1301
|
*/
|
|
1224
1302
|
async function domain() {
|
|
1225
|
-
const { domainCommand, resolveSub
|
|
1303
|
+
const { domainCommand, resolveSub } = await import("../domain/command.js");
|
|
1226
1304
|
const domainCopy = await import("../domain/copy.js");
|
|
1227
1305
|
const api = await import("../domain/api.js");
|
|
1228
1306
|
const sub = positional(1);
|
|
@@ -1246,11 +1324,7 @@ async function domain() {
|
|
|
1246
1324
|
}
|
|
1247
1325
|
const cwd = projectDir();
|
|
1248
1326
|
const machine = await loadMachineConfig();
|
|
1249
|
-
const config = async () =>
|
|
1250
|
-
const apiUrl = process.env.VINCENTT_API_URL ?? machine.apiUrl ?? DEFAULT_API_URL;
|
|
1251
|
-
const pat = process.env.VINCENTT_PAT ?? machine.pat;
|
|
1252
|
-
return pat ? { apiUrl: apiUrl.replace(/\/$/, ""), pat } : undefined;
|
|
1253
|
-
};
|
|
1327
|
+
const config = async () => machineCredentials(machine);
|
|
1254
1328
|
process.exitCode = await domainCommand({
|
|
1255
1329
|
print: (l) => console.log(l),
|
|
1256
1330
|
config,
|
|
@@ -1468,9 +1542,7 @@ async function announceBehind() {
|
|
|
1468
1542
|
}
|
|
1469
1543
|
if (needsRefresh(loadReleaseCache(), Date.now())) {
|
|
1470
1544
|
refreshReleaseCache({
|
|
1471
|
-
apiUrl:
|
|
1472
|
-
(await loadMachineConfig()).apiUrl ??
|
|
1473
|
-
DEFAULT_API_URL,
|
|
1545
|
+
apiUrl: machineApiUrl(await loadMachineConfig()),
|
|
1474
1546
|
now: () => Date.now(),
|
|
1475
1547
|
});
|
|
1476
1548
|
}
|
|
@@ -1506,6 +1578,20 @@ main()
|
|
|
1506
1578
|
journal(Number(process.exitCode ?? 0));
|
|
1507
1579
|
})
|
|
1508
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
|
+
}
|
|
1509
1595
|
console.error(`\n✗ ${err instanceof Error ? err.message : String(err)}`);
|
|
1510
1596
|
journal(1, err);
|
|
1511
1597
|
process.exit(1);
|