@sanity/cli 6.0.0-alpha.6 → 6.0.0-alpha.7

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.
Files changed (68) hide show
  1. package/README.md +2907 -109
  2. package/dist/actions/telemetry/resolveConsent.d.ts +1 -9
  3. package/dist/actions/telemetry/resolveConsent.js +2 -2
  4. package/dist/actions/telemetry/resolveConsent.js.map +1 -1
  5. package/dist/actions/telemetry/setConsent.d.ts +1 -4
  6. package/dist/actions/telemetry/setConsent.js +4 -8
  7. package/dist/actions/telemetry/setConsent.js.map +1 -1
  8. package/dist/commands/telemetry/disable.js +0 -1
  9. package/dist/commands/telemetry/disable.js.map +1 -1
  10. package/dist/commands/telemetry/enable.js +0 -1
  11. package/dist/commands/telemetry/enable.js.map +1 -1
  12. package/dist/commands/telemetry/status.js +1 -3
  13. package/dist/commands/telemetry/status.js.map +1 -1
  14. package/dist/hooks/prerun/flushTelemetry.worker.d.ts +2 -0
  15. package/dist/hooks/prerun/flushTelemetry.worker.js +22 -0
  16. package/dist/hooks/prerun/flushTelemetry.worker.js.map +1 -0
  17. package/dist/hooks/prerun/setupTelemetry.js +65 -1
  18. package/dist/hooks/prerun/setupTelemetry.js.map +1 -1
  19. package/dist/services/telemetry.d.ts +2 -0
  20. package/dist/services/telemetry.js +20 -0
  21. package/dist/services/telemetry.js.map +1 -0
  22. package/dist/telemetry/cli.telemetry.d.ts +20 -0
  23. package/dist/telemetry/cli.telemetry.js +8 -0
  24. package/dist/telemetry/cli.telemetry.js.map +1 -0
  25. package/dist/telemetry/store/cleanupOldTelemetryFiles.d.ts +5 -0
  26. package/dist/telemetry/store/cleanupOldTelemetryFiles.js +30 -0
  27. package/dist/telemetry/store/cleanupOldTelemetryFiles.js.map +1 -0
  28. package/dist/telemetry/store/createTelemetryStore.d.ts +39 -0
  29. package/dist/telemetry/store/createTelemetryStore.js +95 -0
  30. package/dist/telemetry/store/createTelemetryStore.js.map +1 -0
  31. package/dist/telemetry/store/createTraceId.d.ts +10 -0
  32. package/dist/telemetry/store/createTraceId.js +10 -0
  33. package/dist/telemetry/store/createTraceId.js.map +1 -0
  34. package/dist/telemetry/store/debug.d.ts +5 -0
  35. package/dist/telemetry/store/debug.js +7 -0
  36. package/dist/telemetry/store/debug.js.map +1 -0
  37. package/dist/telemetry/store/findTelemetryFiles.d.ts +13 -0
  38. package/dist/telemetry/store/findTelemetryFiles.js +34 -0
  39. package/dist/telemetry/store/findTelemetryFiles.js.map +1 -0
  40. package/dist/telemetry/store/flushTelemetryFiles.d.ts +20 -0
  41. package/dist/telemetry/store/flushTelemetryFiles.js +107 -0
  42. package/dist/telemetry/store/flushTelemetryFiles.js.map +1 -0
  43. package/dist/telemetry/store/generateTelemetryFilePath.d.ts +17 -0
  44. package/dist/telemetry/store/generateTelemetryFilePath.js +30 -0
  45. package/dist/telemetry/store/generateTelemetryFilePath.js.map +1 -0
  46. package/dist/telemetry/store/getTelemetryBaseInfo.d.ts +27 -0
  47. package/dist/telemetry/store/getTelemetryBaseInfo.js +34 -0
  48. package/dist/telemetry/store/getTelemetryBaseInfo.js.map +1 -0
  49. package/dist/telemetry/store/logger.d.ts +6 -0
  50. package/dist/telemetry/store/logger.js +54 -0
  51. package/dist/telemetry/store/logger.js.map +1 -0
  52. package/dist/telemetry/store/trace.d.ts +6 -0
  53. package/dist/telemetry/store/trace.js +150 -0
  54. package/dist/telemetry/store/trace.js.map +1 -0
  55. package/dist/telemetry/utils/readNDJSON.d.ts +10 -0
  56. package/dist/telemetry/utils/readNDJSON.js +18 -0
  57. package/dist/telemetry/utils/readNDJSON.js.map +1 -0
  58. package/dist/util/detectRuntime.d.ts +8 -0
  59. package/dist/util/detectRuntime.js +20 -0
  60. package/dist/util/detectRuntime.js.map +1 -0
  61. package/dist/util/isStaging.d.ts +7 -0
  62. package/dist/util/isStaging.js +10 -0
  63. package/dist/util/isStaging.js.map +1 -0
  64. package/dist/util/parseArguments.d.ts +35 -0
  65. package/dist/util/parseArguments.js +42 -0
  66. package/dist/util/parseArguments.js.map +1 -0
  67. package/oclif.manifest.json +119 -113
  68. package/package.json +15 -12
@@ -1,10 +1,2 @@
1
1
  import { type ConsentInformation } from './types.js';
2
- interface Env {
3
- DO_NOT_TRACK?: string;
4
- SANITY_TELEMETRY_INSPECT?: string;
5
- }
6
- interface Options {
7
- env: Env | NodeJS.ProcessEnv;
8
- }
9
- export declare function resolveConsent({ env }: Options): Promise<ConsentInformation>;
10
- export {};
2
+ export declare function resolveConsent(): Promise<ConsentInformation>;
@@ -8,7 +8,7 @@ function parseApiConsentStatus(value) {
8
8
  }
9
9
  throw new Error(`Invalid consent status. Must be one of: ${VALID_API_STATUSES.join(', ')}`);
10
10
  }
