@vltpkg/cli-sdk 1.0.0-rc.27 → 1.0.0-rc.29
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/commands/access.d.ts +22 -0
- package/dist/commands/access.js +246 -0
- package/dist/commands/deprecate.d.ts +13 -0
- package/dist/commands/deprecate.js +139 -0
- package/dist/commands/dist-tag.d.ts +21 -0
- package/dist/commands/dist-tag.js +177 -0
- package/dist/commands/init.js +25 -9
- package/dist/commands/install.js +10 -0
- package/dist/commands/profile.d.ts +13 -0
- package/dist/commands/profile.js +104 -0
- package/dist/commands/token.d.ts +24 -1
- package/dist/commands/token.js +112 -5
- package/dist/commands/unpublish.d.ts +15 -0
- package/dist/commands/unpublish.js +200 -0
- package/dist/config/definition.d.ts +20 -3
- package/dist/config/definition.js +32 -24
- package/dist/config/index.js +1 -0
- package/dist/custom-help.js +7 -0
- package/dist/exec-command.js +1 -1
- package/dist/index.js +1 -1
- package/dist/output.d.ts +2 -1
- package/dist/output.js +55 -1
- package/dist/telemetry.d.ts +58 -0
- package/dist/telemetry.js +170 -0
- package/package.json +26 -25
|
@@ -23,12 +23,15 @@ export declare const commands: {
|
|
|
23
23
|
readonly ls: "list";
|
|
24
24
|
readonly show: "view";
|
|
25
25
|
readonly xc: "exec-cache";
|
|
26
|
+
readonly access: "access";
|
|
26
27
|
readonly bugs: "bugs";
|
|
27
28
|
readonly build: "build";
|
|
28
29
|
readonly cache: "cache";
|
|
29
30
|
readonly ci: "ci";
|
|
30
31
|
readonly config: "config";
|
|
31
32
|
readonly create: "create";
|
|
33
|
+
readonly deprecate: "deprecate";
|
|
34
|
+
readonly 'dist-tag': "dist-tag";
|
|
32
35
|
readonly docs: "docs";
|
|
33
36
|
readonly exec: "exec";
|
|
34
37
|
readonly 'exec-local': "exec-local";
|
|
@@ -41,6 +44,7 @@ export declare const commands: {
|
|
|
41
44
|
readonly pack: "pack";
|
|
42
45
|
readonly ping: "ping";
|
|
43
46
|
readonly pkg: "pkg";
|
|
47
|
+
readonly profile: "profile";
|
|
44
48
|
readonly publish: "publish";
|
|
45
49
|
readonly query: "query";
|
|
46
50
|
readonly repo: "repo";
|
|
@@ -48,6 +52,7 @@ export declare const commands: {
|
|
|
48
52
|
readonly run: "run";
|
|
49
53
|
readonly token: "token";
|
|
50
54
|
readonly uninstall: "uninstall";
|
|
55
|
+
readonly unpublish: "unpublish";
|
|
51
56
|
readonly update: "update";
|
|
52
57
|
readonly 'exec-cache': "exec-cache";
|
|
53
58
|
readonly version: "version";
|
|
@@ -63,7 +68,7 @@ export declare const getCommand: (s?: string) => Commands[keyof Commands] | unde
|
|
|
63
68
|
/**
|
|
64
69
|
* Fields that are parsed as a set of key=value pairs
|
|
65
70
|
*/
|
|
66
|
-
export declare const recordFields: readonly ["git-hosts", "registries", "git-host-archives", "
|
|
71
|
+
export declare const recordFields: readonly ["git-hosts", "registries", "git-host-archives", "scoped-registries", "jsr-registries"];
|
|
67
72
|
export type RecordField = (typeof recordFields)[number];
|
|
68
73
|
export declare const isRecordField: (s: string) => s is RecordField;
|
|
69
74
|
export declare const definition: import("jackspeak").Jack<{
|
|
@@ -91,7 +96,7 @@ export declare const definition: import("jackspeak").Jack<{
|
|
|
91
96
|
hint: string;
|
|
92
97
|
description: string;
|
|
93
98
|
};
|
|
94
|
-
'
|
|
99
|
+
'scoped-registries': {
|
|
95
100
|
hint: string;
|
|
96
101
|
description: string;
|
|
97
102
|
};
|
|
@@ -111,7 +116,7 @@ export declare const definition: import("jackspeak").Jack<{
|
|
|
111
116
|
};
|
|
112
117
|
} & {
|
|
113
118
|
registries: import("jackspeak").ConfigOption<"string", true, readonly string[] | undefined>;
|
|
114
|
-
'
|
|
119
|
+
'scoped-registries': import("jackspeak").ConfigOption<"string", true, readonly string[] | undefined>;
|
|
115
120
|
'jsr-registries': import("jackspeak").ConfigOption<"string", true, readonly string[] | undefined>;
|
|
116
121
|
'git-hosts': import("jackspeak").ConfigOption<"string", true, readonly string[] | undefined>;
|
|
117
122
|
'git-host-archives': import("jackspeak").ConfigOption<"string", true, readonly string[] | undefined>;
|
|
@@ -341,6 +346,10 @@ export declare const definition: import("jackspeak").Jack<{
|
|
|
341
346
|
'dry-run': {
|
|
342
347
|
description: string;
|
|
343
348
|
};
|
|
349
|
+
force: {
|
|
350
|
+
short: string;
|
|
351
|
+
description: string;
|
|
352
|
+
};
|
|
344
353
|
'expect-lockfile': {
|
|
345
354
|
description: string;
|
|
346
355
|
};
|
|
@@ -352,6 +361,7 @@ export declare const definition: import("jackspeak").Jack<{
|
|
|
352
361
|
};
|
|
353
362
|
} & {
|
|
354
363
|
'dry-run': import("jackspeak").ConfigOption<"boolean", false, undefined>;
|
|
364
|
+
force: import("jackspeak").ConfigOption<"boolean", false, undefined>;
|
|
355
365
|
'expect-lockfile': import("jackspeak").ConfigOption<"boolean", false, undefined>;
|
|
356
366
|
'frozen-lockfile': import("jackspeak").ConfigOption<"boolean", false, undefined>;
|
|
357
367
|
'lockfile-only': import("jackspeak").ConfigOption<"boolean", false, undefined>;
|
|
@@ -380,6 +390,13 @@ export declare const definition: import("jackspeak").Jack<{
|
|
|
380
390
|
} & {
|
|
381
391
|
otp: import("jackspeak").ConfigOption<"string", false, readonly string[] | undefined>;
|
|
382
392
|
'publish-directory': import("jackspeak").ConfigOption<"string", false, readonly string[] | undefined>;
|
|
393
|
+
} & {
|
|
394
|
+
telemetry: {
|
|
395
|
+
description: string;
|
|
396
|
+
default: true;
|
|
397
|
+
};
|
|
398
|
+
} & {
|
|
399
|
+
telemetry: import("jackspeak").ConfigOption<"boolean", false, undefined>;
|
|
383
400
|
} & {
|
|
384
401
|
yes: {
|
|
385
402
|
short: string;
|
|
@@ -15,12 +15,15 @@ export const defaultEditor = () => process.env.EDITOR ||
|
|
|
15
15
|
`${process.env.SYSTEMROOT}\\notepad.exe`
|
|
16
16
|
: 'vi');
|
|
17
17
|
const canonicalCommands = {
|
|
18
|
+
access: 'access',
|
|
18
19
|
bugs: 'bugs',
|
|
19
20
|
build: 'build',
|
|
20
21
|
cache: 'cache',
|
|
21
22
|
ci: 'ci',
|
|
22
23
|
config: 'config',
|
|
23
24
|
create: 'create',
|
|
25
|
+
deprecate: 'deprecate',
|
|
26
|
+
'dist-tag': 'dist-tag',
|
|
24
27
|
docs: 'docs',
|
|
25
28
|
exec: 'exec',
|
|
26
29
|
'exec-local': 'exec-local',
|
|
@@ -34,6 +37,7 @@ const canonicalCommands = {
|
|
|
34
37
|
pack: 'pack',
|
|
35
38
|
ping: 'ping',
|
|
36
39
|
pkg: 'pkg',
|
|
40
|
+
profile: 'profile',
|
|
37
41
|
publish: 'publish',
|
|
38
42
|
query: 'query',
|
|
39
43
|
repo: 'repo',
|
|
@@ -41,6 +45,7 @@ const canonicalCommands = {
|
|
|
41
45
|
run: 'run',
|
|
42
46
|
token: 'token',
|
|
43
47
|
uninstall: 'uninstall',
|
|
48
|
+
unpublish: 'unpublish',
|
|
44
49
|
update: 'update',
|
|
45
50
|
'exec-cache': 'exec-cache',
|
|
46
51
|
version: 'version',
|
|
@@ -98,35 +103,21 @@ export const recordFields = [
|
|
|
98
103
|
'git-hosts',
|
|
99
104
|
'registries',
|
|
100
105
|
'git-host-archives',
|
|
101
|
-
'
|
|
106
|
+
'scoped-registries',
|
|
102
107
|
'jsr-registries',
|
|
103
108
|
];
|
|
104
109
|
export const isRecordField = (s) => recordFields.includes(s);
|
|
105
|
-
const stopParsingCommands = [
|
|
106
|
-
'create',
|
|
107
|
-
'run',
|
|
108
|
-
'run-exec',
|
|
109
|
-
'exec-local',
|
|
110
|
-
'exec',
|
|
111
|
-
];
|
|
112
|
-
let stopParsing = undefined;
|
|
113
110
|
const j = jack({
|
|
114
111
|
envPrefix: 'VLT',
|
|
115
112
|
allowPositionals: true,
|
|
116
113
|
usage: `vlt [<options>] [<cmd> [<args> ...]]`,
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
// vlt exec --vlt --configs command --args --for --command
|
|
125
|
-
if (stopParsingCommands.includes(commands[a])) {
|
|
126
|
-
stopParsing = true;
|
|
127
|
-
}
|
|
128
|
-
return false;
|
|
129
|
-
},
|
|
114
|
+
// vlt options can appear on either side of the command/script
|
|
115
|
+
// name. To forward an argument (especially a flag-like one) to
|
|
116
|
+
// the underlying bin/script without vlt interpreting it, place
|
|
117
|
+
// it after a `--` terminator, e.g.:
|
|
118
|
+
// vlt run scriptName --vlt-flag -- --script-flag --more
|
|
119
|
+
// Node's `parseArgs` natively treats everything following `--`
|
|
120
|
+
// as positional, so no special-casing is needed here.
|
|
130
121
|
})
|
|
131
122
|
.heading('vlt')
|
|
132
123
|
.description(`More documentation available at <https://docs.vlt.sh>`)
|
|
@@ -196,12 +187,12 @@ export const definition = j
|
|
|
196
187
|
mapping in most cases.
|
|
197
188
|
`,
|
|
198
189
|
},
|
|
199
|
-
'
|
|
190
|
+
'scoped-registries': {
|
|
200
191
|
hint: '@scope=url',
|
|
201
192
|
description: `Map package name scopes to registry URLs.
|
|
202
193
|
|
|
203
194
|
For example,
|
|
204
|
-
\`--
|
|
195
|
+
\`--scoped-registries @acme=https://registry.acme/\`
|
|
205
196
|
would tell vlt to fetch any packages named
|
|
206
197
|
\`@acme/...\` from the \`https://registry.acme/\`
|
|
207
198
|
registry.
|
|
@@ -612,6 +603,10 @@ export const definition = j
|
|
|
612
603
|
'dry-run': {
|
|
613
604
|
description: 'Run command without making any changes',
|
|
614
605
|
},
|
|
606
|
+
force: {
|
|
607
|
+
short: 'f',
|
|
608
|
+
description: 'Force potentially dangerous operations, such as unpublishing an entire package.',
|
|
609
|
+
},
|
|
615
610
|
'expect-lockfile': {
|
|
616
611
|
description: 'Fail if lockfile is missing or out of date. Used by ci command to enforce lockfile integrity.',
|
|
617
612
|
},
|
|
@@ -650,6 +645,19 @@ export const definition = j
|
|
|
650
645
|
description: `Directory to use for pack and publish operations instead of the current directory.
|
|
651
646
|
The directory must exist and nothing will be copied to it.`,
|
|
652
647
|
},
|
|
648
|
+
})
|
|
649
|
+
.flag({
|
|
650
|
+
telemetry: {
|
|
651
|
+
description: `Enable anonymous usage telemetry.
|
|
652
|
+
|
|
653
|
+
vlt collects anonymized usage data to help improve
|
|
654
|
+
the tool. No personally identifiable information is
|
|
655
|
+
ever sent.
|
|
656
|
+
|
|
657
|
+
Set \`--no-telemetry\` or \`VLT_TELEMETRY=0\` or
|
|
658
|
+
\`DO_NOT_TRACK=1\` to opt out.`,
|
|
659
|
+
default: true,
|
|
660
|
+
},
|
|
653
661
|
})
|
|
654
662
|
.flag({
|
|
655
663
|
yes: {
|
package/dist/config/index.js
CHANGED
|
@@ -231,6 +231,7 @@ export class Config {
|
|
|
231
231
|
* If the config value is not set at all, an empty object is returned.
|
|
232
232
|
*/
|
|
233
233
|
getRecord(k) {
|
|
234
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
|
234
235
|
const pairs = this.get(k);
|
|
235
236
|
if (!pairs)
|
|
236
237
|
return {};
|
package/dist/custom-help.js
CHANGED
|
@@ -234,6 +234,13 @@ const allCommands = [
|
|
|
234
234
|
desc: 'Remove dependencies',
|
|
235
235
|
showByDefault: false,
|
|
236
236
|
},
|
|
237
|
+
{
|
|
238
|
+
name: 'unpublish',
|
|
239
|
+
aliases: [],
|
|
240
|
+
args: '<pkg>[@<version>]',
|
|
241
|
+
desc: 'Remove a package from the registry',
|
|
242
|
+
showByDefault: false,
|
|
243
|
+
},
|
|
237
244
|
{
|
|
238
245
|
name: 'update',
|
|
239
246
|
aliases: ['u'],
|
package/dist/exec-command.js
CHANGED
|
@@ -73,11 +73,11 @@ export class ExecCommand {
|
|
|
73
73
|
this.conf = conf;
|
|
74
74
|
this.bg = bg;
|
|
75
75
|
this.fg = fg;
|
|
76
|
-
this.view = this.validViewValues.get(conf.values.view) ?? 'human';
|
|
77
76
|
const { projectRoot, positionals: [arg0, ...args], } = conf;
|
|
78
77
|
this.arg0 = arg0;
|
|
79
78
|
this.args = args;
|
|
80
79
|
this.projectRoot = projectRoot;
|
|
80
|
+
this.view = this.validViewValues.get(conf.values.view) ?? 'human';
|
|
81
81
|
}
|
|
82
82
|
#targetCount() {
|
|
83
83
|
if (this.#nodes)
|
package/dist/index.js
CHANGED
|
@@ -67,6 +67,6 @@ const run = async () => {
|
|
|
67
67
|
return process.exit(process.exitCode || 1);
|
|
68
68
|
}
|
|
69
69
|
const command = await loadCommand(vlt.command);
|
|
70
|
-
await outputCommand(command, vlt, { start });
|
|
70
|
+
await outputCommand(command, vlt, { start, vltVersion: version });
|
|
71
71
|
};
|
|
72
72
|
export default run;
|
package/dist/output.d.ts
CHANGED
|
@@ -14,7 +14,8 @@ export type OnDone<T> = (result: T) => Promise<unknown>;
|
|
|
14
14
|
* the user-requested view, or the default if the user requested a view
|
|
15
15
|
* that is not defined for this command.
|
|
16
16
|
*/
|
|
17
|
-
export declare const outputCommand: <T>(cliCommand: Command<T>, conf: LoadedConfig, { start }?: {
|
|
17
|
+
export declare const outputCommand: <T>(cliCommand: Command<T>, conf: LoadedConfig, { start, vltVersion }?: {
|
|
18
18
|
start: number;
|
|
19
|
+
vltVersion?: string;
|
|
19
20
|
}) => Promise<void>;
|
|
20
21
|
export {};
|
package/dist/output.js
CHANGED
|
@@ -4,6 +4,16 @@ import { defaultView } from "./config/definition.js";
|
|
|
4
4
|
import { printErr, formatOptions } from "./print-err.js";
|
|
5
5
|
import { isViewClass } from "./view.js";
|
|
6
6
|
import { generateDefaultHelp, generateFullHelp, } from "./custom-help.js";
|
|
7
|
+
import { flush as flushTelemetry, trackCommand, trackError, } from "./telemetry.js";
|
|
8
|
+
/* c8 ignore start - CI env detection is a best-effort heuristic */
|
|
9
|
+
const isCI = () => !!(process.env.CI ||
|
|
10
|
+
process.env.GITHUB_ACTIONS ||
|
|
11
|
+
process.env.GITLAB_CI ||
|
|
12
|
+
process.env.CIRCLECI ||
|
|
13
|
+
process.env.JENKINS_URL ||
|
|
14
|
+
process.env.BUILDKITE ||
|
|
15
|
+
process.env.TRAVIS);
|
|
16
|
+
/* c8 ignore stop */
|
|
7
17
|
const supportsColor = (stream) => {
|
|
8
18
|
const res = createSupportsColor(stream, { sniffFlags: false });
|
|
9
19
|
if (res === false)
|
|
@@ -77,7 +87,9 @@ const startView = (conf, opts, views, { start } = { start: Date.now() }) => {
|
|
|
77
87
|
* the user-requested view, or the default if the user requested a view
|
|
78
88
|
* that is not defined for this command.
|
|
79
89
|
*/
|
|
80
|
-
export const outputCommand = async (cliCommand, conf, { start } = {
|
|
90
|
+
export const outputCommand = async (cliCommand, conf, { start, vltVersion } = {
|
|
91
|
+
start: Date.now(),
|
|
92
|
+
}) => {
|
|
81
93
|
const { usage, views, command } = cliCommand;
|
|
82
94
|
const stdoutColor = conf.values.color ?? supportsColor(process.stdout);
|
|
83
95
|
const stderrColor = conf.values.color ?? supportsColor(process.stderr);
|
|
@@ -102,8 +114,21 @@ export const outputCommand = async (cliCommand, conf, { start } = { start: Date.
|
|
|
102
114
|
const { onDone, onError } = startView(conf,
|
|
103
115
|
// assume views will always output to stdout so use color support from there
|
|
104
116
|
{ colors: stdoutColor }, views, { start });
|
|
117
|
+
const telemetryEnabled = conf.values.telemetry;
|
|
118
|
+
const commandName = conf.command;
|
|
105
119
|
try {
|
|
106
120
|
const output = await onDone(await command(conf));
|
|
121
|
+
const duration_ms = Date.now() - start;
|
|
122
|
+
trackCommand({
|
|
123
|
+
command: commandName,
|
|
124
|
+
duration_ms,
|
|
125
|
+
success: true,
|
|
126
|
+
node_version: process.version,
|
|
127
|
+
vlt_version: vltVersion ?? 'unknown',
|
|
128
|
+
os: process.platform,
|
|
129
|
+
arch: process.arch,
|
|
130
|
+
ci: isCI(),
|
|
131
|
+
}, telemetryEnabled);
|
|
107
132
|
if (output !== undefined && conf.values.view !== 'silent') {
|
|
108
133
|
stdout(conf.values.view === 'json' ?
|
|
109
134
|
JSON.stringify(output, null, 2)
|
|
@@ -112,10 +137,39 @@ export const outputCommand = async (cliCommand, conf, { start } = { start: Date.
|
|
|
112
137
|
colors: stdoutColor,
|
|
113
138
|
}, output));
|
|
114
139
|
}
|
|
140
|
+
// Await the flush so pending telemetry events are sent before
|
|
141
|
+
// the process exits. The flush has a built-in timeout cap
|
|
142
|
+
// (SHUTDOWN_TIMEOUT_MS) so it will never block for long, and the
|
|
143
|
+
// timer is unreffed so even if this is not awaited the process
|
|
144
|
+
// can still exit promptly.
|
|
145
|
+
await flushTelemetry();
|
|
115
146
|
}
|
|
116
147
|
catch (err) {
|
|
117
148
|
onError?.(err);
|
|
118
149
|
process.exitCode ||= 1;
|
|
150
|
+
const duration_ms = Date.now() - start;
|
|
151
|
+
trackCommand({
|
|
152
|
+
command: commandName,
|
|
153
|
+
duration_ms,
|
|
154
|
+
success: false,
|
|
155
|
+
node_version: process.version,
|
|
156
|
+
vlt_version: vltVersion ?? 'unknown',
|
|
157
|
+
os: process.platform,
|
|
158
|
+
arch: process.arch,
|
|
159
|
+
ci: isCI(),
|
|
160
|
+
}, telemetryEnabled);
|
|
161
|
+
const errorCode = (err instanceof Error &&
|
|
162
|
+
err.cause &&
|
|
163
|
+
typeof err.cause === 'object' &&
|
|
164
|
+
'code' in err.cause &&
|
|
165
|
+
typeof err.cause.code === 'string') ?
|
|
166
|
+
err.cause.code
|
|
167
|
+
: undefined;
|
|
168
|
+
trackError({
|
|
169
|
+
command: commandName,
|
|
170
|
+
error_code: errorCode,
|
|
171
|
+
}, telemetryEnabled);
|
|
172
|
+
await flushTelemetry();
|
|
119
173
|
printErr(err, usage, stderr, {
|
|
120
174
|
...formatOptions,
|
|
121
175
|
colors: stderrColor,
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anonymized telemetry for the vlt CLI.
|
|
3
|
+
*
|
|
4
|
+
* - Sends anonymous, non-PII events to PostHog.
|
|
5
|
+
* - Generates a random UUID on first run, stored in XDG data dir.
|
|
6
|
+
* - Respects opt-out via `DO_NOT_TRACK=1`, `VLT_TELEMETRY=0`,
|
|
7
|
+
* or the `--telemetry=false` config flag.
|
|
8
|
+
* - Flush is awaited with a timeout cap so it never blocks CLI exit for long.
|
|
9
|
+
* - Fails silently on any network or runtime error.
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Check whether telemetry is disabled via environment variables.
|
|
14
|
+
*
|
|
15
|
+
* - `DO_NOT_TRACK=1` — https://consoledonottrack.com/
|
|
16
|
+
* - `VLT_TELEMETRY=0`
|
|
17
|
+
*/
|
|
18
|
+
export declare const isOptedOut: () => boolean;
|
|
19
|
+
export interface CommandEvent {
|
|
20
|
+
command: string;
|
|
21
|
+
duration_ms: number;
|
|
22
|
+
success: boolean;
|
|
23
|
+
node_version: string;
|
|
24
|
+
vlt_version: string;
|
|
25
|
+
os: string;
|
|
26
|
+
arch: string;
|
|
27
|
+
ci: boolean;
|
|
28
|
+
}
|
|
29
|
+
export interface InstallEvent {
|
|
30
|
+
dependency_count: number;
|
|
31
|
+
duration_ms: number;
|
|
32
|
+
}
|
|
33
|
+
export interface ErrorEvent {
|
|
34
|
+
command: string;
|
|
35
|
+
error_code?: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Capture a `cli_command` event.
|
|
39
|
+
*/
|
|
40
|
+
export declare const trackCommand: (ev: CommandEvent, telemetryFlag?: boolean) => void;
|
|
41
|
+
/**
|
|
42
|
+
* Capture a `cli_install` event.
|
|
43
|
+
*/
|
|
44
|
+
export declare const trackInstall: (ev: InstallEvent, telemetryFlag?: boolean) => void;
|
|
45
|
+
/**
|
|
46
|
+
* Capture a `cli_error` event.
|
|
47
|
+
*/
|
|
48
|
+
export declare const trackError: (ev: ErrorEvent, telemetryFlag?: boolean) => void;
|
|
49
|
+
/**
|
|
50
|
+
* Flush pending events. Returns a promise that resolves once flushing
|
|
51
|
+
* completes **or** when `SHUTDOWN_TIMEOUT_MS` elapses — whichever
|
|
52
|
+
* comes first. Never rejects.
|
|
53
|
+
*
|
|
54
|
+
* The safety-net timer is unreffed so it cannot keep the process alive
|
|
55
|
+
* on its own — if the caller does not `await` this, Node will still
|
|
56
|
+
* exit promptly once all other handles are drained.
|
|
57
|
+
*/
|
|
58
|
+
export declare const flush: () => Promise<void>;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anonymized telemetry for the vlt CLI.
|
|
3
|
+
*
|
|
4
|
+
* - Sends anonymous, non-PII events to PostHog.
|
|
5
|
+
* - Generates a random UUID on first run, stored in XDG data dir.
|
|
6
|
+
* - Respects opt-out via `DO_NOT_TRACK=1`, `VLT_TELEMETRY=0`,
|
|
7
|
+
* or the `--telemetry=false` config flag.
|
|
8
|
+
* - Flush is awaited with a timeout cap so it never blocks CLI exit for long.
|
|
9
|
+
* - Fails silently on any network or runtime error.
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
import { randomUUID } from 'node:crypto';
|
|
13
|
+
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
14
|
+
import { createRequire } from 'node:module';
|
|
15
|
+
import { XDG } from '@vltpkg/xdg';
|
|
16
|
+
const POSTHOG_API_KEY = 'phc_k9xCAgC6sPIBLb5UhjhnGWpt1mos0hLV4mmEhZTGPpO';
|
|
17
|
+
const POSTHOG_HOST = 'https://us.i.posthog.com';
|
|
18
|
+
/** Timeout (ms) we wait for PostHog to flush before giving up. */
|
|
19
|
+
const SHUTDOWN_TIMEOUT_MS = 2_000;
|
|
20
|
+
const xdg = new XDG('vlt');
|
|
21
|
+
/**
|
|
22
|
+
* Check whether telemetry is disabled via environment variables.
|
|
23
|
+
*
|
|
24
|
+
* - `DO_NOT_TRACK=1` — https://consoledonottrack.com/
|
|
25
|
+
* - `VLT_TELEMETRY=0`
|
|
26
|
+
*/
|
|
27
|
+
export const isOptedOut = () => process.env.DO_NOT_TRACK === '1' ||
|
|
28
|
+
process.env.VLT_TELEMETRY === '0';
|
|
29
|
+
/**
|
|
30
|
+
* Return (or create) a stable anonymous distinct ID.
|
|
31
|
+
* Stored at `<XDG_DATA_HOME>/vlt/telemetry-id`.
|
|
32
|
+
*/
|
|
33
|
+
const getAnonymousId = () => {
|
|
34
|
+
const dir = xdg.data();
|
|
35
|
+
const file = xdg.data('telemetry-id');
|
|
36
|
+
try {
|
|
37
|
+
const existing = readFileSync(file, 'utf8').trim();
|
|
38
|
+
if (existing)
|
|
39
|
+
return existing;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// file doesn't exist yet — create it below
|
|
43
|
+
}
|
|
44
|
+
const id = randomUUID();
|
|
45
|
+
try {
|
|
46
|
+
mkdirSync(dir, { recursive: true });
|
|
47
|
+
writeFileSync(file, id + '\n');
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// best-effort — if we can't persist, just use a transient id
|
|
51
|
+
}
|
|
52
|
+
return id;
|
|
53
|
+
};
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
// PostHog client — lazily initialised
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
let _posthog;
|
|
58
|
+
let _initFailed = false;
|
|
59
|
+
const getClient = () => {
|
|
60
|
+
if (_posthog)
|
|
61
|
+
return _posthog;
|
|
62
|
+
if (_initFailed)
|
|
63
|
+
return undefined;
|
|
64
|
+
try {
|
|
65
|
+
// Dynamic import would be async; posthog-node also ships a
|
|
66
|
+
// synchronous constructor, so we use createRequire to keep
|
|
67
|
+
// the hot-path synchronous in this ESM package.
|
|
68
|
+
const req = createRequire(import.meta.url);
|
|
69
|
+
const { PostHog } = req('posthog-node');
|
|
70
|
+
_posthog = new PostHog(POSTHOG_API_KEY, {
|
|
71
|
+
host: POSTHOG_HOST,
|
|
72
|
+
// Buffer up to 20 events and flush every 10 s — but we always
|
|
73
|
+
// call shutdown at the end so this is just a safety net.
|
|
74
|
+
flushAt: 20,
|
|
75
|
+
flushInterval: 10_000,
|
|
76
|
+
});
|
|
77
|
+
return _posthog;
|
|
78
|
+
/* c8 ignore next 4 */
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
_initFailed = true;
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Capture a `cli_command` event.
|
|
87
|
+
*/
|
|
88
|
+
export const trackCommand = (ev, telemetryFlag) => {
|
|
89
|
+
if (telemetryFlag === false || isOptedOut())
|
|
90
|
+
return;
|
|
91
|
+
const ph = getClient();
|
|
92
|
+
if (!ph)
|
|
93
|
+
return;
|
|
94
|
+
try {
|
|
95
|
+
ph.capture({
|
|
96
|
+
distinctId: getAnonymousId(),
|
|
97
|
+
event: 'cli_command',
|
|
98
|
+
properties: { ...ev },
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// fail silently
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* Capture a `cli_install` event.
|
|
107
|
+
*/
|
|
108
|
+
export const trackInstall = (ev, telemetryFlag) => {
|
|
109
|
+
if (telemetryFlag === false || isOptedOut())
|
|
110
|
+
return;
|
|
111
|
+
const ph = getClient();
|
|
112
|
+
if (!ph)
|
|
113
|
+
return;
|
|
114
|
+
try {
|
|
115
|
+
ph.capture({
|
|
116
|
+
distinctId: getAnonymousId(),
|
|
117
|
+
event: 'cli_install',
|
|
118
|
+
properties: { ...ev },
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
// fail silently
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* Capture a `cli_error` event.
|
|
127
|
+
*/
|
|
128
|
+
export const trackError = (ev, telemetryFlag) => {
|
|
129
|
+
if (telemetryFlag === false || isOptedOut())
|
|
130
|
+
return;
|
|
131
|
+
const ph = getClient();
|
|
132
|
+
if (!ph)
|
|
133
|
+
return;
|
|
134
|
+
try {
|
|
135
|
+
ph.capture({
|
|
136
|
+
distinctId: getAnonymousId(),
|
|
137
|
+
event: 'cli_error',
|
|
138
|
+
properties: { ...ev },
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
// fail silently
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
/**
|
|
146
|
+
* Flush pending events. Returns a promise that resolves once flushing
|
|
147
|
+
* completes **or** when `SHUTDOWN_TIMEOUT_MS` elapses — whichever
|
|
148
|
+
* comes first. Never rejects.
|
|
149
|
+
*
|
|
150
|
+
* The safety-net timer is unreffed so it cannot keep the process alive
|
|
151
|
+
* on its own — if the caller does not `await` this, Node will still
|
|
152
|
+
* exit promptly once all other handles are drained.
|
|
153
|
+
*/
|
|
154
|
+
export const flush = () => {
|
|
155
|
+
const ph = _posthog;
|
|
156
|
+
if (!ph)
|
|
157
|
+
return Promise.resolve();
|
|
158
|
+
// Clear the reference so no further events are captured and
|
|
159
|
+
// PostHog's internal timers can be garbage-collected.
|
|
160
|
+
_posthog = undefined;
|
|
161
|
+
return Promise.race([
|
|
162
|
+
ph.shutdown().catch(() => { }),
|
|
163
|
+
new Promise(r => {
|
|
164
|
+
const t = setTimeout(r, SHUTDOWN_TIMEOUT_MS);
|
|
165
|
+
// Unref so this timer alone won't keep the process alive.
|
|
166
|
+
if (typeof t === 'object' && 'unref' in t)
|
|
167
|
+
t.unref();
|
|
168
|
+
}),
|
|
169
|
+
]);
|
|
170
|
+
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vltpkg/cli-sdk",
|
|
3
3
|
"description": "The source for the vlt CLI",
|
|
4
|
-
"version": "1.0.0-rc.
|
|
4
|
+
"version": "1.0.0-rc.29",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/vltpkg/vltpkg.git",
|
|
@@ -14,30 +14,30 @@
|
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"@resvg/resvg-wasm": "^2.6.2",
|
|
17
|
-
"@vltpkg/config": "1.0.0-rc.
|
|
18
|
-
"@vltpkg/dep-id": "1.0.0-rc.
|
|
19
|
-
"@vltpkg/dot-prop": "1.0.0-rc.
|
|
20
|
-
"@vltpkg/error-cause": "1.0.0-rc.
|
|
21
|
-
"@vltpkg/git": "1.0.0-rc.
|
|
22
|
-
"@vltpkg/graph": "1.0.0-rc.
|
|
23
|
-
"@vltpkg/graph-run": "1.0.0-rc.
|
|
24
|
-
"@vltpkg/init": "1.0.0-rc.
|
|
25
|
-
"@vltpkg/output": "1.0.0-rc.
|
|
26
|
-
"@vltpkg/package-info": "1.0.0-rc.
|
|
27
|
-
"@vltpkg/package-json": "1.0.0-rc.
|
|
28
|
-
"@vltpkg/promise-spawn": "1.0.0-rc.
|
|
29
|
-
"@vltpkg/query": "1.0.0-rc.
|
|
30
|
-
"@vltpkg/registry-client": "1.0.0-rc.
|
|
31
|
-
"@vltpkg/rollback-remove": "1.0.0-rc.
|
|
32
|
-
"@vltpkg/run": "1.0.0-rc.
|
|
33
|
-
"@vltpkg/security-archive": "1.0.0-rc.
|
|
34
|
-
"@vltpkg/spec": "1.0.0-rc.
|
|
35
|
-
"@vltpkg/types": "1.0.0-rc.
|
|
36
|
-
"@vltpkg/url-open": "1.0.0-rc.
|
|
37
|
-
"@vltpkg/vlt-json": "1.0.0-rc.
|
|
38
|
-
"@vltpkg/vlx": "1.0.0-rc.
|
|
39
|
-
"@vltpkg/workspaces": "1.0.0-rc.
|
|
40
|
-
"@vltpkg/xdg": "1.0.0-rc.
|
|
17
|
+
"@vltpkg/config": "1.0.0-rc.29",
|
|
18
|
+
"@vltpkg/dep-id": "1.0.0-rc.29",
|
|
19
|
+
"@vltpkg/dot-prop": "1.0.0-rc.29",
|
|
20
|
+
"@vltpkg/error-cause": "1.0.0-rc.29",
|
|
21
|
+
"@vltpkg/git": "1.0.0-rc.29",
|
|
22
|
+
"@vltpkg/graph": "1.0.0-rc.29",
|
|
23
|
+
"@vltpkg/graph-run": "1.0.0-rc.29",
|
|
24
|
+
"@vltpkg/init": "1.0.0-rc.29",
|
|
25
|
+
"@vltpkg/output": "1.0.0-rc.29",
|
|
26
|
+
"@vltpkg/package-info": "1.0.0-rc.29",
|
|
27
|
+
"@vltpkg/package-json": "1.0.0-rc.29",
|
|
28
|
+
"@vltpkg/promise-spawn": "1.0.0-rc.29",
|
|
29
|
+
"@vltpkg/query": "1.0.0-rc.29",
|
|
30
|
+
"@vltpkg/registry-client": "1.0.0-rc.29",
|
|
31
|
+
"@vltpkg/rollback-remove": "1.0.0-rc.29",
|
|
32
|
+
"@vltpkg/run": "1.0.0-rc.29",
|
|
33
|
+
"@vltpkg/security-archive": "1.0.0-rc.29",
|
|
34
|
+
"@vltpkg/spec": "1.0.0-rc.29",
|
|
35
|
+
"@vltpkg/types": "1.0.0-rc.29",
|
|
36
|
+
"@vltpkg/url-open": "1.0.0-rc.29",
|
|
37
|
+
"@vltpkg/vlt-json": "1.0.0-rc.29",
|
|
38
|
+
"@vltpkg/vlx": "1.0.0-rc.29",
|
|
39
|
+
"@vltpkg/workspaces": "1.0.0-rc.29",
|
|
40
|
+
"@vltpkg/xdg": "1.0.0-rc.29",
|
|
41
41
|
"ansi-to-pre": "^1.0.6",
|
|
42
42
|
"beautiful-mermaid": "^1.1.3",
|
|
43
43
|
"chalk": "^5.6.2",
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
"package-json-from-dist": "^1.0.1",
|
|
50
50
|
"path-scurry": "^2.0.1",
|
|
51
51
|
"polite-json": "^5.0.0",
|
|
52
|
+
"posthog-node": "^4.0.0",
|
|
52
53
|
"pretty-bytes": "^7.1.0",
|
|
53
54
|
"promise-call-limit": "^3.0.2",
|
|
54
55
|
"react": "^19.2.1",
|