@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/dist/dx/consent.js
CHANGED
|
@@ -23,6 +23,7 @@ import { stateMatches } from "../login/pkce.js";
|
|
|
23
23
|
import { getFreePort } from "../preview/net.js";
|
|
24
24
|
import { authFailureMessage } from "../shared/authError.js";
|
|
25
25
|
import { clientHeaders } from "../shared/clientHeader.js";
|
|
26
|
+
import { apiFetch } from "../shared/http.js";
|
|
26
27
|
import { readAccountConsent } from "./enablement.js";
|
|
27
28
|
async function safeText(res) {
|
|
28
29
|
try {
|
|
@@ -50,7 +51,7 @@ export function httpConsentBackend() {
|
|
|
50
51
|
const strip = (u) => u.replace(/\/$/, "");
|
|
51
52
|
return {
|
|
52
53
|
async read(apiUrl, pat, now) {
|
|
53
|
-
const res = await
|
|
54
|
+
const res = await apiFetch(`${strip(apiUrl)}/me`, {
|
|
54
55
|
headers: { ...clientHeaders(), authorization: `Bearer ${pat}` },
|
|
55
56
|
});
|
|
56
57
|
if (!res.ok) {
|
|
@@ -59,7 +60,7 @@ export function httpConsentBackend() {
|
|
|
59
60
|
return readAccountConsent(unwrapMe(await res.json()), now);
|
|
60
61
|
},
|
|
61
62
|
async grant(apiUrl, pat, now) {
|
|
62
|
-
const res = await
|
|
63
|
+
const res = await apiFetch(`${strip(apiUrl)}/me/dx-consent`, {
|
|
63
64
|
method: "POST",
|
|
64
65
|
headers: { ...clientHeaders(), authorization: `Bearer ${pat}` },
|
|
65
66
|
});
|
|
@@ -69,7 +70,7 @@ export function httpConsentBackend() {
|
|
|
69
70
|
return readAccountConsent(unwrapMe(await res.json()), now);
|
|
70
71
|
},
|
|
71
72
|
async revoke(apiUrl, pat) {
|
|
72
|
-
const res = await
|
|
73
|
+
const res = await apiFetch(`${strip(apiUrl)}/me/dx-consent`, {
|
|
73
74
|
method: "DELETE",
|
|
74
75
|
headers: { ...clientHeaders(), authorization: `Bearer ${pat}` },
|
|
75
76
|
});
|
|
@@ -114,7 +115,7 @@ export function parseConsentCallback(params, expectedState, stateMatches) {
|
|
|
114
115
|
return {
|
|
115
116
|
ok: false,
|
|
116
117
|
reason: "state_mismatch",
|
|
117
|
-
message: "State mismatch
|
|
118
|
+
message: "State mismatch. Ignoring an unexpected callback.",
|
|
118
119
|
};
|
|
119
120
|
}
|
|
120
121
|
const result = params.get("result");
|
|
@@ -165,7 +166,7 @@ export async function runConsentInBrowser(opts) {
|
|
|
165
166
|
value: result.decision,
|
|
166
167
|
page: {
|
|
167
168
|
status: 200,
|
|
168
|
-
html: browserPage(granted, granted ? "Reports allowed" : "Not enabled", "Return to your terminal
|
|
169
|
+
html: browserPage(granted, granted ? "Reports allowed" : "Not enabled", "Return to your terminal. You can close this tab."),
|
|
169
170
|
},
|
|
170
171
|
};
|
|
171
172
|
}
|
package/dist/dx/journal.d.ts
CHANGED
|
@@ -47,5 +47,20 @@ export declare function buildEntry(input: RecordInput): JournalEntry;
|
|
|
47
47
|
* turned into a failed command.
|
|
48
48
|
*/
|
|
49
49
|
export declare function record(input: RecordInput): void;
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
export interface JournalWindow {
|
|
51
|
+
/** Epoch ms. Entries stamped before this are dropped. */
|
|
52
|
+
sinceMs: number;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Keep only entries stamped at or after the cutoff. An entry whose timestamp
|
|
56
|
+
* does not parse is dropped: it cannot be placed inside any window.
|
|
57
|
+
*/
|
|
58
|
+
export declare function withinWindow(entries: JournalEntry[], window: JournalWindow): JournalEntry[];
|
|
59
|
+
/**
|
|
60
|
+
* Read the journal oldest-first. Malformed lines are skipped, not fatal.
|
|
61
|
+
*
|
|
62
|
+
* With a window, only entries inside it are returned. Without one, the whole
|
|
63
|
+
* file is: the size trim is the only other bound, so how far back an unwindowed
|
|
64
|
+
* read reaches depends on how much the CLI was used, not on when.
|
|
65
|
+
*/
|
|
66
|
+
export declare function readJournal(window?: JournalWindow): JournalEntry[];
|
package/dist/dx/journal.js
CHANGED
|
@@ -105,8 +105,24 @@ export function record(input) {
|
|
|
105
105
|
// Instrumentation must not break the tool it measures.
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
|
-
/**
|
|
109
|
-
|
|
108
|
+
/**
|
|
109
|
+
* Keep only entries stamped at or after the cutoff. An entry whose timestamp
|
|
110
|
+
* does not parse is dropped: it cannot be placed inside any window.
|
|
111
|
+
*/
|
|
112
|
+
export function withinWindow(entries, window) {
|
|
113
|
+
return entries.filter((e) => {
|
|
114
|
+
const t = Date.parse(e.ts);
|
|
115
|
+
return Number.isFinite(t) && t >= window.sinceMs;
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Read the journal oldest-first. Malformed lines are skipped, not fatal.
|
|
120
|
+
*
|
|
121
|
+
* With a window, only entries inside it are returned. Without one, the whole
|
|
122
|
+
* file is: the size trim is the only other bound, so how far back an unwindowed
|
|
123
|
+
* read reaches depends on how much the CLI was used, not on when.
|
|
124
|
+
*/
|
|
125
|
+
export function readJournal(window) {
|
|
110
126
|
let raw;
|
|
111
127
|
try {
|
|
112
128
|
raw = fs.readFileSync(journalPath(), "utf8");
|
|
@@ -125,5 +141,5 @@ export function readJournal() {
|
|
|
125
141
|
// A torn line from a killed process is not a reason to lose the rest.
|
|
126
142
|
}
|
|
127
143
|
}
|
|
128
|
-
return out;
|
|
144
|
+
return window === undefined ? out : withinWindow(out, window);
|
|
129
145
|
}
|
package/dist/dx/report.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { enablementLine } from "./enablement.js";
|
|
2
2
|
import { DXSendError, platformTag } from "./send.js";
|
|
3
3
|
import { resolveBuiltWith } from "../platform/resolve.js";
|
|
4
|
+
import { EXIT } from "../lib/exitCodes.js";
|
|
4
5
|
import { renderPayloadWithVersionSummary, summarizeBuiltWith, versionDisclosureLines, } from "../platform/versionSummary.js";
|
|
5
6
|
/** Matches the server's MaxDXNote. Checked here so a refusal costs no round trip. */
|
|
6
7
|
export const MAX_NOTE = 2000;
|
|
@@ -93,7 +94,7 @@ export function renderObservation(report) {
|
|
|
93
94
|
"",
|
|
94
95
|
...renderPayloadWithVersionSummary(report, report.builtWith).map((l) => ` ${l}`),
|
|
95
96
|
"",
|
|
96
|
-
" YOUR NOTE IS SENT EXACTLY AS WRITTEN
|
|
97
|
+
" YOUR NOTE IS SENT EXACTLY AS WRITTEN. Check it for paths, URLs,",
|
|
97
98
|
" tokens, or customer names before sending.",
|
|
98
99
|
// Entry 2: it explains the one anomalous line above, so it sits directly
|
|
99
100
|
// under the warning where the reader's eye lands next — above the context
|
|
@@ -153,7 +154,7 @@ export async function reportCommand(deps, options) {
|
|
|
153
154
|
}
|
|
154
155
|
if (auth === undefined) {
|
|
155
156
|
deps.print("Not signed in. Run `vincentt login` to send a report.");
|
|
156
|
-
return
|
|
157
|
+
return EXIT.NOT_AUTHENTICATED;
|
|
157
158
|
}
|
|
158
159
|
const context = options.noContext ? undefined : lastContext(deps.entries());
|
|
159
160
|
// RESOLVED AFTER THE GATE AND IMMEDIATELY BEFORE THE BUILD.
|
|
@@ -199,7 +200,7 @@ export async function reportCommand(deps, options) {
|
|
|
199
200
|
try {
|
|
200
201
|
const res = await deps.post(auth.apiUrl, auth.pat, report);
|
|
201
202
|
deps.print(`Sent. Reference ${res.ref}.`);
|
|
202
|
-
deps.print("Thank you
|
|
203
|
+
deps.print("Thank you. This is the kind of thing we cannot detect on our own.");
|
|
203
204
|
return 0;
|
|
204
205
|
}
|
|
205
206
|
catch (err) {
|
package/dist/dx/reportIo.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { DXSendError } from "./send.js";
|
|
2
|
-
|
|
2
|
+
import { AUTH_EXPIRED_MESSAGE } from "../shared/authError.js";
|
|
3
|
+
import { apiFetch } from "../shared/http.js";
|
|
4
|
+
export async function postObservationReport(apiUrl, pat, report, fetchImpl = apiFetch) {
|
|
3
5
|
const res = await fetchImpl(`${apiUrl}/dx-reports`, {
|
|
4
6
|
method: "POST",
|
|
5
7
|
headers: { "content-type": "application/json", authorization: `Bearer ${pat}` },
|
|
@@ -14,14 +16,13 @@ export async function postObservationReport(apiUrl, pat, report, fetchImpl = fet
|
|
|
14
16
|
catch {
|
|
15
17
|
// A non-JSON body is not more informative than the status.
|
|
16
18
|
}
|
|
17
|
-
if (res.status === 401)
|
|
18
|
-
throw new DXSendError(
|
|
19
|
-
}
|
|
19
|
+
if (res.status === 401)
|
|
20
|
+
throw new DXSendError(AUTH_EXPIRED_MESSAGE, 401);
|
|
20
21
|
if (res.status === 429) {
|
|
21
22
|
// Distinguished from the generic path because it is the one refusal the
|
|
22
23
|
// creator can act on by simply waiting, and a bare "not accepted" would
|
|
23
24
|
// read as their report being rejected on its merits.
|
|
24
|
-
throw new DXSendError("Too many reports from this account just now
|
|
25
|
+
throw new DXSendError("Too many reports from this account just now. Try again shortly.", 429);
|
|
25
26
|
}
|
|
26
27
|
throw new DXSendError(detail !== "" ? detail : `The report was not accepted (${res.status}).`, res.status);
|
|
27
28
|
}
|
package/dist/dx/send.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { AUTH_EXPIRED_MESSAGE } from "../shared/authError.js";
|
|
2
|
+
import { apiFetch } from "../shared/http.js";
|
|
1
3
|
import { renderPayloadWithVersionSummary, summarizeBuiltWith, versionDisclosureLines, } from "../platform/versionSummary.js";
|
|
2
4
|
/**
|
|
3
5
|
* The server bounds a report at 50 findings and refuses the whole thing past
|
|
@@ -79,7 +81,7 @@ export function renderPayload(report) {
|
|
|
79
81
|
// creator to consent on a false claim — the one thing this screen exists to
|
|
80
82
|
// prevent. So the sentence changes when a note is present.
|
|
81
83
|
if (hasNote) {
|
|
82
|
-
lines.push(" The generated fields carry no paths, flag values, or error messages.", " YOUR NOTE IS SENT EXACTLY AS WRITTEN
|
|
84
|
+
lines.push(" The generated fields carry no paths, flag values, or error messages.", " YOUR NOTE IS SENT EXACTLY AS WRITTEN. Check it for paths, URLs,", " tokens, or customer names before sending.");
|
|
83
85
|
}
|
|
84
86
|
else {
|
|
85
87
|
lines.push(" No paths, no flag values, no error messages, and nothing about which", " project this was.");
|
|
@@ -102,7 +104,7 @@ export class DXSendError extends Error {
|
|
|
102
104
|
/**
|
|
103
105
|
* POST the report. The caller has already shown the payload and had it accepted.
|
|
104
106
|
*/
|
|
105
|
-
export async function postReport(apiUrl, pat, report, fetchImpl =
|
|
107
|
+
export async function postReport(apiUrl, pat, report, fetchImpl = apiFetch) {
|
|
106
108
|
const res = await fetchImpl(`${apiUrl}/dx-reports`, {
|
|
107
109
|
method: "POST",
|
|
108
110
|
headers: { "content-type": "application/json", authorization: `Bearer ${pat}` },
|
|
@@ -117,9 +119,8 @@ export async function postReport(apiUrl, pat, report, fetchImpl = fetch) {
|
|
|
117
119
|
catch {
|
|
118
120
|
// A non-JSON body is not more informative than the status.
|
|
119
121
|
}
|
|
120
|
-
if (res.status === 401)
|
|
121
|
-
throw new DXSendError(
|
|
122
|
-
}
|
|
122
|
+
if (res.status === 401)
|
|
123
|
+
throw new DXSendError(AUTH_EXPIRED_MESSAGE, 401);
|
|
123
124
|
// A 400 here means the payload this CLI built failed the server's own
|
|
124
125
|
// grammar. Surfaced verbatim rather than softened: it is a bug in this CLI,
|
|
125
126
|
// and the creator is the one who can report it.
|
package/dist/lib/backend.d.ts
CHANGED
|
@@ -56,22 +56,6 @@ export declare function createProject(cfg: ResolvedConfig, orgSlug: string, name
|
|
|
56
56
|
export declare function listProjects(cfg: ResolvedConfig, orgSlug: string): Promise<ProjectView[]>;
|
|
57
57
|
/** Read a project by id. Backs `link`; the org is derived server-side from the row. */
|
|
58
58
|
export declare function getProject(cfg: ResolvedConfig, projectId: string): Promise<ProjectView>;
|
|
59
|
-
export interface MintedTunnel {
|
|
60
|
-
tunnelId: string;
|
|
61
|
-
/** The public host the phone opens: <slug>-<token>.<previewApex>. */
|
|
62
|
-
hostname: string;
|
|
63
|
-
/** cloudflared run token — `cloudflared tunnel run --token <this>`. */
|
|
64
|
-
runToken: string;
|
|
65
|
-
/** DNS record id, passed back on reap. */
|
|
66
|
-
dnsRecordId: string;
|
|
67
|
-
}
|
|
68
|
-
/** Mint a per-session dev tunnel for `localPort` (the backend holds CF creds). */
|
|
69
|
-
export declare function mintTunnel(cfg: ResolvedConfig, projectId: string, localPort: number): Promise<MintedTunnel>;
|
|
70
|
-
/** Tear down a session tunnel (DNS route + tunnel). Best-effort; never throws. */
|
|
71
|
-
export declare function reapTunnel(cfg: ResolvedConfig, projectId: string, ref: {
|
|
72
|
-
tunnelId: string;
|
|
73
|
-
dnsRecordId?: string;
|
|
74
|
-
}): Promise<void>;
|
|
75
59
|
/** Recursively list files under `dir`, returning dir-relative POSIX paths. */
|
|
76
60
|
export declare function walkDir(dir: string, base?: string): Promise<string[]>;
|
|
77
61
|
export interface MeMembership {
|
package/dist/lib/backend.js
CHANGED
|
@@ -8,6 +8,7 @@ import { execFile } from "node:child_process";
|
|
|
8
8
|
import { promisify } from "node:util";
|
|
9
9
|
import { clientHeaders } from "../shared/clientHeader.js";
|
|
10
10
|
import { authFailureMessage } from "../shared/authError.js";
|
|
11
|
+
import { apiFetch } from "../shared/http.js";
|
|
11
12
|
const execFileAsync = promisify(execFile);
|
|
12
13
|
export class BackendError extends Error {
|
|
13
14
|
status;
|
|
@@ -18,7 +19,7 @@ export class BackendError extends Error {
|
|
|
18
19
|
}
|
|
19
20
|
}
|
|
20
21
|
async function authFetch(cfg, pathname, init = {}) {
|
|
21
|
-
return
|
|
22
|
+
return apiFetch(`${cfg.apiUrl}${pathname}`, {
|
|
22
23
|
...init,
|
|
23
24
|
headers: {
|
|
24
25
|
...(init.headers ?? {}),
|
|
@@ -159,31 +160,6 @@ function parseProjectView(raw, what) {
|
|
|
159
160
|
publishState: str("publishState") ?? "never",
|
|
160
161
|
};
|
|
161
162
|
}
|
|
162
|
-
/** Mint a per-session dev tunnel for `localPort` (the backend holds CF creds). */
|
|
163
|
-
export async function mintTunnel(cfg, projectId, localPort) {
|
|
164
|
-
const res = await authFetch(cfg, `/projects/${projectId}/tunnel`, {
|
|
165
|
-
method: "POST",
|
|
166
|
-
headers: { "content-type": "application/json" },
|
|
167
|
-
body: JSON.stringify({ port: localPort }),
|
|
168
|
-
});
|
|
169
|
-
if (!res.ok) {
|
|
170
|
-
throw new BackendError(authFailureMessage("tunnel mint failed", res.status, await safeText(res)), res.status);
|
|
171
|
-
}
|
|
172
|
-
return (await res.json());
|
|
173
|
-
}
|
|
174
|
-
/** Tear down a session tunnel (DNS route + tunnel). Best-effort; never throws. */
|
|
175
|
-
export async function reapTunnel(cfg, projectId, ref) {
|
|
176
|
-
try {
|
|
177
|
-
await authFetch(cfg, `/projects/${projectId}/tunnel/reap`, {
|
|
178
|
-
method: "POST",
|
|
179
|
-
headers: { "content-type": "application/json" },
|
|
180
|
-
body: JSON.stringify(ref),
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
catch {
|
|
184
|
-
// Reap is a cleanup courtesy — a failure must never crash the preview teardown.
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
163
|
/** Recursively list files under `dir`, returning dir-relative POSIX paths. */
|
|
188
164
|
export async function walkDir(dir, base = dir) {
|
|
189
165
|
const out = [];
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { SettingsField } from "./settings.js";
|
|
2
|
+
export declare const DETECT_MAX_BYTES: number;
|
|
3
|
+
/** `notFile`: it exists but is not a regular file (a FIFO would block the read). */
|
|
4
|
+
export type ReadResult = {
|
|
5
|
+
text: string;
|
|
6
|
+
} | {
|
|
7
|
+
missing: true;
|
|
8
|
+
} | {
|
|
9
|
+
tooLarge: true;
|
|
10
|
+
} | {
|
|
11
|
+
notFile: true;
|
|
12
|
+
};
|
|
13
|
+
export interface DetectProbes {
|
|
14
|
+
/** Read a repo-relative file, at most DETECT_MAX_BYTES. */
|
|
15
|
+
readText: (rel: string) => Promise<ReadResult>;
|
|
16
|
+
exists: (rel: string) => Promise<boolean>;
|
|
17
|
+
}
|
|
18
|
+
export interface DetectedField {
|
|
19
|
+
value: string | number;
|
|
20
|
+
evidence: string;
|
|
21
|
+
}
|
|
22
|
+
export interface DetectResult {
|
|
23
|
+
/** Exactly the files that were read or looked for by name, in report order. */
|
|
24
|
+
read: string[];
|
|
25
|
+
fields: Partial<Record<SettingsField, DetectedField>>;
|
|
26
|
+
missing: {
|
|
27
|
+
field: SettingsField;
|
|
28
|
+
reason: string;
|
|
29
|
+
}[];
|
|
30
|
+
pm: PackageManager;
|
|
31
|
+
}
|
|
32
|
+
export type PackageManager = "npm" | "pnpm" | "yarn" | "bun";
|
|
33
|
+
/** One simple command of a script body: its words, wrappers and env assignments removed. */
|
|
34
|
+
interface Command {
|
|
35
|
+
words: string[];
|
|
36
|
+
/** The command as written, for evidence. */
|
|
37
|
+
text: string;
|
|
38
|
+
}
|
|
39
|
+
/** Split a script body on `&&`, `||`, `;`, `|` and `&`, honouring quotes. Linear. */
|
|
40
|
+
export declare function splitScript(body: string): Command[];
|
|
41
|
+
type Tok = {
|
|
42
|
+
t: "ident";
|
|
43
|
+
v: string;
|
|
44
|
+
} | {
|
|
45
|
+
t: "str";
|
|
46
|
+
v: string;
|
|
47
|
+
} | {
|
|
48
|
+
t: "num";
|
|
49
|
+
v: string;
|
|
50
|
+
} | {
|
|
51
|
+
t: "tpl";
|
|
52
|
+
v: string;
|
|
53
|
+
interpolated: boolean;
|
|
54
|
+
} | {
|
|
55
|
+
t: "p";
|
|
56
|
+
v: string;
|
|
57
|
+
} | {
|
|
58
|
+
t: "re";
|
|
59
|
+
};
|
|
60
|
+
/** A JS/TS tokenizer just good enough to find object keys. One pass, no backtracking. */
|
|
61
|
+
export declare function tokenize(src: string): Tok[];
|
|
62
|
+
export type ConfigValue = {
|
|
63
|
+
kind: "object";
|
|
64
|
+
spread: boolean;
|
|
65
|
+
} | {
|
|
66
|
+
kind: "number";
|
|
67
|
+
value: number;
|
|
68
|
+
} | {
|
|
69
|
+
kind: "string";
|
|
70
|
+
value: string;
|
|
71
|
+
} | {
|
|
72
|
+
kind: "dirname";
|
|
73
|
+
fn: "resolve" | "join";
|
|
74
|
+
args: string[];
|
|
75
|
+
} | {
|
|
76
|
+
kind: "expr";
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Every object key whose objects nest DIRECTLY from an anonymous root, keyed by
|
|
80
|
+
* its dotted path (`devServer.port`, `build.outDir`). A key inside a function
|
|
81
|
+
* body, an array, a call argument that is not the config itself, or any value
|
|
82
|
+
* expression is not recorded: its value is judged as "an expression", which
|
|
83
|
+
* detection reports as not found.
|
|
84
|
+
*/
|
|
85
|
+
export declare function scanConfigKeys(src: string): Map<string, ConfigValue[]>;
|
|
86
|
+
export declare function detect(probes: DetectProbes): Promise<DetectResult>;
|
|
87
|
+
export {};
|