11
- export async function resolveConsent({ env }) {
11
+ export async function resolveConsent() {
12
12
  telemetryDebug('Resolving consent…');
13
13
  if (isCi()) {
14
14
  telemetryDebug('CI environment detected, treating telemetry consent as denied');
@@ -16,7 +16,7 @@ export async function resolveConsent({ env }) {
16
16
  status: 'denied'
17
17
  };
18
18
  }
19
- if (isTrueish(env.DO_NOT_TRACK)) {
19
+ if (isTrueish(process.env.DO_NOT_TRACK)) {
20
20
  telemetryDebug('DO_NOT_TRACK is set, consent is denied');
21
21
  return {
22
22
  reason: 'localOverride',
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/actions/telemetry/resolveConsent.ts"],"sourcesContent":["import {getCliToken, isCi, isTrueish} from '@sanity/cli-core'\n\nimport {fetchTelemetryConsent} from './fetchTelemetryConsent.js'\nimport {\n isValidApiConsentStatus,\n VALID_API_STATUSES,\n type ValidApiConsentStatus,\n} from './isValidApiConsentStatus.js'\nimport {telemetryDebug} from './telemetryDebug.js'\nimport {type ConsentInformation} from './types.js'\n\ninterface Env {\n DO_NOT_TRACK?: string\n SANITY_TELEMETRY_INSPECT?: string\n}\n\ninterface Options {\n env: Env | NodeJS.ProcessEnv\n}\n\nfunction parseApiConsentStatus(value: unknown): ValidApiConsentStatus {\n if (typeof value === 'string' && isValidApiConsentStatus(value)) {\n return value\n }\n throw new Error(`Invalid consent status. Must be one of: ${VALID_API_STATUSES.join(', ')}`)\n}\n\nexport async function resolveConsent({env}: Options): Promise<ConsentInformation> {\n telemetryDebug('Resolving consent…')\n if (isCi()) {\n telemetryDebug('CI environment detected, treating telemetry consent as denied')\n return {status: 'denied'}\n }\n\n if (isTrueish(env.DO_NOT_TRACK)) {\n telemetryDebug('DO_NOT_TRACK is set, consent is denied')\n return {\n reason: 'localOverride',\n status: 'denied',\n }\n }\n\n const token = await getCliToken()\n if (!token) {\n telemetryDebug('User is not logged in, consent is undetermined')\n return {\n reason: 'unauthenticated',\n status: 'undetermined',\n }\n }\n\n try {\n const response = await fetchTelemetryConsent()\n\n telemetryDebug('User consent status is %s', response.status)\n return {status: parseApiConsentStatus(response.status)}\n } catch (err) {\n telemetryDebug('Failed to fetch user consent status, treating it as \"undetermined\": %s', err)\n return {\n reason: 'fetchError',\n status: 'undetermined',\n }\n }\n}\n"],"names":["getCliToken","isCi","isTrueish","fetchTelemetryConsent","isValidApiConsentStatus","VALID_API_STATUSES","telemetryDebug","parseApiConsentStatus","value","Error","join","resolveConsent","env","status","DO_NOT_TRACK","reason","token","response","err"],"mappings":"AAAA,SAAQA,WAAW,EAAEC,IAAI,EAAEC,SAAS,QAAO,mBAAkB;AAE7D,SAAQC,qBAAqB,QAAO,6BAA4B;AAChE,SACEC,uBAAuB,EACvBC,kBAAkB,QAEb,+BAA8B;AACrC,SAAQC,cAAc,QAAO,sBAAqB;AAYlD,SAASC,sBAAsBC,KAAc;IAC3C,IAAI,OAAOA,UAAU,YAAYJ,wBAAwBI,QAAQ;QAC/D,OAAOA;IACT;IACA,MAAM,IAAIC,MAAM,CAAC,wCAAwC,EAAEJ,mBAAmBK,IAAI,CAAC,OAAO;AAC5F;AAEA,OAAO,eAAeC,eAAe,EAACC,GAAG,EAAU;IACjDN,eAAe;IACf,IAAIL,QAAQ;QACVK,eAAe;QACf,OAAO;YAACO,QAAQ;QAAQ;IAC1B;IAEA,IAAIX,UAAUU,IAAIE,YAAY,GAAG;QAC/BR,eAAe;QACf,OAAO;YACLS,QAAQ;YACRF,QAAQ;QACV;IACF;IAEA,MAAMG,QAAQ,MAAMhB;IACpB,IAAI,CAACgB,OAAO;QACVV,eAAe;QACf,OAAO;YACLS,QAAQ;YACRF,QAAQ;QACV;IACF;IAEA,IAAI;QACF,MAAMI,WAAW,MAAMd;QAEvBG,eAAe,6BAA6BW,SAASJ,MAAM;QAC3D,OAAO;YAACA,QAAQN,sBAAsBU,SAASJ,MAAM;QAAC;IACxD,EAAE,OAAOK,KAAK;QACZZ,eAAe,0EAA0EY;QACzF,OAAO;YACLH,QAAQ;YACRF,QAAQ;QACV;IACF;AACF"}
1
+ {"version":3,"sources":["../../../src/actions/telemetry/resolveConsent.ts"],"sourcesContent":["import {getCliToken, isCi, isTrueish} from '@sanity/cli-core'\n\nimport {fetchTelemetryConsent} from './fetchTelemetryConsent.js'\nimport {\n isValidApiConsentStatus,\n VALID_API_STATUSES,\n type ValidApiConsentStatus,\n} from './isValidApiConsentStatus.js'\nimport {telemetryDebug} from './telemetryDebug.js'\nimport {type ConsentInformation} from './types.js'\n\nfunction parseApiConsentStatus(value: unknown): ValidApiConsentStatus {\n if (typeof value === 'string' && isValidApiConsentStatus(value)) {\n return value\n }\n throw new Error(`Invalid consent status. Must be one of: ${VALID_API_STATUSES.join(', ')}`)\n}\n\nexport async function resolveConsent(): Promise<ConsentInformation> {\n telemetryDebug('Resolving consent…')\n if (isCi()) {\n telemetryDebug('CI environment detected, treating telemetry consent as denied')\n return {status: 'denied'}\n }\n\n if (isTrueish(process.env.DO_NOT_TRACK)) {\n telemetryDebug('DO_NOT_TRACK is set, consent is denied')\n return {\n reason: 'localOverride',\n status: 'denied',\n }\n }\n\n const token = await getCliToken()\n if (!token) {\n telemetryDebug('User is not logged in, consent is undetermined')\n return {\n reason: 'unauthenticated',\n status: 'undetermined',\n }\n }\n\n try {\n const response = await fetchTelemetryConsent()\n\n telemetryDebug('User consent status is %s', response.status)\n return {status: parseApiConsentStatus(response.status)}\n } catch (err) {\n telemetryDebug('Failed to fetch user consent status, treating it as \"undetermined\": %s', err)\n return {\n reason: 'fetchError',\n status: 'undetermined',\n }\n }\n}\n"],"names":["getCliToken","isCi","isTrueish","fetchTelemetryConsent","isValidApiConsentStatus","VALID_API_STATUSES","telemetryDebug","parseApiConsentStatus","value","Error","join","resolveConsent","status","process","env","DO_NOT_TRACK","reason","token","response","err"],"mappings":"AAAA,SAAQA,WAAW,EAAEC,IAAI,EAAEC,SAAS,QAAO,mBAAkB;AAE7D,SAAQC,qBAAqB,QAAO,6BAA4B;AAChE,SACEC,uBAAuB,EACvBC,kBAAkB,QAEb,+BAA8B;AACrC,SAAQC,cAAc,QAAO,sBAAqB;AAGlD,SAASC,sBAAsBC,KAAc;IAC3C,IAAI,OAAOA,UAAU,YAAYJ,wBAAwBI,QAAQ;QAC/D,OAAOA;IACT;IACA,MAAM,IAAIC,MAAM,CAAC,wCAAwC,EAAEJ,mBAAmBK,IAAI,CAAC,OAAO;AAC5F;AAEA,OAAO,eAAeC;IACpBL,eAAe;IACf,IAAIL,QAAQ;QACVK,eAAe;QACf,OAAO;YAACM,QAAQ;QAAQ;IAC1B;IAEA,IAAIV,UAAUW,QAAQC,GAAG,CAACC,YAAY,GAAG;QACvCT,eAAe;QACf,OAAO;YACLU,QAAQ;YACRJ,QAAQ;QACV;IACF;IAEA,MAAMK,QAAQ,MAAMjB;IACpB,IAAI,CAACiB,OAAO;QACVX,eAAe;QACf,OAAO;YACLU,QAAQ;YACRJ,QAAQ;QACV;IACF;IAEA,IAAI;QACF,MAAMM,WAAW,MAAMf;QAEvBG,eAAe,6BAA6BY,SAASN,MAAM;QAC3D,OAAO;YAACA,QAAQL,sBAAsBW,SAASN,MAAM;QAAC;IACxD,EAAE,OAAOO,KAAK;QACZb,eAAe,0EAA0Ea;QACzF,OAAO;YACLH,QAAQ;YACRJ,QAAQ;QACV;IACF;AACF"}
@@ -2,9 +2,6 @@ import { type ValidApiConsentStatus } from './isValidApiConsentStatus.js';
2
2
  import { type ConsentInformation } from './types.js';
3
3
  type SettableConsentStatus = Extract<ValidApiConsentStatus, 'denied' | 'granted'>;
4
4
  interface SetConsentOptions {
5
- env: NodeJS.ProcessEnv | {
6
- [key: string]: string | undefined;
7
- };
8
5
  status: SettableConsentStatus;
9
6
  }
10
7
  interface SetConsentResult {
@@ -12,5 +9,5 @@ interface SetConsentResult {
12
9
  currentStatus: ConsentInformation;
13
10
  message: string;
14
11
  }
15
- export declare function setConsent({ env, status }: SetConsentOptions): Promise<SetConsentResult>;
12
+ export declare function setConsent({ status }: SetConsentOptions): Promise<SetConsentResult>;
16
13
  export {};
@@ -6,12 +6,10 @@ const TELEMETRY_CONSENT_CONFIG_KEY = 'telemetryConsent';
6
6
  function isHttpError(error) {
7
7
  return typeof error === 'object' && error !== null && 'statusCode' in error && 'message' in error;
8
8
  }
9
- export async function setConsent({ env, status }) {
9
+ export async function setConsent({ status }) {
10
10
  telemetryDebug('Setting telemetry consent to "%s"', status);
11
11
  // Check current consent status first
12
- const currentConsent = await resolveConsent({
13
- env
14
- });
12
+ const currentConsent = await resolveConsent();
15
13
  // Handle various blocking conditions
16
14
  if (isCi()) {
17
15
  return {
@@ -20,7 +18,7 @@ export async function setConsent({ env, status }) {
20
18
  message: 'Cannot set telemetry consent in CI environment'
21
19
  };
22
20
  }
23
- if (isTrueish(env.DO_NOT_TRACK) && status === 'granted') {
21
+ if (isTrueish(process.env.DO_NOT_TRACK) && status === 'granted') {
24
22
  return {
25
23
  changed: false,
26
24
  currentStatus: currentConsent,
@@ -59,9 +57,7 @@ export async function setConsent({ env, status }) {
59
57
  const userConfig = getUserConfig();
60
58
  userConfig.delete(TELEMETRY_CONSENT_CONFIG_KEY);
61
59
  const successMessage = status === 'granted' ? "You've now enabled telemetry data collection to help us improve Sanity." : "You've opted out of telemetry data collection.\nNo data will be collected from your Sanity account.";
62
- const newConsent = await resolveConsent({
63
- env
64
- });
60
+ const newConsent = await resolveConsent();
65
61
  return {
66
62
  changed: true,
67
63
  currentStatus: newConsent,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/actions/telemetry/setConsent.ts"],"sourcesContent":["import {getGlobalCliClient, getUserConfig, isCi, isTrueish} from '@sanity/cli-core'\n\nimport {type ValidApiConsentStatus} from './isValidApiConsentStatus.js'\nimport {resolveConsent} from './resolveConsent.js'\nimport {telemetryDebug} from './telemetryDebug.js'\nimport {type ConsentInformation} from './types.js'\n\nconst TELEMETRY_CONSENT_CONFIG_KEY = 'telemetryConsent'\n\ntype SettableConsentStatus = Extract<ValidApiConsentStatus, 'denied' | 'granted'>\n\n// Type guard for error objects with HTTP properties\nfunction isHttpError(error: unknown): error is {\n message: string\n response?: {body?: {message?: string}}\n statusCode: number\n} {\n return typeof error === 'object' && error !== null && 'statusCode' in error && 'message' in error\n}\n\ninterface SetConsentOptions {\n env: NodeJS.ProcessEnv | {[key: string]: string | undefined}\n status: SettableConsentStatus\n}\n\ninterface SetConsentResult {\n changed: boolean\n currentStatus: ConsentInformation\n message: string\n}\n\nexport async function setConsent({env, status}: SetConsentOptions): Promise<SetConsentResult> {\n telemetryDebug('Setting telemetry consent to \"%s\"', status)\n\n // Check current consent status first\n const currentConsent = await resolveConsent({env})\n\n // Handle various blocking conditions\n if (isCi()) {\n return {\n changed: false,\n currentStatus: currentConsent,\n message: 'Cannot set telemetry consent in CI environment',\n }\n }\n\n if (isTrueish(env.DO_NOT_TRACK) && status === 'granted') {\n return {\n changed: false,\n currentStatus: currentConsent,\n message:\n 'Cannot enable telemetry while DO_NOT_TRACK environment variable is set. Unset DO_NOT_TRACK to enable telemetry.',\n }\n }\n\n // Check if already at desired status\n if (currentConsent.status === status) {\n const message =\n status === 'granted'\n ? \"You've already enabled telemetry data collection to help us improve Sanity.\"\n : currentConsent.reason === 'localOverride'\n ? \"You've already opted out of telemetry data collection.\\nNo data is collected from your machine.\\n\\nUsing DO_NOT_TRACK environment variable.\"\n : \"You've already opted out of telemetry data collection.\\nNo data is collected from your Sanity account.\"\n\n return {\n changed: false,\n currentStatus: currentConsent,\n message,\n }\n }\n\n // User must be logged in to set consent\n if (currentConsent.status === 'undetermined' && currentConsent.reason === 'unauthenticated') {\n return {\n changed: false,\n currentStatus: currentConsent,\n message: 'You need to log in first to set telemetry preferences.',\n }\n }\n\n try {\n const client = await getGlobalCliClient({\n apiVersion: '2023-12-18',\n requireUser: true,\n })\n\n const uri = `/users/me/consents/telemetry/status/${status}`\n telemetryDebug('Sending telemetry consent status to %s', uri)\n\n await client.request({\n method: 'PUT',\n uri,\n })\n\n // Clear cached telemetry consent\n const userConfig = getUserConfig()\n userConfig.delete(TELEMETRY_CONSENT_CONFIG_KEY)\n\n const successMessage =\n status === 'granted'\n ? \"You've now enabled telemetry data collection to help us improve Sanity.\"\n : \"You've opted out of telemetry data collection.\\nNo data will be collected from your Sanity account.\"\n\n const newConsent = await resolveConsent({env})\n\n return {\n changed: true,\n currentStatus: newConsent,\n message: successMessage,\n }\n } catch (err: unknown) {\n const errorMessage = `Failed to ${status === 'granted' ? 'enable' : 'disable'} telemetry`\n\n if (isHttpError(err) && err.statusCode === 403) {\n // Create a new error without stack trace from original error\n const message = err.response?.body?.message\n ? `${errorMessage}: ${err.response.body.message}`\n : errorMessage\n throw new Error(message)\n }\n\n if (isHttpError(err)) {\n // For other errors, preserve the original error but update the message\n err.message = err.response?.body?.message\n ? `${errorMessage}: ${err.response.body.message}`\n : errorMessage\n throw err\n }\n\n // For non-HTTP errors, wrap in a new error\n throw new Error(errorMessage)\n }\n}\n"],"names":["getGlobalCliClient","getUserConfig","isCi","isTrueish","resolveConsent","telemetryDebug","TELEMETRY_CONSENT_CONFIG_KEY","isHttpError","error","setConsent","env","status","currentConsent","changed","currentStatus","message","DO_NOT_TRACK","reason","client","apiVersion","requireUser","uri","request","method","userConfig","delete","successMessage","newConsent","err","errorMessage","statusCode","response","body","Error"],"mappings":"AAAA,SAAQA,kBAAkB,EAAEC,aAAa,EAAEC,IAAI,EAAEC,SAAS,QAAO,mBAAkB;AAGnF,SAAQC,cAAc,QAAO,sBAAqB;AAClD,SAAQC,cAAc,QAAO,sBAAqB;AAGlD,MAAMC,+BAA+B;AAIrC,oDAAoD;AACpD,SAASC,YAAYC,KAAc;IAKjC,OAAO,OAAOA,UAAU,YAAYA,UAAU,QAAQ,gBAAgBA,SAAS,aAAaA;AAC9F;AAaA,OAAO,eAAeC,WAAW,EAACC,GAAG,EAAEC,MAAM,EAAoB;IAC/DN,eAAe,qCAAqCM;IAEpD,qCAAqC;IACrC,MAAMC,iBAAiB,MAAMR,eAAe;QAACM;IAAG;IAEhD,qCAAqC;IACrC,IAAIR,QAAQ;QACV,OAAO;YACLW,SAAS;YACTC,eAAeF;YACfG,SAAS;QACX;IACF;IAEA,IAAIZ,UAAUO,IAAIM,YAAY,KAAKL,WAAW,WAAW;QACvD,OAAO;YACLE,SAAS;YACTC,eAAeF;YACfG,SACE;QACJ;IACF;IAEA,qCAAqC;IACrC,IAAIH,eAAeD,MAAM,KAAKA,QAAQ;QACpC,MAAMI,UACJJ,WAAW,YACP,gFACAC,eAAeK,MAAM,KAAK,kBACxB,gJACA;QAER,OAAO;YACLJ,SAAS;YACTC,eAAeF;YACfG;QACF;IACF;IAEA,wCAAwC;IACxC,IAAIH,eAAeD,MAAM,KAAK,kBAAkBC,eAAeK,MAAM,KAAK,mBAAmB;QAC3F,OAAO;YACLJ,SAAS;YACTC,eAAeF;YACfG,SAAS;QACX;IACF;IAEA,IAAI;QACF,MAAMG,SAAS,MAAMlB,mBAAmB;YACtCmB,YAAY;YACZC,aAAa;QACf;QAEA,MAAMC,MAAM,CAAC,oCAAoC,EAAEV,QAAQ;QAC3DN,eAAe,0CAA0CgB;QAEzD,MAAMH,OAAOI,OAAO,CAAC;YACnBC,QAAQ;YACRF;QACF;QAEA,iCAAiC;QACjC,MAAMG,aAAavB;QACnBuB,WAAWC,MAAM,CAACnB;QAElB,MAAMoB,iBACJf,WAAW,YACP,4EACA;QAEN,MAAMgB,aAAa,MAAMvB,eAAe;YAACM;QAAG;QAE5C,OAAO;YACLG,SAAS;YACTC,eAAea;YACfZ,SAASW;QACX;IACF,EAAE,OAAOE,KAAc;QACrB,MAAMC,eAAe,CAAC,UAAU,EAAElB,WAAW,YAAY,WAAW,UAAU,UAAU,CAAC;QAEzF,IAAIJ,YAAYqB,QAAQA,IAAIE,UAAU,KAAK,KAAK;YAC9C,6DAA6D;YAC7D,MAAMf,UAAUa,IAAIG,QAAQ,EAAEC,MAAMjB,UAChC,GAAGc,aAAa,EAAE,EAAED,IAAIG,QAAQ,CAACC,IAAI,CAACjB,OAAO,EAAE,GAC/Cc;YACJ,MAAM,IAAII,MAAMlB;QAClB;QAEA,IAAIR,YAAYqB,MAAM;YACpB,uEAAuE;YACvEA,IAAIb,OAAO,GAAGa,IAAIG,QAAQ,EAAEC,MAAMjB,UAC9B,GAAGc,aAAa,EAAE,EAAED,IAAIG,QAAQ,CAACC,IAAI,CAACjB,OAAO,EAAE,GAC/Cc;YACJ,MAAMD;QACR;QAEA,2CAA2C;QAC3C,MAAM,IAAIK,MAAMJ;IAClB;AACF"}
1
+ {"version":3,"sources":["../../../src/actions/telemetry/setConsent.ts"],"sourcesContent":["import {getGlobalCliClient, getUserConfig, isCi, isTrueish} from '@sanity/cli-core'\n\nimport {type ValidApiConsentStatus} from './isValidApiConsentStatus.js'\nimport {resolveConsent} from './resolveConsent.js'\nimport {telemetryDebug} from './telemetryDebug.js'\nimport {type ConsentInformation} from './types.js'\n\nconst TELEMETRY_CONSENT_CONFIG_KEY = 'telemetryConsent'\n\ntype SettableConsentStatus = Extract<ValidApiConsentStatus, 'denied' | 'granted'>\n\n// Type guard for error objects with HTTP properties\nfunction isHttpError(error: unknown): error is {\n message: string\n response?: {body?: {message?: string}}\n statusCode: number\n} {\n return typeof error === 'object' && error !== null && 'statusCode' in error && 'message' in error\n}\n\ninterface SetConsentOptions {\n status: SettableConsentStatus\n}\n\ninterface SetConsentResult {\n changed: boolean\n currentStatus: ConsentInformation\n message: string\n}\n\nexport async function setConsent({status}: SetConsentOptions): Promise<SetConsentResult> {\n telemetryDebug('Setting telemetry consent to \"%s\"', status)\n\n // Check current consent status first\n const currentConsent = await resolveConsent()\n\n // Handle various blocking conditions\n if (isCi()) {\n return {\n changed: false,\n currentStatus: currentConsent,\n message: 'Cannot set telemetry consent in CI environment',\n }\n }\n\n if (isTrueish(process.env.DO_NOT_TRACK) && status === 'granted') {\n return {\n changed: false,\n currentStatus: currentConsent,\n message:\n 'Cannot enable telemetry while DO_NOT_TRACK environment variable is set. Unset DO_NOT_TRACK to enable telemetry.',\n }\n }\n\n // Check if already at desired status\n if (currentConsent.status === status) {\n const message =\n status === 'granted'\n ? \"You've already enabled telemetry data collection to help us improve Sanity.\"\n : currentConsent.reason === 'localOverride'\n ? \"You've already opted out of telemetry data collection.\\nNo data is collected from your machine.\\n\\nUsing DO_NOT_TRACK environment variable.\"\n : \"You've already opted out of telemetry data collection.\\nNo data is collected from your Sanity account.\"\n\n return {\n changed: false,\n currentStatus: currentConsent,\n message,\n }\n }\n\n // User must be logged in to set consent\n if (currentConsent.status === 'undetermined' && currentConsent.reason === 'unauthenticated') {\n return {\n changed: false,\n currentStatus: currentConsent,\n message: 'You need to log in first to set telemetry preferences.',\n }\n }\n\n try {\n const client = await getGlobalCliClient({\n apiVersion: '2023-12-18',\n requireUser: true,\n })\n\n const uri = `/users/me/consents/telemetry/status/${status}`\n telemetryDebug('Sending telemetry consent status to %s', uri)\n\n await client.request({\n method: 'PUT',\n uri,\n })\n\n // Clear cached telemetry consent\n const userConfig = getUserConfig()\n userConfig.delete(TELEMETRY_CONSENT_CONFIG_KEY)\n\n const successMessage =\n status === 'granted'\n ? \"You've now enabled telemetry data collection to help us improve Sanity.\"\n : \"You've opted out of telemetry data collection.\\nNo data will be collected from your Sanity account.\"\n\n const newConsent = await resolveConsent()\n\n return {\n changed: true,\n currentStatus: newConsent,\n message: successMessage,\n }\n } catch (err: unknown) {\n const errorMessage = `Failed to ${status === 'granted' ? 'enable' : 'disable'} telemetry`\n\n if (isHttpError(err) && err.statusCode === 403) {\n // Create a new error without stack trace from original error\n const message = err.response?.body?.message\n ? `${errorMessage}: ${err.response.body.message}`\n : errorMessage\n throw new Error(message)\n }\n\n if (isHttpError(err)) {\n // For other errors, preserve the original error but update the message\n err.message = err.response?.body?.message\n ? `${errorMessage}: ${err.response.body.message}`\n : errorMessage\n throw err\n }\n\n // For non-HTTP errors, wrap in a new error\n throw new Error(errorMessage)\n }\n}\n"],"names":["getGlobalCliClient","getUserConfig","isCi","isTrueish","resolveConsent","telemetryDebug","TELEMETRY_CONSENT_CONFIG_KEY","isHttpError","error","setConsent","status","currentConsent","changed","currentStatus","message","process","env","DO_NOT_TRACK","reason","client","apiVersion","requireUser","uri","request","method","userConfig","delete","successMessage","newConsent","err","errorMessage","statusCode","response","body","Error"],"mappings":"AAAA,SAAQA,kBAAkB,EAAEC,aAAa,EAAEC,IAAI,EAAEC,SAAS,QAAO,mBAAkB;AAGnF,SAAQC,cAAc,QAAO,sBAAqB;AAClD,SAAQC,cAAc,QAAO,sBAAqB;AAGlD,MAAMC,+BAA+B;AAIrC,oDAAoD;AACpD,SAASC,YAAYC,KAAc;IAKjC,OAAO,OAAOA,UAAU,YAAYA,UAAU,QAAQ,gBAAgBA,SAAS,aAAaA;AAC9F;AAYA,OAAO,eAAeC,WAAW,EAACC,MAAM,EAAoB;IAC1DL,eAAe,qCAAqCK;IAEpD,qCAAqC;IACrC,MAAMC,iBAAiB,MAAMP;IAE7B,qCAAqC;IACrC,IAAIF,QAAQ;QACV,OAAO;YACLU,SAAS;YACTC,eAAeF;YACfG,SAAS;QACX;IACF;IAEA,IAAIX,UAAUY,QAAQC,GAAG,CAACC,YAAY,KAAKP,WAAW,WAAW;QAC/D,OAAO;YACLE,SAAS;YACTC,eAAeF;YACfG,SACE;QACJ;IACF;IAEA,qCAAqC;IACrC,IAAIH,eAAeD,MAAM,KAAKA,QAAQ;QACpC,MAAMI,UACJJ,WAAW,YACP,gFACAC,eAAeO,MAAM,KAAK,kBACxB,gJACA;QAER,OAAO;YACLN,SAAS;YACTC,eAAeF;YACfG;QACF;IACF;IAEA,wCAAwC;IACxC,IAAIH,eAAeD,MAAM,KAAK,kBAAkBC,eAAeO,MAAM,KAAK,mBAAmB;QAC3F,OAAO;YACLN,SAAS;YACTC,eAAeF;YACfG,SAAS;QACX;IACF;IAEA,IAAI;QACF,MAAMK,SAAS,MAAMnB,mBAAmB;YACtCoB,YAAY;YACZC,aAAa;QACf;QAEA,MAAMC,MAAM,CAAC,oCAAoC,EAAEZ,QAAQ;QAC3DL,eAAe,0CAA0CiB;QAEzD,MAAMH,OAAOI,OAAO,CAAC;YACnBC,QAAQ;YACRF;QACF;QAEA,iCAAiC;QACjC,MAAMG,aAAaxB;QACnBwB,WAAWC,MAAM,CAACpB;QAElB,MAAMqB,iBACJjB,WAAW,YACP,4EACA;QAEN,MAAMkB,aAAa,MAAMxB;QAEzB,OAAO;YACLQ,SAAS;YACTC,eAAee;YACfd,SAASa;QACX;IACF,EAAE,OAAOE,KAAc;QACrB,MAAMC,eAAe,CAAC,UAAU,EAAEpB,WAAW,YAAY,WAAW,UAAU,UAAU,CAAC;QAEzF,IAAIH,YAAYsB,QAAQA,IAAIE,UAAU,KAAK,KAAK;YAC9C,6DAA6D;YAC7D,MAAMjB,UAAUe,IAAIG,QAAQ,EAAEC,MAAMnB,UAChC,GAAGgB,aAAa,EAAE,EAAED,IAAIG,QAAQ,CAACC,IAAI,CAACnB,OAAO,EAAE,GAC/CgB;YACJ,MAAM,IAAII,MAAMpB;QAClB;QAEA,IAAIP,YAAYsB,MAAM;YACpB,uEAAuE;YACvEA,IAAIf,OAAO,GAAGe,IAAIG,QAAQ,EAAEC,MAAMnB,UAC9B,GAAGgB,aAAa,EAAE,EAAED,IAAIG,QAAQ,CAACC,IAAI,CAACnB,OAAO,EAAE,GAC/CgB;YACJ,MAAMD;QACR;QAEA,2CAA2C;QAC3C,MAAM,IAAIK,MAAMJ;IAClB;AACF"}
@@ -15,7 +15,6 @@ export class Disable extends SanityCommand {
15
15
  await this.parse(Disable);
16
16
  try {
17
17
  const result = await setConsent({
18
- env: process.env,
19
18
  status: 'denied'
20
19
  });
21
20
  this.log(result.message);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/commands/telemetry/disable.ts"],"sourcesContent":["import {Command} from '@oclif/core'\nimport {type FlagInput} from '@oclif/core/interfaces'\nimport {SanityCommand} from '@sanity/cli-core'\n\nimport {setConsent} from '../../actions/telemetry/setConsent.js'\nimport {telemetryLearnMoreMessage} from '../../actions/telemetry/telemetryLearnMoreMessage.js'\n\nexport class Disable extends SanityCommand<typeof Disable> {\n static override description = 'Disable telemetry for your logged in user'\n\n static override examples: Array<Command.Example> = [\n {\n command: '<%= config.bin %> telemetry <%= command.id %>',\n description: 'Disable telemetry for your logged in user',\n },\n ]\n\n static override flags = {} satisfies FlagInput\n\n public async run(): Promise<void> {\n // Parse to ensure no invalid flags are passed\n await this.parse(Disable)\n\n try {\n const result = await setConsent({\n env: process.env,\n status: 'denied',\n })\n\n this.log(result.message)\n\n if (result.changed) {\n this.log(`\\n${telemetryLearnMoreMessage('denied')}`)\n }\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : 'An unknown error occurred'\n this.error(message, {exit: 1})\n }\n }\n}\n"],"names":["SanityCommand","setConsent","telemetryLearnMoreMessage","Disable","description","examples","command","flags","run","parse","result","env","process","status","log","message","changed","err","Error","error","exit"],"mappings":"AAEA,SAAQA,aAAa,QAAO,mBAAkB;AAE9C,SAAQC,UAAU,QAAO,wCAAuC;AAChE,SAAQC,yBAAyB,QAAO,uDAAsD;AAE9F,OAAO,MAAMC,gBAAgBH;IAC3B,OAAgBI,cAAc,4CAA2C;IAEzE,OAAgBC,WAAmC;QACjD;YACEC,SAAS;YACTF,aAAa;QACf;KACD,CAAA;IAED,OAAgBG,QAAQ,CAAC,EAAqB;IAE9C,MAAaC,MAAqB;QAChC,8CAA8C;QAC9C,MAAM,IAAI,CAACC,KAAK,CAACN;QAEjB,IAAI;YACF,MAAMO,SAAS,MAAMT,WAAW;gBAC9BU,KAAKC,QAAQD,GAAG;gBAChBE,QAAQ;YACV;YAEA,IAAI,CAACC,GAAG,CAACJ,OAAOK,OAAO;YAEvB,IAAIL,OAAOM,OAAO,EAAE;gBAClB,IAAI,CAACF,GAAG,CAAC,CAAC,EAAE,EAAEZ,0BAA0B,WAAW;YACrD;QACF,EAAE,OAAOe,KAAc;YACrB,MAAMF,UAAUE,eAAeC,QAAQD,IAAIF,OAAO,GAAG;YACrD,IAAI,CAACI,KAAK,CAACJ,SAAS;gBAACK,MAAM;YAAC;QAC9B;IACF;AACF"}
1
+ {"version":3,"sources":["../../../src/commands/telemetry/disable.ts"],"sourcesContent":["import {Command} from '@oclif/core'\nimport {type FlagInput} from '@oclif/core/interfaces'\nimport {SanityCommand} from '@sanity/cli-core'\n\nimport {setConsent} from '../../actions/telemetry/setConsent.js'\nimport {telemetryLearnMoreMessage} from '../../actions/telemetry/telemetryLearnMoreMessage.js'\n\nexport class Disable extends SanityCommand<typeof Disable> {\n static override description = 'Disable telemetry for your logged in user'\n\n static override examples: Array<Command.Example> = [\n {\n command: '<%= config.bin %> telemetry <%= command.id %>',\n description: 'Disable telemetry for your logged in user',\n },\n ]\n\n static override flags = {} satisfies FlagInput\n\n public async run(): Promise<void> {\n // Parse to ensure no invalid flags are passed\n await this.parse(Disable)\n\n try {\n const result = await setConsent({\n status: 'denied',\n })\n\n this.log(result.message)\n\n if (result.changed) {\n this.log(`\\n${telemetryLearnMoreMessage('denied')}`)\n }\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : 'An unknown error occurred'\n this.error(message, {exit: 1})\n }\n }\n}\n"],"names":["SanityCommand","setConsent","telemetryLearnMoreMessage","Disable","description","examples","command","flags","run","parse","result","status","log","message","changed","err","Error","error","exit"],"mappings":"AAEA,SAAQA,aAAa,QAAO,mBAAkB;AAE9C,SAAQC,UAAU,QAAO,wCAAuC;AAChE,SAAQC,yBAAyB,QAAO,uDAAsD;AAE9F,OAAO,MAAMC,gBAAgBH;IAC3B,OAAgBI,cAAc,4CAA2C;IAEzE,OAAgBC,WAAmC;QACjD;YACEC,SAAS;YACTF,aAAa;QACf;KACD,CAAA;IAED,OAAgBG,QAAQ,CAAC,EAAqB;IAE9C,MAAaC,MAAqB;QAChC,8CAA8C;QAC9C,MAAM,IAAI,CAACC,KAAK,CAACN;QAEjB,IAAI;YACF,MAAMO,SAAS,MAAMT,WAAW;gBAC9BU,QAAQ;YACV;YAEA,IAAI,CAACC,GAAG,CAACF,OAAOG,OAAO;YAEvB,IAAIH,OAAOI,OAAO,EAAE;gBAClB,IAAI,CAACF,GAAG,CAAC,CAAC,EAAE,EAAEV,0BAA0B,WAAW;YACrD;QACF,EAAE,OAAOa,KAAc;YACrB,MAAMF,UAAUE,eAAeC,QAAQD,IAAIF,OAAO,GAAG;YACrD,IAAI,CAACI,KAAK,CAACJ,SAAS;gBAACK,MAAM;YAAC;QAC9B;IACF;AACF"}
@@ -15,7 +15,6 @@ export class Enable extends SanityCommand {
15
15
  await this.parse(Enable);
16
16
  try {
17
17
  const result = await setConsent({
18
- env: process.env,
19
18
  status: 'granted'
20
19
  });
21
20
  this.log(result.message);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/commands/telemetry/enable.ts"],"sourcesContent":["import {Command} from '@oclif/core'\nimport {type FlagInput} from '@oclif/core/interfaces'\nimport {SanityCommand} from '@sanity/cli-core'\n\nimport {setConsent} from '../../actions/telemetry/setConsent.js'\nimport {telemetryLearnMoreMessage} from '../../actions/telemetry/telemetryLearnMoreMessage.js'\n\nexport class Enable extends SanityCommand<typeof Enable> {\n static override description = 'Enable telemetry for your logged in user'\n\n static override examples: Array<Command.Example> = [\n {\n command: '<%= config.bin %> telemetry <%= command.id %>',\n description: 'Enable telemetry for your logged in user',\n },\n ]\n\n static override flags = {} satisfies FlagInput\n\n public async run(): Promise<void> {\n // Parse to ensure no invalid flags are passed\n await this.parse(Enable)\n\n try {\n const result = await setConsent({\n env: process.env,\n status: 'granted',\n })\n\n this.log(result.message)\n\n if (result.changed) {\n this.log(`\\n${telemetryLearnMoreMessage('granted')}`)\n }\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : 'An unknown error occurred'\n this.error(message, {exit: 1})\n }\n }\n}\n"],"names":["SanityCommand","setConsent","telemetryLearnMoreMessage","Enable","description","examples","command","flags","run","parse","result","env","process","status","log","message","changed","err","Error","error","exit"],"mappings":"AAEA,SAAQA,aAAa,QAAO,mBAAkB;AAE9C,SAAQC,UAAU,QAAO,wCAAuC;AAChE,SAAQC,yBAAyB,QAAO,uDAAsD;AAE9F,OAAO,MAAMC,eAAeH;IAC1B,OAAgBI,cAAc,2CAA0C;IAExE,OAAgBC,WAAmC;QACjD;YACEC,SAAS;YACTF,aAAa;QACf;KACD,CAAA;IAED,OAAgBG,QAAQ,CAAC,EAAqB;IAE9C,MAAaC,MAAqB;QAChC,8CAA8C;QAC9C,MAAM,IAAI,CAACC,KAAK,CAACN;QAEjB,IAAI;YACF,MAAMO,SAAS,MAAMT,WAAW;gBAC9BU,KAAKC,QAAQD,GAAG;gBAChBE,QAAQ;YACV;YAEA,IAAI,CAACC,GAAG,CAACJ,OAAOK,OAAO;YAEvB,IAAIL,OAAOM,OAAO,EAAE;gBAClB,IAAI,CAACF,GAAG,CAAC,CAAC,EAAE,EAAEZ,0BAA0B,YAAY;YACtD;QACF,EAAE,OAAOe,KAAc;YACrB,MAAMF,UAAUE,eAAeC,QAAQD,IAAIF,OAAO,GAAG;YACrD,IAAI,CAACI,KAAK,CAACJ,SAAS;gBAACK,MAAM;YAAC;QAC9B;IACF;AACF"}
1
+ {"version":3,"sources":["../../../src/commands/telemetry/enable.ts"],"sourcesContent":["import {Command} from '@oclif/core'\nimport {type FlagInput} from '@oclif/core/interfaces'\nimport {SanityCommand} from '@sanity/cli-core'\n\nimport {setConsent} from '../../actions/telemetry/setConsent.js'\nimport {telemetryLearnMoreMessage} from '../../actions/telemetry/telemetryLearnMoreMessage.js'\n\nexport class Enable extends SanityCommand<typeof Enable> {\n static override description = 'Enable telemetry for your logged in user'\n\n static override examples: Array<Command.Example> = [\n {\n command: '<%= config.bin %> telemetry <%= command.id %>',\n description: 'Enable telemetry for your logged in user',\n },\n ]\n\n static override flags = {} satisfies FlagInput\n\n public async run(): Promise<void> {\n // Parse to ensure no invalid flags are passed\n await this.parse(Enable)\n\n try {\n const result = await setConsent({\n status: 'granted',\n })\n\n this.log(result.message)\n\n if (result.changed) {\n this.log(`\\n${telemetryLearnMoreMessage('granted')}`)\n }\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : 'An unknown error occurred'\n this.error(message, {exit: 1})\n }\n }\n}\n"],"names":["SanityCommand","setConsent","telemetryLearnMoreMessage","Enable","description","examples","command","flags","run","parse","result","status","log","message","changed","err","Error","error","exit"],"mappings":"AAEA,SAAQA,aAAa,QAAO,mBAAkB;AAE9C,SAAQC,UAAU,QAAO,wCAAuC;AAChE,SAAQC,yBAAyB,QAAO,uDAAsD;AAE9F,OAAO,MAAMC,eAAeH;IAC1B,OAAgBI,cAAc,2CAA0C;IAExE,OAAgBC,WAAmC;QACjD;YACEC,SAAS;YACTF,aAAa;QACf;KACD,CAAA;IAED,OAAgBG,QAAQ,CAAC,EAAqB;IAE9C,MAAaC,MAAqB;QAChC,8CAA8C;QAC9C,MAAM,IAAI,CAACC,KAAK,CAACN;QAEjB,IAAI;YACF,MAAMO,SAAS,MAAMT,WAAW;gBAC9BU,QAAQ;YACV;YAEA,IAAI,CAACC,GAAG,CAACF,OAAOG,OAAO;YAEvB,IAAIH,OAAOI,OAAO,EAAE;gBAClB,IAAI,CAACF,GAAG,CAAC,CAAC,EAAE,EAAEV,0BAA0B,YAAY;YACtD;QACF,EAAE,OAAOa,KAAc;YACrB,MAAMF,UAAUE,eAAeC,QAAQD,IAAIF,OAAO,GAAG;YACrD,IAAI,CAACI,KAAK,CAACJ,SAAS;gBAACK,MAAM;YAAC;QAC9B;IACF;AACF"}
@@ -14,9 +14,7 @@ export class Status extends SanityCommand {
14
14
  async run() {
15
15
  // Parse to ensure no invalid flags are passed
16
16
  await this.parse(Status);
17
- const consentInfo = await resolveConsent({
18
- env: process.env
19
- });
17
+ const consentInfo = await resolveConsent();
20
18
  const statusMessage = getStatusMessage(consentInfo);
21
19
  const learnMoreMessage = getLearnMoreMessage(consentInfo.status);
22
20
  this.log(statusMessage);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/commands/telemetry/status.ts"],"sourcesContent":["import {Command} from '@oclif/core'\nimport {type FlagInput} from '@oclif/core/interfaces'\nimport {SanityCommand} from '@sanity/cli-core'\n\nimport {getLearnMoreMessage} from '../../actions/telemetry/getLearnMoreMessage.js'\nimport {getStatusMessage} from '../../actions/telemetry/getStatusMessage.js'\nimport {resolveConsent} from '../../actions/telemetry/resolveConsent.js'\n\nexport class Status extends SanityCommand<typeof Status> {\n static override description = 'Check telemetry consent status for your logged in user'\n\n static override examples: Array<Command.Example> = [\n {\n command: '<%= config.bin %> telemetry <%= command.id %>',\n description: 'Check telemetry consent status for your logged in user',\n },\n ]\n\n static override flags = {} satisfies FlagInput\n\n public async run(): Promise<void> {\n // Parse to ensure no invalid flags are passed\n await this.parse(Status)\n\n const consentInfo = await resolveConsent({env: process.env})\n\n const statusMessage = getStatusMessage(consentInfo)\n const learnMoreMessage = getLearnMoreMessage(consentInfo.status)\n\n this.log(statusMessage)\n this.log(`\\n${learnMoreMessage}`)\n }\n}\n"],"names":["SanityCommand","getLearnMoreMessage","getStatusMessage","resolveConsent","Status","description","examples","command","flags","run","parse","consentInfo","env","process","statusMessage","learnMoreMessage","status","log"],"mappings":"AAEA,SAAQA,aAAa,QAAO,mBAAkB;AAE9C,SAAQC,mBAAmB,QAAO,iDAAgD;AAClF,SAAQC,gBAAgB,QAAO,8CAA6C;AAC5E,SAAQC,cAAc,QAAO,4CAA2C;AAExE,OAAO,MAAMC,eAAeJ;IAC1B,OAAgBK,cAAc,yDAAwD;IAEtF,OAAgBC,WAAmC;QACjD;YACEC,SAAS;YACTF,aAAa;QACf;KACD,CAAA;IAED,OAAgBG,QAAQ,CAAC,EAAqB;IAE9C,MAAaC,MAAqB;QAChC,8CAA8C;QAC9C,MAAM,IAAI,CAACC,KAAK,CAACN;QAEjB,MAAMO,cAAc,MAAMR,eAAe;YAACS,KAAKC,QAAQD,GAAG;QAAA;QAE1D,MAAME,gBAAgBZ,iBAAiBS;QACvC,MAAMI,mBAAmBd,oBAAoBU,YAAYK,MAAM;QAE/D,IAAI,CAACC,GAAG,CAACH;QACT,IAAI,CAACG,GAAG,CAAC,CAAC,EAAE,EAAEF,kBAAkB;IAClC;AACF"}
1
+ {"version":3,"sources":["../../../src/commands/telemetry/status.ts"],"sourcesContent":["import {Command} from '@oclif/core'\nimport {type FlagInput} from '@oclif/core/interfaces'\nimport {SanityCommand} from '@sanity/cli-core'\n\nimport {getLearnMoreMessage} from '../../actions/telemetry/getLearnMoreMessage.js'\nimport {getStatusMessage} from '../../actions/telemetry/getStatusMessage.js'\nimport {resolveConsent} from '../../actions/telemetry/resolveConsent.js'\n\nexport class Status extends SanityCommand<typeof Status> {\n static override description = 'Check telemetry consent status for your logged in user'\n\n static override examples: Array<Command.Example> = [\n {\n command: '<%= config.bin %> telemetry <%= command.id %>',\n description: 'Check telemetry consent status for your logged in user',\n },\n ]\n\n static override flags = {} satisfies FlagInput\n\n public async run(): Promise<void> {\n // Parse to ensure no invalid flags are passed\n await this.parse(Status)\n\n const consentInfo = await resolveConsent()\n\n const statusMessage = getStatusMessage(consentInfo)\n const learnMoreMessage = getLearnMoreMessage(consentInfo.status)\n\n this.log(statusMessage)\n this.log(`\\n${learnMoreMessage}`)\n }\n}\n"],"names":["SanityCommand","getLearnMoreMessage","getStatusMessage","resolveConsent","Status","description","examples","command","flags","run","parse","consentInfo","statusMessage","learnMoreMessage","status","log"],"mappings":"AAEA,SAAQA,aAAa,QAAO,mBAAkB;AAE9C,SAAQC,mBAAmB,QAAO,iDAAgD;AAClF,SAAQC,gBAAgB,QAAO,8CAA6C;AAC5E,SAAQC,cAAc,QAAO,4CAA2C;AAExE,OAAO,MAAMC,eAAeJ;IAC1B,OAAgBK,cAAc,yDAAwD;IAEtF,OAAgBC,WAAmC;QACjD;YACEC,SAAS;YACTF,aAAa;QACf;KACD,CAAA;IAED,OAAgBG,QAAQ,CAAC,EAAqB;IAE9C,MAAaC,MAAqB;QAChC,8CAA8C;QAC9C,MAAM,IAAI,CAACC,KAAK,CAACN;QAEjB,MAAMO,cAAc,MAAMR;QAE1B,MAAMS,gBAAgBV,iBAAiBS;QACvC,MAAME,mBAAmBZ,oBAAoBU,YAAYG,MAAM;QAE/D,IAAI,CAACC,GAAG,CAACH;QACT,IAAI,CAACG,GAAG,CAAC,CAAC,EAAE,EAAEF,kBAAkB;IAClC;AACF"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export declare function runFlushWorker(): Promise<void>;
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+ import { resolveConsent } from '../../actions/telemetry/resolveConsent.js';
3
+ import { sendEvents } from '../../services/telemetry.js';
4
+ import { flushTelemetryFiles } from '../../telemetry/store/flushTelemetryFiles.js';
5
+ export async function runFlushWorker() {
6
+ await flushTelemetryFiles({
7
+ resolveConsent,
8
+ sendEvents
9
+ });
10
+ }
11
+ // Only run if executed directly (not imported)
12
+ if (import.meta.url === `file://${process.argv[1]}`) {
13
+ try {
14
+ await runFlushWorker();
15
+ process.exit(0);
16
+ } catch {
17
+ // Silently exit - don't block parent process
18
+ process.exit(1);
19
+ }
20
+ }
21
+
22
+ //# sourceMappingURL=flushTelemetry.worker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/hooks/prerun/flushTelemetry.worker.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport {resolveConsent} from '../../actions/telemetry/resolveConsent.js'\nimport {sendEvents} from '../../services/telemetry.js'\nimport {flushTelemetryFiles} from '../../telemetry/store/flushTelemetryFiles.js'\n\nexport async function runFlushWorker() {\n await flushTelemetryFiles({resolveConsent, sendEvents})\n}\n\n// Only run if executed directly (not imported)\nif (import.meta.url === `file://${process.argv[1]}`) {\n try {\n await runFlushWorker()\n process.exit(0)\n } catch {\n // Silently exit - don't block parent process\n process.exit(1)\n }\n}\n"],"names":["resolveConsent","sendEvents","flushTelemetryFiles","runFlushWorker","url","process","argv","exit"],"mappings":";AAEA,SAAQA,cAAc,QAAO,4CAA2C;AACxE,SAAQC,UAAU,QAAO,8BAA6B;AACtD,SAAQC,mBAAmB,QAAO,+CAA8C;AAEhF,OAAO,eAAeC;IACpB,MAAMD,oBAAoB;QAACF;QAAgBC;IAAU;AACvD;AAEA,+CAA+C;AAC/C,IAAI,YAAYG,GAAG,KAAK,CAAC,OAAO,EAAEC,QAAQC,IAAI,CAAC,EAAE,EAAE,EAAE;IACnD,IAAI;QACF,MAAMH;QACNE,QAAQE,IAAI,CAAC;IACf,EAAE,OAAM;QACN,6CAA6C;QAC7CF,QAAQE,IAAI,CAAC;IACf;AACF"}
@@ -1,7 +1,71 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { debug, findProjectRoot, getCliConfig } from '@sanity/cli-core';
4
+ import { createSessionId } from '@sanity/telemetry';
5
+ import { resolveConsent } from '../../actions/telemetry/resolveConsent.js';
6
+ import { telemetryDebug } from '../../actions/telemetry/telemetryDebug.js';
1
7
  import { telemetryDisclosure } from '../../actions/telemetry/telemetryDisclosure.js';
2
- export const setupTelemetry = async function() {
8
+ import { CliCommandTelemetry } from '../../telemetry/cli.telemetry.js';
9
+ import { createTelemetryStore } from '../../telemetry/store/createTelemetryStore.js';
10
+ import { detectRuntime } from '../../util/detectRuntime.js';
11
+ import { parseArguments } from '../../util/parseArguments.js';
12
+ export const setupTelemetry = async function({ config }) {
3
13
  // Show telemetry disclosure
4
14
  telemetryDisclosure();
15
+ const sessionId = createSessionId();
16
+ const store = createTelemetryStore(sessionId, {
17
+ resolveConsent
18
+ });
19
+ const projectRoot = await findProjectRoot(process.cwd());
20
+ const cliConfig = await getCliConfig(projectRoot.directory);
21
+ store.logger.updateUserProperties({
22
+ cliVersion: config.version,
23
+ cpuArchitecture: process.arch,
24
+ dataset: cliConfig?.api?.dataset,
25
+ machinePlatform: process.platform,
26
+ projectId: cliConfig?.api?.projectId,
27
+ runtime: detectRuntime(),
28
+ runtimeVersion: process.version
29
+ });
30
+ const args = parseArguments();
31
+ const traceOptions = {
32
+ commandArguments: args.argsWithoutOptions,
33
+ coreOptions: {
34
+ debug: args.coreOptions.debug ?? undefined,
35
+ help: args.coreOptions.help ?? undefined,
36
+ version: args.coreOptions.version ?? undefined
37
+ },
38
+ extraArguments: args.extraArguments,
39
+ groupOrCommand: args.groupOrCommand
40
+ };
41
+ telemetryDebug('Starting command trace', traceOptions);
42
+ const cliCommandTrace = store.logger.trace(CliCommandTelemetry, traceOptions);
43
+ cliCommandTrace.start();
44
+ // Handle process exit - complete trace and spawn worker to flush all telemetry
45
+ process.once('exit', (status)=>{
46
+ if (status === 0) {
47
+ cliCommandTrace.complete();
48
+ } else {
49
+ // TODO: Properly handle errors
50
+ // https://oclif.io/docs/error_handling/#error-handling-in-the-catch-method
51
+ cliCommandTrace.error(new Error('Process exited with status ' + status));
52
+ }
53
+ const workerPath = fileURLToPath(new URL('flushTelemetry.worker.js', import.meta.url));
54
+ telemetryDebug(`Spawning "${process.execPath} ${workerPath}"`);
55
+ // Spawn detached worker to flush all telemetry files
56
+ // unref will ensure the child process can keep doing work even after the parent process exits
57
+ spawn(process.execPath, [
58
+ workerPath
59
+ ], {
60
+ detached: true,
61
+ env: {
62
+ ...process.env,
63
+ SANITY_TELEMETRY_PROJECT_ID: cliConfig?.api?.projectId || ''
64
+ },
65
+ // If debug is enabled, spawn the worker with stdio inherit to see the output
66
+ stdio: debug.enabled ? 'inherit' : 'ignore'
67
+ }).unref();
68
+ });
5
69
  };
6
70
 
7
71
  //# sourceMappingURL=setupTelemetry.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/hooks/prerun/setupTelemetry.ts"],"sourcesContent":["import {type Hook} from '@oclif/core'\n\nimport {telemetryDisclosure} from '../../actions/telemetry/telemetryDisclosure.js'\n\nexport const setupTelemetry: Hook.Prerun = async function () {\n // Show telemetry disclosure\n telemetryDisclosure()\n}\n"],"names":["telemetryDisclosure","setupTelemetry"],"mappings":"AAEA,SAAQA,mBAAmB,QAAO,iDAAgD;AAElF,OAAO,MAAMC,iBAA8B;IACzC,4BAA4B;IAC5BD;AACF,EAAC"}
1
+ {"version":3,"sources":["../../../src/hooks/prerun/setupTelemetry.ts"],"sourcesContent":["import {spawn} from 'node:child_process'\nimport {fileURLToPath} from 'node:url'\n\nimport {type Hook} from '@oclif/core'\nimport {debug, findProjectRoot, getCliConfig} from '@sanity/cli-core'\nimport {createSessionId} from '@sanity/telemetry'\n\nimport {resolveConsent} from '../../actions/telemetry/resolveConsent.js'\nimport {telemetryDebug} from '../../actions/telemetry/telemetryDebug.js'\nimport {telemetryDisclosure} from '../../actions/telemetry/telemetryDisclosure.js'\nimport {CliCommandTelemetry, type CLITraceData} from '../../telemetry/cli.telemetry.js'\nimport {createTelemetryStore} from '../../telemetry/store/createTelemetryStore.js'\nimport {detectRuntime} from '../../util/detectRuntime.js'\nimport {parseArguments} from '../../util/parseArguments.js'\n\nexport const setupTelemetry: Hook.Prerun = async function ({config}) {\n // Show telemetry disclosure\n telemetryDisclosure()\n\n const sessionId = createSessionId()\n\n const store = createTelemetryStore(sessionId, {\n resolveConsent,\n })\n\n const projectRoot = await findProjectRoot(process.cwd())\n const cliConfig = await getCliConfig(projectRoot.directory)\n\n store.logger.updateUserProperties({\n cliVersion: config.version,\n cpuArchitecture: process.arch,\n dataset: cliConfig?.api?.dataset,\n machinePlatform: process.platform,\n projectId: cliConfig?.api?.projectId,\n runtime: detectRuntime(),\n runtimeVersion: process.version,\n })\n\n const args = parseArguments()\n\n const traceOptions: CLITraceData = {\n commandArguments: args.argsWithoutOptions,\n coreOptions: {\n debug: args.coreOptions.debug ?? undefined,\n help: args.coreOptions.help ?? undefined,\n version: args.coreOptions.version ?? undefined,\n },\n extraArguments: args.extraArguments,\n groupOrCommand: args.groupOrCommand,\n }\n\n telemetryDebug('Starting command trace', traceOptions)\n\n const cliCommandTrace = store.logger.trace(CliCommandTelemetry, traceOptions)\n cliCommandTrace.start()\n\n // Handle process exit - complete trace and spawn worker to flush all telemetry\n process.once('exit', (status) => {\n if (status === 0) {\n cliCommandTrace.complete()\n } else {\n // TODO: Properly handle errors\n // https://oclif.io/docs/error_handling/#error-handling-in-the-catch-method\n cliCommandTrace.error(new Error('Process exited with status ' + status))\n }\n\n const workerPath = fileURLToPath(new URL('flushTelemetry.worker.js', import.meta.url))\n telemetryDebug(`Spawning \"${process.execPath} ${workerPath}\"`)\n\n // Spawn detached worker to flush all telemetry files\n // unref will ensure the child process can keep doing work even after the parent process exits\n spawn(process.execPath, [workerPath], {\n detached: true,\n env: {\n ...process.env,\n SANITY_TELEMETRY_PROJECT_ID: cliConfig?.api?.projectId || '',\n },\n // If debug is enabled, spawn the worker with stdio inherit to see the output\n stdio: debug.enabled ? 'inherit' : 'ignore',\n }).unref()\n })\n}\n"],"names":["spawn","fileURLToPath","debug","findProjectRoot","getCliConfig","createSessionId","resolveConsent","telemetryDebug","telemetryDisclosure","CliCommandTelemetry","createTelemetryStore","detectRuntime","parseArguments","setupTelemetry","config","sessionId","store","projectRoot","process","cwd","cliConfig","directory","logger","updateUserProperties","cliVersion","version","cpuArchitecture","arch","dataset","api","machinePlatform","platform","projectId","runtime","runtimeVersion","args","traceOptions","commandArguments","argsWithoutOptions","coreOptions","undefined","help","extraArguments","groupOrCommand","cliCommandTrace","trace","start","once","status","complete","error","Error","workerPath","URL","url","execPath","detached","env","SANITY_TELEMETRY_PROJECT_ID","stdio","enabled","unref"],"mappings":"AAAA,SAAQA,KAAK,QAAO,qBAAoB;AACxC,SAAQC,aAAa,QAAO,WAAU;AAGtC,SAAQC,KAAK,EAAEC,eAAe,EAAEC,YAAY,QAAO,mBAAkB;AACrE,SAAQC,eAAe,QAAO,oBAAmB;AAEjD,SAAQC,cAAc,QAAO,4CAA2C;AACxE,SAAQC,cAAc,QAAO,4CAA2C;AACxE,SAAQC,mBAAmB,QAAO,iDAAgD;AAClF,SAAQC,mBAAmB,QAA0B,mCAAkC;AACvF,SAAQC,oBAAoB,QAAO,gDAA+C;AAClF,SAAQC,aAAa,QAAO,8BAA6B;AACzD,SAAQC,cAAc,QAAO,+BAA8B;AAE3D,OAAO,MAAMC,iBAA8B,eAAgB,EAACC,MAAM,EAAC;IACjE,4BAA4B;IAC5BN;IAEA,MAAMO,YAAYV;IAElB,MAAMW,QAAQN,qBAAqBK,WAAW;QAC5CT;IACF;IAEA,MAAMW,cAAc,MAAMd,gBAAgBe,QAAQC,GAAG;IACrD,MAAMC,YAAY,MAAMhB,aAAaa,YAAYI,SAAS;IAE1DL,MAAMM,MAAM,CAACC,oBAAoB,CAAC;QAChCC,YAAYV,OAAOW,OAAO;QAC1BC,iBAAiBR,QAAQS,IAAI;QAC7BC,SAASR,WAAWS,KAAKD;QACzBE,iBAAiBZ,QAAQa,QAAQ;QACjCC,WAAWZ,WAAWS,KAAKG;QAC3BC,SAAStB;QACTuB,gBAAgBhB,QAAQO,OAAO;IACjC;IAEA,MAAMU,OAAOvB;IAEb,MAAMwB,eAA6B;QACjCC,kBAAkBF,KAAKG,kBAAkB;QACzCC,aAAa;YACXrC,OAAOiC,KAAKI,WAAW,CAACrC,KAAK,IAAIsC;YACjCC,MAAMN,KAAKI,WAAW,CAACE,IAAI,IAAID;YAC/Bf,SAASU,KAAKI,WAAW,CAACd,OAAO,IAAIe;QACvC;QACAE,gBAAgBP,KAAKO,cAAc;QACnCC,gBAAgBR,KAAKQ,cAAc;IACrC;IAEApC,eAAe,0BAA0B6B;IAEzC,MAAMQ,kBAAkB5B,MAAMM,MAAM,CAACuB,KAAK,CAACpC,qBAAqB2B;IAChEQ,gBAAgBE,KAAK;IAErB,+EAA+E;IAC/E5B,QAAQ6B,IAAI,CAAC,QAAQ,CAACC;QACpB,IAAIA,WAAW,GAAG;YAChBJ,gBAAgBK,QAAQ;QAC1B,OAAO;YACL,+BAA+B;YAC/B,2EAA2E;YAC3EL,gBAAgBM,KAAK,CAAC,IAAIC,MAAM,gCAAgCH;QAClE;QAEA,MAAMI,aAAanD,cAAc,IAAIoD,IAAI,4BAA4B,YAAYC,GAAG;QACpF/C,eAAe,CAAC,UAAU,EAAEW,QAAQqC,QAAQ,CAAC,CAAC,EAAEH,WAAW,CAAC,CAAC;QAE7D,qDAAqD;QACrD,8FAA8F;QAC9FpD,MAAMkB,QAAQqC,QAAQ,EAAE;YAACH;SAAW,EAAE;YACpCI,UAAU;YACVC,KAAK;gBACH,GAAGvC,QAAQuC,GAAG;gBACdC,6BAA6BtC,WAAWS,KAAKG,aAAa;YAC5D;YACA,6EAA6E;YAC7E2B,OAAOzD,MAAM0D,OAAO,GAAG,YAAY;QACrC,GAAGC,KAAK;IACV;AACF,EAAC"}
@@ -0,0 +1,2 @@
1
+ import { type TelemetryEvent } from '@sanity/telemetry';
2
+ export declare function sendEvents(batch: TelemetryEvent[]): Promise<any>;
@@ -0,0 +1,20 @@
1
+ import { getGlobalCliClient } from '@sanity/cli-core';
2
+ const TELEMETRY_API_VERSION = 'v2023-12-18';
3
+ export async function sendEvents(batch) {
4
+ const client = await getGlobalCliClient({
5
+ apiVersion: TELEMETRY_API_VERSION,
6
+ requireUser: true
7
+ });
8
+ const projectId = process.env.SANITY_TELEMETRY_PROJECT_ID;
9
+ return client.request({
10
+ body: {
11
+ batch,
12
+ projectId
13
+ },
14
+ json: true,
15
+ method: 'POST',
16
+ uri: '/intake/batch'
17
+ });
18
+ }
19
+
20
+ //# sourceMappingURL=telemetry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/services/telemetry.ts"],"sourcesContent":["import {getGlobalCliClient} from '@sanity/cli-core'\nimport {type TelemetryEvent} from '@sanity/telemetry'\n\nconst TELEMETRY_API_VERSION = 'v2023-12-18'\n\nexport async function sendEvents(batch: TelemetryEvent[]) {\n const client = await getGlobalCliClient({\n apiVersion: TELEMETRY_API_VERSION,\n requireUser: true,\n })\n\n const projectId = process.env.SANITY_TELEMETRY_PROJECT_ID\n\n return client.request({\n body: {batch, projectId},\n json: true,\n method: 'POST',\n uri: '/intake/batch',\n })\n}\n"],"names":["getGlobalCliClient","TELEMETRY_API_VERSION","sendEvents","batch","client","apiVersion","requireUser","projectId","process","env","SANITY_TELEMETRY_PROJECT_ID","request","body","json","method","uri"],"mappings":"AAAA,SAAQA,kBAAkB,QAAO,mBAAkB;AAGnD,MAAMC,wBAAwB;AAE9B,OAAO,eAAeC,WAAWC,KAAuB;IACtD,MAAMC,SAAS,MAAMJ,mBAAmB;QACtCK,YAAYJ;QACZK,aAAa;IACf;IAEA,MAAMC,YAAYC,QAAQC,GAAG,CAACC,2BAA2B;IAEzD,OAAON,OAAOO,OAAO,CAAC;QACpBC,MAAM;YAACT;YAAOI;QAAS;QACvBM,MAAM;QACNC,QAAQ;QACRC,KAAK;IACP;AACF"}
@@ -0,0 +1,20 @@
1
+ export interface CLITraceData {
2
+ /**
3
+ * Command arguments, eg any arguments after `sanity <command>` (no flags)
4
+ */
5
+ commandArguments: string[];
6
+ coreOptions: {
7
+ debug?: boolean;
8
+ help?: boolean;
9
+ version?: boolean;
10
+ };
11
+ /**
12
+ * Arguments after the ended argument list (--)
13
+ */
14
+ extraArguments: string[];
15
+ /**
16
+ * Command flags, without the core options (help, debug, version etc)
17
+ */
18
+ groupOrCommand: string;
19
+ }
20
+ export declare const CliCommandTelemetry: import("@sanity/telemetry").DefinedTelemetryTrace<CLITraceData, void>;
@@ -0,0 +1,8 @@
1
+ import { defineTrace } from '@sanity/telemetry';
2
+ export const CliCommandTelemetry = defineTrace({
3
+ description: 'A CLI command was executed',
4
+ name: 'CLI Command Executed',
5
+ version: 1
6
+ });
7
+
8
+ //# sourceMappingURL=cli.telemetry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/telemetry/cli.telemetry.ts"],"sourcesContent":["import {defineTrace} from '@sanity/telemetry'\n\nexport interface CLITraceData {\n /**\n * Command arguments, eg any arguments after `sanity <command>` (no flags)\n */\n commandArguments: string[]\n\n coreOptions: {\n debug?: boolean\n help?: boolean\n version?: boolean\n }\n\n /**\n * Arguments after the ended argument list (--)\n */\n extraArguments: string[]\n /**\n * Command flags, without the core options (help, debug, version etc)\n */\n groupOrCommand: string\n}\n\nexport const CliCommandTelemetry = defineTrace<CLITraceData>({\n description: 'A CLI command was executed',\n name: 'CLI Command Executed',\n version: 1,\n})\n"],"names":["defineTrace","CliCommandTelemetry","description","name","version"],"mappings":"AAAA,SAAQA,WAAW,QAAO,oBAAmB;AAwB7C,OAAO,MAAMC,sBAAsBD,YAA0B;IAC3DE,aAAa;IACbC,MAAM;IACNC,SAAS;AACX,GAAE"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Cleans up telemetry files older than the specified number of days
3
+ * @internal
4
+ */
5
+ export declare function cleanupOldTelemetryFiles(maxAgeDays?: number): Promise<void>;
@@ -0,0 +1,30 @@
1
+ import { rm, stat } from 'node:fs/promises';
2
+ import { telemetryStoreDebug } from './debug.js';
3
+ import { findTelemetryFiles } from './findTelemetryFiles.js';
4
+ /**
5
+ * Cleans up telemetry files older than the specified number of days
6
+ * @internal
7
+ */ export async function cleanupOldTelemetryFiles(maxAgeDays = 7) {
8
+ try {
9
+ const files = await findTelemetryFiles();
10
+ const cutoffTime = Date.now() - maxAgeDays * 24 * 60 * 60 * 1000;
11
+ for (const filePath of files){
12
+ try {
13
+ const stats = await stat(filePath);
14
+ if (stats.mtime.getTime() < cutoffTime) {
15
+ telemetryStoreDebug('Cleaning up old telemetry file: %s', filePath);
16
+ await rm(filePath, {
17
+ force: true
18
+ });
19
+ }
20
+ } catch (error) {
21
+ telemetryStoreDebug('Error checking/removing old file %s: %o', filePath, error);
22
+ }
23
+ }
24
+ } catch (error) {
25
+ telemetryStoreDebug('Error during cleanup: %o', error);
26
+ // Don't throw - cleanup is best effort
27
+ }
28
+ }
29
+
30
+ //# sourceMappingURL=cleanupOldTelemetryFiles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/telemetry/store/cleanupOldTelemetryFiles.ts"],"sourcesContent":["import {rm, stat} from 'node:fs/promises'\n\nimport {telemetryStoreDebug} from './debug.js'\nimport {findTelemetryFiles} from './findTelemetryFiles.js'\n\n/**\n * Cleans up telemetry files older than the specified number of days\n * @internal\n */\nexport async function cleanupOldTelemetryFiles(maxAgeDays: number = 7): Promise<void> {\n try {\n const files = await findTelemetryFiles()\n const cutoffTime = Date.now() - maxAgeDays * 24 * 60 * 60 * 1000\n\n for (const filePath of files) {\n try {\n const stats = await stat(filePath)\n if (stats.mtime.getTime() < cutoffTime) {\n telemetryStoreDebug('Cleaning up old telemetry file: %s', filePath)\n await rm(filePath, {force: true})\n }\n } catch (error) {\n telemetryStoreDebug('Error checking/removing old file %s: %o', filePath, error)\n }\n }\n } catch (error) {\n telemetryStoreDebug('Error during cleanup: %o', error)\n // Don't throw - cleanup is best effort\n }\n}\n"],"names":["rm","stat","telemetryStoreDebug","findTelemetryFiles","cleanupOldTelemetryFiles","maxAgeDays","files","cutoffTime","Date","now","filePath","stats","mtime","getTime","force","error"],"mappings":"AAAA,SAAQA,EAAE,EAAEC,IAAI,QAAO,mBAAkB;AAEzC,SAAQC,mBAAmB,QAAO,aAAY;AAC9C,SAAQC,kBAAkB,QAAO,0BAAyB;AAE1D;;;CAGC,GACD,OAAO,eAAeC,yBAAyBC,aAAqB,CAAC;IACnE,IAAI;QACF,MAAMC,QAAQ,MAAMH;QACpB,MAAMI,aAAaC,KAAKC,GAAG,KAAKJ,aAAa,KAAK,KAAK,KAAK;QAE5D,KAAK,MAAMK,YAAYJ,MAAO;YAC5B,IAAI;gBACF,MAAMK,QAAQ,MAAMV,KAAKS;gBACzB,IAAIC,MAAMC,KAAK,CAACC,OAAO,KAAKN,YAAY;oBACtCL,oBAAoB,sCAAsCQ;oBAC1D,MAAMV,GAAGU,UAAU;wBAACI,OAAO;oBAAI;gBACjC;YACF,EAAE,OAAOC,OAAO;gBACdb,oBAAoB,2CAA2CQ,UAAUK;YAC3E;QACF;IACF,EAAE,OAAOA,OAAO;QACdb,oBAAoB,4BAA4Ba;IAChD,uCAAuC;IACzC;AACF"}
@@ -0,0 +1,39 @@
1
+ import { type TelemetryStore } from '@sanity/telemetry';
2
+ import { type ConsentInformation } from '../../actions/telemetry/types.js';
3
+ /**
4
+ * FILE MANAGEMENT STRATEGY:
5
+ *
6
+ * The telemetry system uses a multi-file approach to handle concurrent CLI processes:
7
+ *
8
+ * 1. WRITING (per session):
9
+ * - Each CLI session gets a unique file: telemetry-\{hash\}-\{env\}-\{sessionId\}.ndjson
10
+ * - Prevents write conflicts when multiple CLI commands run simultaneously
11
+ * - Events are written using an RxJS queue for ordered processing with retry logic
12
+ *
13
+ * 2. FLUSHING (aggregate all sessions):
14
+ * - findTelemetryFiles() discovers ALL telemetry files for user/environment
15
+ * - Events are collected from all session files and sent as a batch
16
+ * - Files are deleted after successful transmission
17
+ *
18
+ * 3. CLEANUP (background maintenance):
19
+ * - cleanupOldTelemetryFiles() removes stale files older than 7 days
20
+ * - Prevents disk space accumulation from abandoned sessions
21
+ */
22
+ interface CreateTelemetryStoreOptions {
23
+ resolveConsent: () => Promise<ConsentInformation>;
24
+ }
25
+ type CLITelemetryStore<T> = Pick<TelemetryStore<T>, 'logger'>;
26
+ /**
27
+ * Creates a file-based telemetry store with cached consent and reliable synchronous I/O.
28
+ *
29
+ * Key optimizations:
30
+ * - Consent resolved once at creation and cached (vs checking on every emit)
31
+ * - File path generated and directory created once during initialization
32
+ * - Synchronous file writes to ensure events are captured even during process exit
33
+ *
34
+ * @param sessionId - Unique session identifier for file isolation
35
+ * @param options - Configuration options
36
+ * @returns TelemetryStore instance compatible with the telemetry interface
37
+ */
38
+ export declare function createTelemetryStore<UserProperties>(sessionId: string, options: CreateTelemetryStoreOptions): CLITelemetryStore<UserProperties>;
39
+ export {};
@@ -0,0 +1,95 @@
1
+ import { appendFileSync } from 'node:fs';
2
+ import { mkdir } from 'node:fs/promises';
3
+ import { dirname } from 'node:path';
4
+ import { telemetryStoreDebug } from './debug.js';
5
+ import { generateTelemetryFilePath } from './generateTelemetryFilePath.js';
6
+ import { createLogger } from './logger.js';
7
+ /**
8
+ * Creates a file-based telemetry store with cached consent and reliable synchronous I/O.
9
+ *
10
+ * Key optimizations:
11
+ * - Consent resolved once at creation and cached (vs checking on every emit)
12
+ * - File path generated and directory created once during initialization
13
+ * - Synchronous file writes to ensure events are captured even during process exit
14
+ *
15
+ * @param sessionId - Unique session identifier for file isolation
16
+ * @param options - Configuration options
17
+ * @returns TelemetryStore instance compatible with the telemetry interface
18
+ */ export function createTelemetryStore(sessionId, options) {
19
+ telemetryStoreDebug('Creating telemetry store with sessionId: %s', sessionId);
20
+ let cachedConsent = null;
21
+ let filePath = null;
22
+ const initializeConsent = async ()=>{
23
+ if (cachedConsent) return;
24
+ try {
25
+ cachedConsent = await options.resolveConsent();
26
+ telemetryStoreDebug('Cached consent status: %s', cachedConsent.status);
27
+ } catch (error) {
28
+ telemetryStoreDebug('Failed to initialize consent, treating as undetermined: %o', error);
29
+ cachedConsent = {
30
+ reason: 'fetchError',
31
+ status: 'undetermined'
32
+ };
33
+ }
34
+ };
35
+ const initializeFilePath = async ()=>{
36
+ if (filePath) return;
37
+ try {
38
+ filePath = await generateTelemetryFilePath(sessionId);
39
+ telemetryStoreDebug('Generated file path: %s', filePath);
40
+ await mkdir(dirname(filePath), {
41
+ recursive: true
42
+ });
43
+ telemetryStoreDebug('Created directory structure for: %s', filePath);
44
+ } catch (error) {
45
+ telemetryStoreDebug('Failed to initialize file path: %o', error);
46
+ filePath = null;
47
+ }
48
+ };
49
+ const emit = (event)=>{
50
+ if (!cachedConsent || cachedConsent.status !== 'granted') {
51
+ if (cachedConsent) {
52
+ telemetryStoreDebug('Cached consent not granted (%s), skipping event: %s', cachedConsent.status, event.type);
53
+ } else {
54
+ telemetryStoreDebug('Consent not resolved, skipping event: %s', event.type);
55
+ }
56
+ return;
57
+ }
58
+ if (!filePath) {
59
+ telemetryStoreDebug('File path not initialized, skipping event: %s', event.type);
60
+ return;
61
+ }
62
+ telemetryStoreDebug('Emitting event: %s', event.type);
63
+ try {
64
+ const eventLine = JSON.stringify(event) + '\n';
65
+ // We use synchronous file writes to ensure telemetry events are captured even when
66
+ // the process exits abruptly (process.exit, uncaught exceptions, SIGTERM, etc.).
67
+ // The performance impact is probably negligible and is worth the trade-off
68
+ // for 100% reliability. Async writes would be lost when the event loop
69
+ // shuts down during process exit.
70
+ appendFileSync(filePath, eventLine, 'utf8');
71
+ telemetryStoreDebug('Successfully wrote event to file: %s', filePath);
72
+ } catch (error) {
73
+ telemetryStoreDebug('Failed to write telemetry event: %o', error);
74
+ // Silent failure - don't break CLI functionality
75
+ }
76
+ };
77
+ const logger = createLogger(sessionId, emit);
78
+ // Initialize both consent and file path concurrently
79
+ Promise.allSettled([
80
+ initializeConsent(),
81
+ initializeFilePath()
82
+ ]).then((results)=>{
83
+ for (const [index, result] of results.entries()){
84
+ if (result.status === 'rejected') {
85
+ const type = index === 0 ? 'consent' : 'file path';
86
+ telemetryStoreDebug('Error initializing %s: %o', type, result.reason);
87
+ }
88
+ }
89
+ });
90
+ return {
91
+ logger
92
+ };
93
+ }
94
+
95
+ //# sourceMappingURL=createTelemetryStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/telemetry/store/createTelemetryStore.ts"],"sourcesContent":["import {appendFileSync} from 'node:fs'\nimport {mkdir} from 'node:fs/promises'\nimport {dirname} from 'node:path'\n\nimport {type TelemetryEvent, type TelemetryStore} from '@sanity/telemetry'\n\nimport {type ConsentInformation} from '../../actions/telemetry/types.js'\nimport {telemetryStoreDebug} from './debug.js'\nimport {generateTelemetryFilePath} from './generateTelemetryFilePath.js'\nimport {createLogger} from './logger.js'\n\n/**\n * FILE MANAGEMENT STRATEGY:\n *\n * The telemetry system uses a multi-file approach to handle concurrent CLI processes:\n *\n * 1. WRITING (per session):\n * - Each CLI session gets a unique file: telemetry-\\{hash\\}-\\{env\\}-\\{sessionId\\}.ndjson\n * - Prevents write conflicts when multiple CLI commands run simultaneously\n * - Events are written using an RxJS queue for ordered processing with retry logic\n *\n * 2. FLUSHING (aggregate all sessions):\n * - findTelemetryFiles() discovers ALL telemetry files for user/environment\n * - Events are collected from all session files and sent as a batch\n * - Files are deleted after successful transmission\n *\n * 3. CLEANUP (background maintenance):\n * - cleanupOldTelemetryFiles() removes stale files older than 7 days\n * - Prevents disk space accumulation from abandoned sessions\n */\n\ninterface CreateTelemetryStoreOptions {\n resolveConsent: () => Promise<ConsentInformation>\n}\n\ntype CLITelemetryStore<T> = Pick<TelemetryStore<T>, 'logger'>\n\n/**\n * Creates a file-based telemetry store with cached consent and reliable synchronous I/O.\n *\n * Key optimizations:\n * - Consent resolved once at creation and cached (vs checking on every emit)\n * - File path generated and directory created once during initialization\n * - Synchronous file writes to ensure events are captured even during process exit\n *\n * @param sessionId - Unique session identifier for file isolation\n * @param options - Configuration options\n * @returns TelemetryStore instance compatible with the telemetry interface\n */\nexport function createTelemetryStore<UserProperties>(\n sessionId: string,\n options: CreateTelemetryStoreOptions,\n): CLITelemetryStore<UserProperties> {\n telemetryStoreDebug('Creating telemetry store with sessionId: %s', sessionId)\n\n let cachedConsent: ConsentInformation | null = null\n let filePath: string | null = null\n\n const initializeConsent = async () => {\n if (cachedConsent) return\n\n try {\n cachedConsent = await options.resolveConsent()\n telemetryStoreDebug('Cached consent status: %s', cachedConsent.status)\n } catch (error) {\n telemetryStoreDebug('Failed to initialize consent, treating as undetermined: %o', error)\n cachedConsent = {reason: 'fetchError', status: 'undetermined'}\n }\n }\n\n const initializeFilePath = async () => {\n if (filePath) return\n\n try {\n filePath = await generateTelemetryFilePath(sessionId)\n telemetryStoreDebug('Generated file path: %s', filePath)\n\n await mkdir(dirname(filePath), {recursive: true})\n telemetryStoreDebug('Created directory structure for: %s', filePath)\n } catch (error) {\n telemetryStoreDebug('Failed to initialize file path: %o', error)\n filePath = null\n }\n }\n\n const emit = (event: TelemetryEvent) => {\n if (!cachedConsent || cachedConsent.status !== 'granted') {\n if (cachedConsent) {\n telemetryStoreDebug(\n 'Cached consent not granted (%s), skipping event: %s',\n cachedConsent.status,\n event.type,\n )\n } else {\n telemetryStoreDebug('Consent not resolved, skipping event: %s', event.type)\n }\n return\n }\n\n if (!filePath) {\n telemetryStoreDebug('File path not initialized, skipping event: %s', event.type)\n return\n }\n\n telemetryStoreDebug('Emitting event: %s', event.type)\n\n try {\n const eventLine = JSON.stringify(event) + '\\n'\n\n // We use synchronous file writes to ensure telemetry events are captured even when\n // the process exits abruptly (process.exit, uncaught exceptions, SIGTERM, etc.).\n // The performance impact is probably negligible and is worth the trade-off\n // for 100% reliability. Async writes would be lost when the event loop\n // shuts down during process exit.\n appendFileSync(filePath, eventLine, 'utf8')\n telemetryStoreDebug('Successfully wrote event to file: %s', filePath)\n } catch (error) {\n telemetryStoreDebug('Failed to write telemetry event: %o', error)\n // Silent failure - don't break CLI functionality\n }\n }\n\n const logger = createLogger<UserProperties>(sessionId, emit)\n\n // Initialize both consent and file path concurrently\n Promise.allSettled([initializeConsent(), initializeFilePath()]).then((results) => {\n for (const [index, result] of results.entries()) {\n if (result.status === 'rejected') {\n const type = index === 0 ? 'consent' : 'file path'\n telemetryStoreDebug('Error initializing %s: %o', type, result.reason)\n }\n }\n })\n\n return {\n logger,\n }\n}\n"],"names":["appendFileSync","mkdir","dirname","telemetryStoreDebug","generateTelemetryFilePath","createLogger","createTelemetryStore","sessionId","options","cachedConsent","filePath","initializeConsent","resolveConsent","status","error","reason","initializeFilePath","recursive","emit","event","type","eventLine","JSON","stringify","logger","Promise","allSettled","then","results","index","result","entries"],"mappings":"AAAA,SAAQA,cAAc,QAAO,UAAS;AACtC,SAAQC,KAAK,QAAO,mBAAkB;AACtC,SAAQC,OAAO,QAAO,YAAW;AAKjC,SAAQC,mBAAmB,QAAO,aAAY;AAC9C,SAAQC,yBAAyB,QAAO,iCAAgC;AACxE,SAAQC,YAAY,QAAO,cAAa;AA4BxC;;;;;;;;;;;CAWC,GACD,OAAO,SAASC,qBACdC,SAAiB,EACjBC,OAAoC;IAEpCL,oBAAoB,+CAA+CI;IAEnE,IAAIE,gBAA2C;IAC/C,IAAIC,WAA0B;IAE9B,MAAMC,oBAAoB;QACxB,IAAIF,eAAe;QAEnB,IAAI;YACFA,gBAAgB,MAAMD,QAAQI,cAAc;YAC5CT,oBAAoB,6BAA6BM,cAAcI,MAAM;QACvE,EAAE,OAAOC,OAAO;YACdX,oBAAoB,8DAA8DW;YAClFL,gBAAgB;gBAACM,QAAQ;gBAAcF,QAAQ;YAAc;QAC/D;IACF;IAEA,MAAMG,qBAAqB;QACzB,IAAIN,UAAU;QAEd,IAAI;YACFA,WAAW,MAAMN,0BAA0BG;YAC3CJ,oBAAoB,2BAA2BO;YAE/C,MAAMT,MAAMC,QAAQQ,WAAW;gBAACO,WAAW;YAAI;YAC/Cd,oBAAoB,uCAAuCO;QAC7D,EAAE,OAAOI,OAAO;YACdX,oBAAoB,sCAAsCW;YAC1DJ,WAAW;QACb;IACF;IAEA,MAAMQ,OAAO,CAACC;QACZ,IAAI,CAACV,iBAAiBA,cAAcI,MAAM,KAAK,WAAW;YACxD,IAAIJ,eAAe;gBACjBN,oBACE,uDACAM,cAAcI,MAAM,EACpBM,MAAMC,IAAI;YAEd,OAAO;gBACLjB,oBAAoB,4CAA4CgB,MAAMC,IAAI;YAC5E;YACA;QACF;QAEA,IAAI,CAACV,UAAU;YACbP,oBAAoB,iDAAiDgB,MAAMC,IAAI;YAC/E;QACF;QAEAjB,oBAAoB,sBAAsBgB,MAAMC,IAAI;QAEpD,IAAI;YACF,MAAMC,YAAYC,KAAKC,SAAS,CAACJ,SAAS;YAE1C,mFAAmF;YACnF,iFAAiF;YACjF,2EAA2E;YAC3E,uEAAuE;YACvE,kCAAkC;YAClCnB,eAAeU,UAAUW,WAAW;YACpClB,oBAAoB,wCAAwCO;QAC9D,EAAE,OAAOI,OAAO;YACdX,oBAAoB,uCAAuCW;QAC3D,iDAAiD;QACnD;IACF;IAEA,MAAMU,SAASnB,aAA6BE,WAAWW;IAEvD,qDAAqD;IACrDO,QAAQC,UAAU,CAAC;QAACf;QAAqBK;KAAqB,EAAEW,IAAI,CAAC,CAACC;QACpE,KAAK,MAAM,CAACC,OAAOC,OAAO,IAAIF,QAAQG,OAAO,GAAI;YAC/C,IAAID,OAAOjB,MAAM,KAAK,YAAY;gBAChC,MAAMO,OAAOS,UAAU,IAAI,YAAY;gBACvC1B,oBAAoB,6BAA6BiB,MAAMU,OAAOf,MAAM;YACtE;QACF;IACF;IAEA,OAAO;QACLS;IACF;AACF"}