@extenshi/cli 0.12.0 → 0.14.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/dist/cli.d.ts +10 -4
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +69 -6
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +16 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +20 -0
- package/dist/config.js.map +1 -1
- package/dist/cws-policies.d.ts +65 -0
- package/dist/cws-policies.d.ts.map +1 -0
- package/dist/cws-policies.js +113 -0
- package/dist/cws-policies.js.map +1 -0
- package/dist/listing-checks.d.ts +91 -0
- package/dist/listing-checks.d.ts.map +1 -0
- package/dist/listing-checks.js +547 -0
- package/dist/listing-checks.js.map +1 -0
- package/dist/listing.d.ts +91 -0
- package/dist/listing.d.ts.map +1 -0
- package/dist/listing.js +246 -0
- package/dist/listing.js.map +1 -0
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +7 -1
- package/dist/render.js.map +1 -1
- package/dist/review-checks.d.ts +24 -1
- package/dist/review-checks.d.ts.map +1 -1
- package/dist/review-checks.js +23 -3
- package/dist/review-checks.js.map +1 -1
- package/dist/review-render.js +4 -0
- package/dist/review-render.js.map +1 -1
- package/dist/review-risk.d.ts +17 -4
- package/dist/review-risk.d.ts.map +1 -1
- package/dist/review-risk.js +76 -5
- package/dist/review-risk.js.map +1 -1
- package/dist/telemetry-consent.d.ts +62 -0
- package/dist/telemetry-consent.d.ts.map +1 -0
- package/dist/telemetry-consent.js +144 -0
- package/dist/telemetry-consent.js.map +1 -0
- package/package.json +1 -1
package/dist/review-render.js
CHANGED
|
@@ -71,6 +71,10 @@ function renderFinding(f) {
|
|
|
71
71
|
console.log(chalk.dim(` ${chalk.cyan('found:')} ${f.evidence}`));
|
|
72
72
|
for (const line of wrap(f.recommendation, 84))
|
|
73
73
|
console.log(` ${chalk.green('→')} ${chalk.green(line)}`);
|
|
74
|
+
// Naming the policy is what lets the developer check the call and argue it
|
|
75
|
+
// with a reviewer; the citation below is the page, this is the rule.
|
|
76
|
+
if (f.policy)
|
|
77
|
+
console.log(chalk.dim(` ${chalk.cyan('policy:')} ${f.policy.title} (${f.policy.id})`));
|
|
74
78
|
console.log(chalk.dim(` ${f.citation}`));
|
|
75
79
|
console.log();
|
|
76
80
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"review-render.js","sourceRoot":"","sources":["../src/review-render.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"review-render.js","sourceRoot":"","sources":["../src/review-render.ts"],"names":[],"mappings":"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"}
|
package/dist/review-risk.d.ts
CHANGED
|
@@ -8,10 +8,17 @@
|
|
|
8
8
|
* → [optional] fetch last-published manifest for --extension-id (review-catalog)
|
|
9
9
|
* → run checks (review-checks) → render (review-render) or --json
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
* The build never leaves the machine — every check reads the artifact locally
|
|
12
|
+
* and no API key is required. Two things do go out, neither of them the build:
|
|
13
|
+
* the manifest diff (only with --extension-id, and it sends the id), and the
|
|
14
|
+
* anonymous telemetry every command emits via runCommand (cli.ts), which the
|
|
15
|
+
* first interactive run asks about and which reports nothing before an answer.
|
|
16
|
+
* "Fully offline" is therefore the wrong claim to make in user-facing copy —
|
|
17
|
+
* see docs/src/app/developers/cli/page.mdx "What leaves your machine" and the
|
|
18
|
+
* /cli FAQ, which are worded to match.
|
|
19
|
+
*
|
|
20
|
+
* Exit code is 1 only when something WILL BE REJECTED, so it gates a publish
|
|
21
|
+
* step in CI without blocking on slow-review / attrition advisories.
|
|
15
22
|
*/
|
|
16
23
|
export interface ReviewRiskOptions {
|
|
17
24
|
artifactPath: string;
|
|
@@ -23,6 +30,12 @@ export interface ReviewRiskOptions {
|
|
|
23
30
|
apiKey?: string;
|
|
24
31
|
/** Store to diff the manifest against (default CHROME — this tool is CWS-centric). */
|
|
25
32
|
store?: 'CHROME' | 'FIREFOX' | 'EDGE';
|
|
33
|
+
/**
|
|
34
|
+
* Path to a listing draft (`listing.json`). Optional: the listing rules
|
|
35
|
+
* already run against the package's own name/description in every locale.
|
|
36
|
+
* This is what adds the FULL description, which lives only in the dashboard.
|
|
37
|
+
*/
|
|
38
|
+
listingPath?: string;
|
|
26
39
|
outputJson: boolean;
|
|
27
40
|
}
|
|
28
41
|
export declare function runReviewRisk(opts: ReviewRiskOptions): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"review-risk.d.ts","sourceRoot":"","sources":["../src/review-risk.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"review-risk.d.ts","sourceRoot":"","sources":["../src/review-risk.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAYH,MAAM,WAAW,iBAAiB;IACjC,YAAY,EAAE,MAAM,CAAA;IACpB,6EAA6E;IAC7E,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,4EAA4E;IAC5E,MAAM,EAAE,MAAM,CAAA;IACd,8EAA8E;IAC9E,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,sFAAsF;IACtF,KAAK,CAAC,EAAE,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAA;IACrC;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,UAAU,EAAE,OAAO,CAAA;CACnB;AAuDD,wBAAsB,aAAa,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CA6E1E"}
|
package/dist/review-risk.js
CHANGED
|
@@ -8,18 +8,77 @@
|
|
|
8
8
|
* → [optional] fetch last-published manifest for --extension-id (review-catalog)
|
|
9
9
|
* → run checks (review-checks) → render (review-render) or --json
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
* The build never leaves the machine — every check reads the artifact locally
|
|
12
|
+
* and no API key is required. Two things do go out, neither of them the build:
|
|
13
|
+
* the manifest diff (only with --extension-id, and it sends the id), and the
|
|
14
|
+
* anonymous telemetry every command emits via runCommand (cli.ts), which the
|
|
15
|
+
* first interactive run asks about and which reports nothing before an answer.
|
|
16
|
+
* "Fully offline" is therefore the wrong claim to make in user-facing copy —
|
|
17
|
+
* see docs/src/app/developers/cli/page.mdx "What leaves your machine" and the
|
|
18
|
+
* /cli FAQ, which are worded to match.
|
|
19
|
+
*
|
|
20
|
+
* Exit code is 1 only when something WILL BE REJECTED, so it gates a publish
|
|
21
|
+
* step in CI without blocking on slow-review / attrition advisories.
|
|
15
22
|
*/
|
|
16
23
|
import fs from 'node:fs';
|
|
17
24
|
import ora from 'ora';
|
|
25
|
+
import { listingFromArtifact, mergeListings, parseListingFile } from './listing.js';
|
|
18
26
|
import { loadArtifact } from './review-archive.js';
|
|
19
27
|
import { fetchPriorManifest } from './review-catalog.js';
|
|
20
28
|
import { runChecks, summarize } from './review-checks.js';
|
|
21
29
|
import { renderReviewReport } from './review-render.js';
|
|
22
30
|
const MAX_SIZE = 50 * 1024 * 1024; // 50 MB, matches `scan`
|
|
31
|
+
/**
|
|
32
|
+
* Assemble the listing to check: the package's localized name/description,
|
|
33
|
+
* overlaid with the draft file when one was passed.
|
|
34
|
+
*
|
|
35
|
+
* A bad `--listing` file THROWS rather than resolving to "no listing": the user
|
|
36
|
+
* pointed at it explicitly, so silently checking nothing would look like the
|
|
37
|
+
* listing passed. A package with no listing text at all resolves to null and
|
|
38
|
+
* the listing rules simply do not run.
|
|
39
|
+
*/
|
|
40
|
+
function assembleListing(manifest, files, listingPath) {
|
|
41
|
+
const fromArtifact = listingFromArtifact(manifest, files);
|
|
42
|
+
if (!listingPath)
|
|
43
|
+
return fromArtifact;
|
|
44
|
+
if (!fs.existsSync(listingPath)) {
|
|
45
|
+
throw new Error(`Listing file not found: ${listingPath}`);
|
|
46
|
+
}
|
|
47
|
+
let parsed;
|
|
48
|
+
try {
|
|
49
|
+
parsed = JSON.parse(fs.readFileSync(listingPath, 'utf8'));
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
throw new Error(`Listing file is not valid JSON: ${err.message}`);
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
return mergeListings(fromArtifact, parseListingFile(parsed));
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
throw new Error(`${listingPath}: ${err.message}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* What the listing rules covered, stated plainly.
|
|
63
|
+
*
|
|
64
|
+
* A clean listing section means nothing unless the reader knows WHICH text was
|
|
65
|
+
* read: the package carries the name and short description in every locale, but
|
|
66
|
+
* never the full dashboard description, and that is where keyword spam
|
|
67
|
+
* overwhelmingly lives. Saying so is the difference between "your listing is
|
|
68
|
+
* fine" and "the half of your listing we could see is fine".
|
|
69
|
+
*/
|
|
70
|
+
function listingNotes(listing, listingPath) {
|
|
71
|
+
if (!listing) {
|
|
72
|
+
return ['no listing text found in the package — pass --listing <file> to check name and descriptions'];
|
|
73
|
+
}
|
|
74
|
+
const notes = [
|
|
75
|
+
`listing checked in ${listing.locales.length} locale(s): ${listing.locales.map((l) => l.locale).join(', ')}`,
|
|
76
|
+
];
|
|
77
|
+
if (!listingPath && !listing.locales.some((l) => l.description)) {
|
|
78
|
+
notes.push('the full store description is not in the package — pass --listing <file> with it to check for keyword spam');
|
|
79
|
+
}
|
|
80
|
+
return notes;
|
|
81
|
+
}
|
|
23
82
|
export async function runReviewRisk(opts) {
|
|
24
83
|
if (!fs.existsSync(opts.artifactPath)) {
|
|
25
84
|
throw new Error(`Artifact file not found: ${opts.artifactPath}`);
|
|
@@ -57,7 +116,8 @@ export async function runReviewRisk(opts) {
|
|
|
57
116
|
diffNote =
|
|
58
117
|
'pass --extension-id <numeric catalog id> to diff against your last published manifest (auto-disable check)';
|
|
59
118
|
}
|
|
60
|
-
const
|
|
119
|
+
const listing = assembleListing(manifest, files, opts.listingPath);
|
|
120
|
+
const summary = summarize(runChecks({ manifest, files, priorManifest, listing }));
|
|
61
121
|
if (opts.outputJson) {
|
|
62
122
|
process.stdout.write(`${JSON.stringify({
|
|
63
123
|
artifact: opts.artifactPath,
|
|
@@ -67,6 +127,15 @@ export async function runReviewRisk(opts) {
|
|
|
67
127
|
counts: summary.counts,
|
|
68
128
|
blocking: summary.blocking,
|
|
69
129
|
findings: summary.findings,
|
|
130
|
+
// What the listing rules actually saw, so a --json consumer can tell
|
|
131
|
+
// "no listing findings" from "no listing was checked".
|
|
132
|
+
listing: listing
|
|
133
|
+
? {
|
|
134
|
+
source: listing.source,
|
|
135
|
+
defaultLocale: listing.defaultLocale,
|
|
136
|
+
locales: listing.locales.map((l) => l.locale),
|
|
137
|
+
}
|
|
138
|
+
: null,
|
|
70
139
|
diffSkippedReason: priorManifest ? undefined : diffNote,
|
|
71
140
|
archiveWarnings: warnings,
|
|
72
141
|
}, null, 2)}\n`);
|
|
@@ -77,6 +146,8 @@ export async function runReviewRisk(opts) {
|
|
|
77
146
|
renderReviewReport(summary);
|
|
78
147
|
if (diffNote)
|
|
79
148
|
process.stderr.write(`note: ${diffNote}\n`);
|
|
149
|
+
for (const note of listingNotes(listing, opts.listingPath))
|
|
150
|
+
process.stderr.write(`note: ${note}\n`);
|
|
80
151
|
}
|
|
81
152
|
// Block a publish pipeline only on a definite rejection.
|
|
82
153
|
if (summary.blocking)
|
package/dist/review-risk.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"review-risk.js","sourceRoot":"","sources":["../src/review-risk.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"review-risk.js","sourceRoot":"","sources":["../src/review-risk.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* First-run telemetry question.
|
|
3
|
+
*
|
|
4
|
+
* Telemetry itself lives in telemetry.ts, which is vendored byte-identical into
|
|
5
|
+
* @extenshi/mcp — the MCP server runs headless and can never prompt, so the
|
|
6
|
+
* asking lives here, in the CLI only, and the answer is persisted to the shared
|
|
7
|
+
* `~/.extenshi/config.json` (`telemetry: true|false`) that both packages read.
|
|
8
|
+
*
|
|
9
|
+
* It is asked ONCE, before the first event of the first interactive run, and
|
|
10
|
+
* never again: `readTelemetryPreference()` returning a boolean is the record of
|
|
11
|
+
* having asked. Everything else is a reason NOT to ask —
|
|
12
|
+
*
|
|
13
|
+
* - not a TTY on both stdin and stderr (piped, redirected, or a daemon):
|
|
14
|
+
* a prompt would hang a build forever;
|
|
15
|
+
* - CI is set: same, and the answer would belong to nobody;
|
|
16
|
+
* - DO_NOT_TRACK / EXTENSHI_TELEMETRY is set: the user has already answered
|
|
17
|
+
* with the environment, and re-asking would be nagging;
|
|
18
|
+
* - EXTENSHI_POSTHOG_KEY is set but empty: the build ships dark, so there is
|
|
19
|
+
* nothing to consent to.
|
|
20
|
+
*
|
|
21
|
+
* Declining is recorded, not just obeyed: a `false` in the config is what makes
|
|
22
|
+
* the decision stick across runs and machines-per-home-dir. When the config
|
|
23
|
+
* cannot be written, the answer is still enforced for the current run through
|
|
24
|
+
* the environment, and the question comes back next time.
|
|
25
|
+
*
|
|
26
|
+
* Fail-soft like the rest of the telemetry path — an unwritable home directory
|
|
27
|
+
* or a closed stdin must never break the command the user actually typed.
|
|
28
|
+
*/
|
|
29
|
+
export declare const TELEMETRY_PROMPT_TEXT: string;
|
|
30
|
+
export interface ConsentDeps {
|
|
31
|
+
env: NodeJS.ProcessEnv;
|
|
32
|
+
/** True only when a human can actually see and answer the question. */
|
|
33
|
+
isInteractive: () => boolean;
|
|
34
|
+
readPreference: () => boolean | undefined;
|
|
35
|
+
savePreference: (enabled: boolean) => void;
|
|
36
|
+
/** Resolves to the raw answer, or null when nothing was answered in time. */
|
|
37
|
+
ask: (question: string) => Promise<string | null>;
|
|
38
|
+
log: (message: string) => void;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Which environment variable, if any, has already answered the question.
|
|
42
|
+
*
|
|
43
|
+
* Exported because `extenshi telemetry status` has to name the variable that is
|
|
44
|
+
* overriding the saved answer, and a second copy of this list there would drift
|
|
45
|
+
* from the one that actually suppresses the prompt. Any non-empty value counts
|
|
46
|
+
* as an answer — including `DO_NOT_TRACK=0`, which telemetry.ts reads as falsy
|
|
47
|
+
* and so leaves reporting on. Not asking is still right there: the user has
|
|
48
|
+
* said something about telemetry with the environment, and re-asking would
|
|
49
|
+
* override a deliberate setting with a prompt.
|
|
50
|
+
*/
|
|
51
|
+
export declare function envAnswerSource(env: NodeJS.ProcessEnv): 'DO_NOT_TRACK' | 'EXTENSHI_TELEMETRY' | null;
|
|
52
|
+
/** Should the question be put to the user at all? */
|
|
53
|
+
export declare function shouldAskForConsent(deps: ConsentDeps): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Ask once, persist the answer, and return it. Returns `undefined` when the
|
|
56
|
+
* question was skipped or went unanswered — the caller carries on either way,
|
|
57
|
+
* and an unanswered question is simply asked again on the next interactive run.
|
|
58
|
+
*/
|
|
59
|
+
export declare function maybeAskForTelemetryConsent(deps: ConsentDeps): Promise<boolean | undefined>;
|
|
60
|
+
/** The real dependencies: a TTY prompt on stderr so stdout stays pipeable. */
|
|
61
|
+
export declare function defaultConsentDeps(): ConsentDeps;
|
|
62
|
+
//# sourceMappingURL=telemetry-consent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetry-consent.d.ts","sourceRoot":"","sources":["../src/telemetry-consent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAYH,eAAO,MAAM,qBAAqB,QAIiC,CAAA;AAEnE,MAAM,WAAW,WAAW;IAC3B,GAAG,EAAE,MAAM,CAAC,UAAU,CAAA;IACtB,uEAAuE;IACvE,aAAa,EAAE,MAAM,OAAO,CAAA;IAC5B,cAAc,EAAE,MAAM,OAAO,GAAG,SAAS,CAAA;IACzC,cAAc,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;IAC1C,6EAA6E;IAC7E,GAAG,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IACjD,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CAC9B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,cAAc,GAAG,oBAAoB,GAAG,IAAI,CAIpG;AAED,qDAAqD;AACrD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAS9D;AAED;;;;GAIG;AACH,wBAAsB,2BAA2B,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CA2CjG;AAED,8EAA8E;AAC9E,wBAAgB,kBAAkB,IAAI,WAAW,CAoBhD"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* First-run telemetry question.
|
|
3
|
+
*
|
|
4
|
+
* Telemetry itself lives in telemetry.ts, which is vendored byte-identical into
|
|
5
|
+
* @extenshi/mcp — the MCP server runs headless and can never prompt, so the
|
|
6
|
+
* asking lives here, in the CLI only, and the answer is persisted to the shared
|
|
7
|
+
* `~/.extenshi/config.json` (`telemetry: true|false`) that both packages read.
|
|
8
|
+
*
|
|
9
|
+
* It is asked ONCE, before the first event of the first interactive run, and
|
|
10
|
+
* never again: `readTelemetryPreference()` returning a boolean is the record of
|
|
11
|
+
* having asked. Everything else is a reason NOT to ask —
|
|
12
|
+
*
|
|
13
|
+
* - not a TTY on both stdin and stderr (piped, redirected, or a daemon):
|
|
14
|
+
* a prompt would hang a build forever;
|
|
15
|
+
* - CI is set: same, and the answer would belong to nobody;
|
|
16
|
+
* - DO_NOT_TRACK / EXTENSHI_TELEMETRY is set: the user has already answered
|
|
17
|
+
* with the environment, and re-asking would be nagging;
|
|
18
|
+
* - EXTENSHI_POSTHOG_KEY is set but empty: the build ships dark, so there is
|
|
19
|
+
* nothing to consent to.
|
|
20
|
+
*
|
|
21
|
+
* Declining is recorded, not just obeyed: a `false` in the config is what makes
|
|
22
|
+
* the decision stick across runs and machines-per-home-dir. When the config
|
|
23
|
+
* cannot be written, the answer is still enforced for the current run through
|
|
24
|
+
* the environment, and the question comes back next time.
|
|
25
|
+
*
|
|
26
|
+
* Fail-soft like the rest of the telemetry path — an unwritable home directory
|
|
27
|
+
* or a closed stdin must never break the command the user actually typed.
|
|
28
|
+
*/
|
|
29
|
+
import * as readline from 'node:readline/promises';
|
|
30
|
+
import { readTelemetryPreference, saveTelemetryPreference } from './config.js';
|
|
31
|
+
/**
|
|
32
|
+
* How long to wait for an answer before giving up and asking again next run.
|
|
33
|
+
* Kept short because this blocks the command the user actually typed: a TTY
|
|
34
|
+
* that nobody is watching (a pty in a script) should not look like a hang.
|
|
35
|
+
*/
|
|
36
|
+
const ANSWER_TIMEOUT_MS = 30_000;
|
|
37
|
+
export const TELEMETRY_PROMPT_TEXT = 'Extenshi can report anonymous usage — which command ran, its flag names,\n' +
|
|
38
|
+
'versions, and a coarse error kind. Never file paths, artifact contents,\n' +
|
|
39
|
+
'manifest data, or flag values. It helps us fix what actually breaks.\n' +
|
|
40
|
+
'You can change this any time with `extenshi telemetry on|off`.\n';
|
|
41
|
+
/**
|
|
42
|
+
* Which environment variable, if any, has already answered the question.
|
|
43
|
+
*
|
|
44
|
+
* Exported because `extenshi telemetry status` has to name the variable that is
|
|
45
|
+
* overriding the saved answer, and a second copy of this list there would drift
|
|
46
|
+
* from the one that actually suppresses the prompt. Any non-empty value counts
|
|
47
|
+
* as an answer — including `DO_NOT_TRACK=0`, which telemetry.ts reads as falsy
|
|
48
|
+
* and so leaves reporting on. Not asking is still right there: the user has
|
|
49
|
+
* said something about telemetry with the environment, and re-asking would
|
|
50
|
+
* override a deliberate setting with a prompt.
|
|
51
|
+
*/
|
|
52
|
+
export function envAnswerSource(env) {
|
|
53
|
+
if ((env.DO_NOT_TRACK ?? '').trim() !== '')
|
|
54
|
+
return 'DO_NOT_TRACK';
|
|
55
|
+
if ((env.EXTENSHI_TELEMETRY ?? '').trim() !== '')
|
|
56
|
+
return 'EXTENSHI_TELEMETRY';
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
/** Should the question be put to the user at all? */
|
|
60
|
+
export function shouldAskForConsent(deps) {
|
|
61
|
+
if (deps.readPreference() !== undefined)
|
|
62
|
+
return false;
|
|
63
|
+
if (envAnswerSource(deps.env) !== null)
|
|
64
|
+
return false;
|
|
65
|
+
if ((deps.env.CI ?? '') !== '')
|
|
66
|
+
return false;
|
|
67
|
+
// Set-but-empty means the build ships dark; unset means the embedded key.
|
|
68
|
+
if (deps.env.EXTENSHI_POSTHOG_KEY !== undefined && deps.env.EXTENSHI_POSTHOG_KEY.trim() === '') {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
return deps.isInteractive();
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Ask once, persist the answer, and return it. Returns `undefined` when the
|
|
75
|
+
* question was skipped or went unanswered — the caller carries on either way,
|
|
76
|
+
* and an unanswered question is simply asked again on the next interactive run.
|
|
77
|
+
*/
|
|
78
|
+
export async function maybeAskForTelemetryConsent(deps) {
|
|
79
|
+
try {
|
|
80
|
+
if (!shouldAskForConsent(deps))
|
|
81
|
+
return undefined;
|
|
82
|
+
deps.log(`\n${TELEMETRY_PROMPT_TEXT}`);
|
|
83
|
+
const answer = await deps.ask('Share anonymous usage data? [Y/n] ');
|
|
84
|
+
// No answer at all (timeout, closed stdin) is not consent, and is not a
|
|
85
|
+
// refusal either — record nothing and ask again next time.
|
|
86
|
+
if (answer === null)
|
|
87
|
+
return undefined;
|
|
88
|
+
const normalized = answer.trim().toLowerCase();
|
|
89
|
+
const enabled = !(normalized === 'n' || normalized === 'no');
|
|
90
|
+
// Apply the answer to THIS process before trying to persist it. A decline
|
|
91
|
+
// that cannot be written (read-only $HOME) must still hold for the run
|
|
92
|
+
// that made it — otherwise the user says no, the write fails silently, and
|
|
93
|
+
// the very next captureEvent reports anyway. telemetry.ts reads this
|
|
94
|
+
// variable on its first event, which has not happened yet: runCommand()
|
|
95
|
+
// asks before it captures anything. Accepting needs no variable, since
|
|
96
|
+
// reporting is what happens without one.
|
|
97
|
+
if (!enabled)
|
|
98
|
+
deps.env.EXTENSHI_TELEMETRY = '0';
|
|
99
|
+
let persisted = true;
|
|
100
|
+
try {
|
|
101
|
+
deps.savePreference(enabled);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
persisted = false;
|
|
105
|
+
}
|
|
106
|
+
deps.log(enabled
|
|
107
|
+
? '✓ Thanks — anonymous usage reporting is on. Turn it off any time with `extenshi telemetry off`.\n'
|
|
108
|
+
: '✓ Anonymous usage reporting is off. Nothing but your explicit commands leaves this machine.\n');
|
|
109
|
+
if (!persisted) {
|
|
110
|
+
deps.log('(The choice could not be saved, so this run honours it but the next one will ask again.)\n');
|
|
111
|
+
}
|
|
112
|
+
return enabled;
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
// Unwritable config, closed stdin, anything at all — never break the
|
|
116
|
+
// command the user actually ran.
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/** The real dependencies: a TTY prompt on stderr so stdout stays pipeable. */
|
|
121
|
+
export function defaultConsentDeps() {
|
|
122
|
+
return {
|
|
123
|
+
env: process.env,
|
|
124
|
+
isInteractive: () => Boolean(process.stdin.isTTY && process.stderr.isTTY),
|
|
125
|
+
readPreference: readTelemetryPreference,
|
|
126
|
+
savePreference: (enabled) => {
|
|
127
|
+
saveTelemetryPreference(enabled);
|
|
128
|
+
},
|
|
129
|
+
ask: async (question) => {
|
|
130
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
131
|
+
try {
|
|
132
|
+
return await rl.question(question, { signal: AbortSignal.timeout(ANSWER_TIMEOUT_MS) });
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
rl.close();
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
log: (message) => console.error(message),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=telemetry-consent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetry-consent.js","sourceRoot":"","sources":["../src/telemetry-consent.ts"],"names":[],"mappings":"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"}
|