@rebasepro/cli 0.19.2-canary.g9b599d4 → 0.20.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/index.es.js CHANGED
@@ -1474,33 +1474,12 @@ async function recordEvent(event, properties = {}, options = {}) {
1474
1474
  }
1475
1475
  //#endregion
1476
1476
  //#region src/telemetry/consent.ts
1477
- /**
1478
- * Asking, and what the question looks like.
1479
- *
1480
- * ## Why the prompt comes *after* the work
1481
- *
1482
- * The first `rebase init` on a machine is the event most worth having, and it
1483
- * is the one where no consent exists yet. Asking before scaffolding puts a
1484
- * privacy negotiation in front of someone who has not yet seen the tool do
1485
- * anything — the worst possible moment, and a reliable way to get a reflexive
1486
- * no.
1487
- *
1488
- * So the question is asked once the project exists and the user has seen it
1489
- * work. The event's data is still in memory at that point, so nothing is lost
1490
- * by waiting, and — this is the part that matters — **nothing has been
1491
- * transmitted or written**. Declining leaves no id, no file, no record.
1492
- *
1493
- * ## Why the payload is shown rather than described
1494
- *
1495
- * "Anonymous usage data" is a phrase that has been used to mean almost
1496
- * anything. Printing the exact JSON costs four lines of output and replaces a
1497
- * claim the user has to take on faith with something they can read. It is also
1498
- * the same builder the sender uses, so it cannot drift into a comfortable
1499
- * fiction.
1500
- */
1501
- /** True when we may ask: no decision recorded, and nothing else forbids it. */
1502
- function shouldPrompt(env = process.env) {
1503
- return suppressionReason(env) === "not_asked" && Boolean(process.stdin.isTTY);
1477
+ /** True when we may ask: nothing forbids it, and there is a question to ask. */
1478
+ function shouldPrompt(env = process.env, { reAskDeclined = false } = {}) {
1479
+ if (!process.stdin.isTTY) return false;
1480
+ const reason = suppressionReason(env);
1481
+ if (reason === "not_asked") return true;
1482
+ return reAskDeclined && reason === "declined";
1504
1483
  }
1505
1484
  function renderPreview(event, properties) {
1506
1485
  const preview = buildEvent(event, properties, {
@@ -1516,8 +1495,9 @@ function renderPreview(event, properties) {
1516
1495
  * CI must behave exactly as it does today, which means not asking and not
1517
1496
  * sending.
1518
1497
  */
1519
- async function promptForConsent(event, properties) {
1520
- if (!shouldPrompt()) return false;
1498
+ async function promptForConsent(event, properties, options = {}) {
1499
+ if (!shouldPrompt(process.env, options)) return false;
1500
+ const askedBefore = suppressionReason(process.env) === "declined";
1521
1501
  try {
1522
1502
  console.log("");
1523
1503
  console.log(chalk.bold("Help improve Rebase?"));
@@ -1531,6 +1511,11 @@ async function promptForConsent(event, properties) {
1531
1511
  console.log("");
1532
1512
  console.log(chalk.gray(" No project names, paths, schemas, URLs or error messages. Change your"));
1533
1513
  console.log(chalk.gray(` mind any time with ${chalk.cyan("rebase telemetry disable")}.`));
1514
+ if (askedBefore) {
1515
+ console.log("");
1516
+ console.log(chalk.gray(" You declined before, and that is still the answer unless you change it"));
1517
+ console.log(chalk.gray(" here. Asked once per new project; never during ordinary work."));
1518
+ }
1534
1519
  console.log("");
1535
1520
  const { accepted } = await inquirer.prompt([{
1536
1521
  type: "confirm",
@@ -1539,7 +1524,7 @@ async function promptForConsent(event, properties) {
1539
1524
  default: false
1540
1525
  }]);
1541
1526
  setConsent(Boolean(accepted));
1542
- console.log(accepted ? chalk.green(" Thank you — sharing enabled.") : chalk.gray(" Nothing will be sent. You will not be asked again."));
1527
+ console.log(accepted ? chalk.green(" Thank you — sharing enabled.") : chalk.gray(" Nothing will be sent." + (options.reAskDeclined ? " You will be asked again the next time you scaffold a project." : " You will not be asked again.")));
1543
1528
  console.log("");
1544
1529
  return Boolean(accepted);
1545
1530
  } catch {
@@ -2139,7 +2124,7 @@ async function createProject$1(options) {
2139
2124
  git: Boolean(options.git),
2140
2125
  duration: durationBucket(Date.now() - startedAt)
2141
2126
  };
2142
- if (await promptForConsent("cli.init", initProperties)) await recordEvent("cli.init", initProperties, { projectRoot: options.targetDirectory });
2127
+ if (await promptForConsent("cli.init", initProperties, { reAskDeclined: true })) await recordEvent("cli.init", initProperties, { projectRoot: options.targetDirectory });
2143
2128
  }
2144
2129
  /**
2145
2130
  * Apply a template preset by replacing the default collection files.
@@ -15174,7 +15159,31 @@ function resolveDeployArgs(rawArgs) {
15174
15159
  appName: positionals[0]
15175
15160
  };
15176
15161
  }
15162
+ /**
15163
+ * `cli.deploy` — the event that says whether anyone ever ships.
15164
+ *
15165
+ * Declared in `TelemetryEventName` from the start and recorded nowhere, so a
15166
+ * consenting user reported scaffolding, dev servers and schema pushes and never
15167
+ * the one act the product exists for.
15168
+ *
15169
+ * What it carries is deliberately thin. No project id, no app name, no
15170
+ * deployment id, no URL: those are the "project names" and "URLs" the consent
15171
+ * screen promises are never sent, and a deployment id is a durable handle on
15172
+ * somebody's infrastructure. `frameworkVersion` is *our* version, not the
15173
+ * user's data, and it is the one field that makes the rest legible — a failure
15174
+ * rate means nothing without knowing which release it is a rate for.
15175
+ */
15176
+ async function recordDeploy(o) {
15177
+ await recordEvent("cli.deploy", {
15178
+ followed: o.followed,
15179
+ status: o.status,
15180
+ deduplicated: o.deduplicated,
15181
+ framework_version: o.frameworkVersion ?? null,
15182
+ duration: durationBucket(Date.now() - o.startedAt)
15183
+ }, { projectRoot: process.cwd() });
15184
+ }
15177
15185
  async function deployCommand(rawArgs, projectRef) {
15186
+ const startedAt = Date.now();
15178
15187
  const { flags: args, appName } = resolveDeployArgs(rawArgs);
15179
15188
  if (args["--wait"] && args["--no-follow"]) fail("--wait and --no-follow ask for opposite things.", "`deploy` follows by default — pass neither, or `--no-follow` to return as soon as the build is triggered.", "usage");
15180
15189
  const { client, url } = await requireClient(rawArgs);
@@ -15258,6 +15267,13 @@ async function deployCommand(rawArgs, projectRef) {
15258
15267
  following: false,
15259
15268
  ...warningPayload(warnings)
15260
15269
  });
15270
+ await recordDeploy({
15271
+ followed: false,
15272
+ status: "not_followed",
15273
+ deduplicated,
15274
+ frameworkVersion,
15275
+ startedAt
15276
+ });
15261
15277
  return;
15262
15278
  }
15263
15279
  if (!isJsonMode()) {
@@ -15270,6 +15286,13 @@ async function deployCommand(rawArgs, projectRef) {
15270
15286
  projectId,
15271
15287
  url
15272
15288
  });
15289
+ await recordDeploy({
15290
+ followed: true,
15291
+ status,
15292
+ deduplicated,
15293
+ frameworkVersion,
15294
+ startedAt
15295
+ });
15273
15296
  emit(() => {}, {
15274
15297
  deploymentId,
15275
15298
  deduplicated,
@@ -19079,6 +19102,24 @@ async function entry(args) {
19079
19102
  return;
19080
19103
  }
19081
19104
  const effectiveSubcommand = parsedArgs["--help"] && !subcommand ? "--help" : subcommand;
19105
+ try {
19106
+ await dispatch(command, effectiveSubcommand, args, parsedArgs, namespacedCommands);
19107
+ } catch (error) {
19108
+ await recordEvent("cli.error", {
19109
+ command: command ?? "none",
19110
+ subcommand: effectiveSubcommand ?? "none",
19111
+ error_type: error instanceof Error ? error.constructor.name : "unknown",
19112
+ usage: Boolean(error && typeof error === "object" && error.isUsageError)
19113
+ }, { projectRoot: process.cwd() });
19114
+ throw error;
19115
+ }
19116
+ }
19117
+ /**
19118
+ * The dispatch, lifted out of {@link entry} so the whole of it sits inside one
19119
+ * `try`. Inlining the switch there would have put the `catch` around the flag
19120
+ * parsing too, which throws its own usage errors before a command is even named.
19121
+ */
19122
+ async function dispatch(command, effectiveSubcommand, args, parsedArgs, namespacedCommands) {
19082
19123
  switch (command) {
19083
19124
  case "__dev-db-daemon": {
19084
19125
  const { parseDaemonArgs, runDaemon } = await import("./daemon-entry-LTFKtpHy.js");