Skip to content

Commit 69979a3

Browse files
authored
[wrangler] Add analytics properties to secret commands (#11777)
* Add analytics properties to secret commands * fix: rename shadowed variable to fix eslint no-shadow error
1 parent be5ef57 commit 69979a3

File tree

5 files changed

+131
-17
lines changed

5 files changed

+131
-17
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
"wrangler": patch
3+
---
4+
5+
Add analytics properties to secret commands for better usage insights
6+
7+
Secret commands (`wrangler secret put`, `wrangler secret bulk`, and their Pages/versions equivalents) now include additional analytics properties to help understand how secrets are being managed:
8+
9+
- `secretOperation`: Whether this is a "single" or "bulk" secret operation
10+
- `secretSource`: How the secret was provided ("interactive", "stdin", or "file")
11+
- `secretFormat`: For bulk operations, the format used ("json" or "dotenv")
12+
- `hasEnvironment`: Whether an environment was specified
13+
14+
These properties help improve the developer experience by understanding common usage patterns. No sensitive information (secret names, values, or counts) is tracked.

packages/wrangler/src/pages/secret/index.ts

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -175,9 +175,17 @@ export const pagesSecretPutCommand = createCommand({
175175
}
176176
);
177177

178-
metrics.sendMetricsEvent("create pages encrypted variable", {
179-
sendMetrics: config?.send_metrics,
180-
});
178+
metrics.sendMetricsEvent(
179+
"create pages encrypted variable",
180+
{
181+
secretOperation: "single",
182+
secretSource: isInteractive() ? "interactive" : "stdin",
183+
hasEnvironment: Boolean(args.env),
184+
},
185+
{
186+
sendMetrics: config?.send_metrics,
187+
}
188+
);
181189

182190
logger.log(`✨ Success! Uploaded secret ${args.key}`);
183191
},
@@ -206,20 +214,22 @@ export const pagesSecretBulkCommand = createCommand({
206214
},
207215
positionalArgs: ["file"],
208216
async handler(args) {
209-
const { env, project, accountId } = await pagesProject(
217+
const { env, project, accountId, config } = await pagesProject(
210218
args.env,
211219
args.projectName
212220
);
213221

214222
logger.log(
215223
`🌀 Creating the secrets for the Pages project "${project.name}" (${env})`
216224
);
217-
const content = await parseBulkInputToObject(args.file);
225+
const result = await parseBulkInputToObject(args.file);
218226

219-
if (!content) {
227+
if (!result) {
220228
throw new FatalError(`🚨 No content found in file or piped input.`);
221229
}
222230

231+
const { content, secretSource, secretFormat } = result;
232+
223233
const upsertBindings = Object.fromEntries(
224234
Object.entries(content).map(([key, value]) => {
225235
return [
@@ -254,6 +264,18 @@ export const pagesSecretBulkCommand = createCommand({
254264
logger.log(
255265
`✨ ${Object.keys(upsertBindings).length} secrets successfully uploaded`
256266
);
267+
metrics.sendMetricsEvent(
268+
"create pages encrypted variable",
269+
{
270+
secretOperation: "bulk",
271+
secretSource,
272+
secretFormat,
273+
hasEnvironment: Boolean(args.env),
274+
},
275+
{
276+
sendMetrics: config?.send_metrics,
277+
}
278+
);
257279
} catch (err) {
258280
logger.log(`🚨 Secrets failed to upload`);
259281
throw err;

packages/wrangler/src/secret/index.ts

Lines changed: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -229,9 +229,17 @@ export const secretPutCommand = createCommand({
229229

230230
try {
231231
await submitSecret();
232-
metrics.sendMetricsEvent("create encrypted variable", {
233-
sendMetrics: config.send_metrics,
234-
});
232+
metrics.sendMetricsEvent(
233+
"create encrypted variable",
234+
{
235+
secretOperation: "single",
236+
secretSource: isInteractive ? "interactive" : "stdin",
237+
hasEnvironment: Boolean(args.env),
238+
},
239+
{
240+
sendMetrics: config.send_metrics,
241+
}
242+
);
235243
} catch (e) {
236244
if (isWorkerNotFoundError(e)) {
237245
// create a draft worker and try again
@@ -449,12 +457,14 @@ export const secretBulkCommand = createCommand({
449457
}`
450458
);
451459

452-
const content = await parseBulkInputToObject(args.file);
460+
const result = await parseBulkInputToObject(args.file);
453461

454-
if (!content) {
462+
if (!result) {
455463
return logger.error(`🚨 No content found in file, or piped input.`);
456464
}
457465

466+
const { content, secretSource, secretFormat } = result;
467+
458468
function getSettings() {
459469
const url = isServiceEnv
460470
? `/accounts/${accountId}/workers/services/${scriptName}/environments/${args.env}/settings`
@@ -487,13 +497,13 @@ export const secretBulkCommand = createCommand({
487497
} catch (e) {
488498
if (isWorkerNotFoundError(e)) {
489499
// create a draft worker before patching
490-
const result = await createDraftWorker({
500+
const draftWorkerResult = await createDraftWorker({
491501
config,
492502
args: args,
493503
accountId,
494504
scriptName,
495505
});
496-
if (result === null) {
506+
if (draftWorkerResult === null) {
497507
return;
498508
}
499509
existingBindings = [];
@@ -534,6 +544,18 @@ export const secretBulkCommand = createCommand({
534544
logger.log("");
535545
logger.log("Finished processing secrets file:");
536546
logger.log(`✨ ${upsertBindings.length} secrets successfully uploaded`);
547+
metrics.sendMetricsEvent(
548+
"create encrypted variable",
549+
{
550+
secretOperation: "bulk",
551+
secretSource,
552+
secretFormat,
553+
hasEnvironment: Boolean(args.env),
554+
},
555+
{
556+
sendMetrics: config.send_metrics,
557+
}
558+
);
537559
} catch (err) {
538560
logger.log("");
539561
logger.log(`🚨 Secrets failed to upload`);
@@ -561,16 +583,39 @@ export function validateFileSecrets(
561583
}
562584
}
563585

564-
export async function parseBulkInputToObject(input?: string) {
586+
/** Error thrown when no input is provided to parseBulkInputToObject */
587+
export class NoInputError extends Error {
588+
constructor() {
589+
super("No input provided");
590+
this.name = "NoInputError";
591+
}
592+
}
593+
594+
/** Result from parsing bulk secret input, including metadata for analytics */
595+
export type BulkInputResult = {
596+
content: Record<string, string>;
597+
secretSource: "file" | "stdin";
598+
secretFormat: "json" | "dotenv";
599+
};
600+
601+
export async function parseBulkInputToObject(
602+
input?: string
603+
): Promise<BulkInputResult | undefined> {
565604
let content: Record<string, string>;
605+
let secretSource: "file" | "stdin";
606+
let secretFormat: "json" | "dotenv";
607+
566608
if (input) {
609+
secretSource = "file";
567610
const jsonFilePath = path.resolve(input);
568611
try {
569612
const fileContent = readFileSync(jsonFilePath);
570613
try {
571614
content = parseJSON(fileContent) as Record<string, string>;
615+
secretFormat = "json";
572616
} catch (e) {
573617
content = dotenvParse(fileContent);
618+
secretFormat = "dotenv";
574619
// dotenvParse does not error unless fileContent is undefined, no keys === error
575620
if (Object.keys(content).length === 0) {
576621
throw e;
@@ -583,6 +628,7 @@ export async function parseBulkInputToObject(input?: string) {
583628
}
584629
validateFileSecrets(content, input);
585630
} else {
631+
secretSource = "stdin";
586632
try {
587633
const rl = readline.createInterface({ input: process.stdin });
588634
let pipedInput = "";
@@ -591,8 +637,10 @@ export async function parseBulkInputToObject(input?: string) {
591637
}
592638
try {
593639
content = parseJSON(pipedInput) as Record<string, string>;
640+
secretFormat = "json";
594641
} catch (e) {
595642
content = dotenvParse(pipedInput);
643+
secretFormat = "dotenv";
596644
// dotenvParse does not error unless fileContent is undefined, no keys === error
597645
if (Object.keys(content).length === 0) {
598646
throw e;
@@ -602,5 +650,5 @@ export async function parseBulkInputToObject(input?: string) {
602650
return;
603651
}
604652
}
605-
return content;
653+
return { content, secretSource, secretFormat };
606654
}

packages/wrangler/src/versions/secrets/bulk.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { configFileName, UserError } from "@cloudflare/workers-utils";
22
import { fetchResult } from "../../cfetch";
33
import { createCommand } from "../../core/create-command";
44
import { logger } from "../../logger";
5+
import * as metrics from "../../metrics";
56
import { parseBulkInputToObject } from "../../secret";
67
import { requireAuth } from "../../user";
78
import { getLegacyScriptName } from "../../utils/getLegacyScriptName";
@@ -54,12 +55,14 @@ export const versionsSecretBulkCommand = createCommand({
5455
`🌀 Creating the secrets for the Worker "${scriptName}" ${args.env ? `(${args.env})` : ""}`
5556
);
5657

57-
const content = await parseBulkInputToObject(args.file);
58+
const result = await parseBulkInputToObject(args.file);
5859

59-
if (!content) {
60+
if (!result) {
6061
return logger.error(`No content found in file or piped input.`);
6162
}
6263

64+
const { content, secretSource, secretFormat } = result;
65+
6366
const secrets = Object.entries(content).map(([key, value]) => ({
6467
name: key,
6568
value,
@@ -94,6 +97,20 @@ export const versionsSecretBulkCommand = createCommand({
9497
for (const secret of secrets) {
9598
logger.log(`✨ Successfully created secret for key: ${secret.name}`);
9699
}
100+
101+
metrics.sendMetricsEvent(
102+
"create encrypted variable",
103+
{
104+
secretOperation: "bulk",
105+
secretSource,
106+
secretFormat,
107+
hasEnvironment: Boolean(args.env),
108+
},
109+
{
110+
sendMetrics: config.send_metrics,
111+
}
112+
);
113+
97114
logger.log(
98115
`✨ Success! Created version ${newVersion.id} with ${secrets.length} secrets.` +
99116
`\n➡️ To deploy this version to production traffic use the command "wrangler versions deploy".`

packages/wrangler/src/versions/secrets/put.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { fetchResult } from "../../cfetch";
33
import { createCommand } from "../../core/create-command";
44
import { prompt } from "../../dialogs";
55
import { logger } from "../../logger";
6+
import * as metrics from "../../metrics";
67
import { requireAuth } from "../../user";
78
import { getLegacyScriptName } from "../../utils/getLegacyScriptName";
89
import { readFromStdin, trimTrailingWhitespace } from "../../utils/std";
@@ -95,6 +96,18 @@ export const versionsSecretPutCommand = createCommand({
9596
unsafeMetadata: config.unsafe.metadata,
9697
});
9798

99+
metrics.sendMetricsEvent(
100+
"create encrypted variable",
101+
{
102+
secretOperation: "single",
103+
secretSource: isInteractive ? "interactive" : "stdin",
104+
hasEnvironment: Boolean(args.env),
105+
},
106+
{
107+
sendMetrics: config.send_metrics,
108+
}
109+
);
110+
98111
logger.log(
99112
`✨ Success! Created version ${newVersion.id} with secret ${args.key}.` +
100113
`\n➡️ To deploy this version with secret ${args.key} to production traffic use the command "wrangler versions deploy".`

0 commit comments

Comments
 (0)