@microsoft/managed-apps-cli 0.18.0 → 0.19.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.
Files changed (88) hide show
  1. package/dist/ArgumentProvider.d.ts +25 -1
  2. package/dist/ArgumentProvider.d.ts.map +1 -1
  3. package/dist/ArgumentProvider.js +80 -23
  4. package/dist/ArgumentProvider.js.map +1 -1
  5. package/dist/Authentication/ActiveAccountStore.d.ts +1 -1
  6. package/dist/Authentication/ActiveAccountStore.d.ts.map +1 -1
  7. package/dist/Authentication/ActiveAccountStore.js +3 -3
  8. package/dist/Authentication/ActiveAccountStore.js.map +1 -1
  9. package/dist/Authentication/NodeMsalAuthenticationProvider.d.ts +16 -1
  10. package/dist/Authentication/NodeMsalAuthenticationProvider.d.ts.map +1 -1
  11. package/dist/Authentication/NodeMsalAuthenticationProvider.js +72 -11
  12. package/dist/Authentication/NodeMsalAuthenticationProvider.js.map +1 -1
  13. package/dist/Cli.d.ts.map +1 -1
  14. package/dist/Cli.js +15 -7
  15. package/dist/Cli.js.map +1 -1
  16. package/dist/CliUtils.d.ts +4 -7
  17. package/dist/CliUtils.d.ts.map +1 -1
  18. package/dist/CliUtils.js +2 -12
  19. package/dist/CliUtils.js.map +1 -1
  20. package/dist/Constants/HelpStrings.d.ts +1 -0
  21. package/dist/Constants/HelpStrings.d.ts.map +1 -1
  22. package/dist/Constants/HelpStrings.js +2 -0
  23. package/dist/Constants/HelpStrings.js.map +1 -1
  24. package/dist/Constants.d.ts +3 -0
  25. package/dist/Constants.d.ts.map +1 -1
  26. package/dist/Constants.js +6 -0
  27. package/dist/Constants.js.map +1 -1
  28. package/dist/Errors/GitHubErrorMap.d.ts.map +1 -1
  29. package/dist/Errors/GitHubErrorMap.js +2 -0
  30. package/dist/Errors/GitHubErrorMap.js.map +1 -1
  31. package/dist/HttpClient/CliHttpClient.d.ts +0 -13
  32. package/dist/HttpClient/CliHttpClient.d.ts.map +1 -1
  33. package/dist/HttpClient/CliHttpClient.js +0 -24
  34. package/dist/HttpClient/CliHttpClient.js.map +1 -1
  35. package/dist/Logger/BufferedTelemetryWriter.d.ts +20 -0
  36. package/dist/Logger/BufferedTelemetryWriter.d.ts.map +1 -0
  37. package/dist/Logger/BufferedTelemetryWriter.js +163 -0
  38. package/dist/Logger/BufferedTelemetryWriter.js.map +1 -0
  39. package/dist/Logger/CliLogger.d.ts +28 -50
  40. package/dist/Logger/CliLogger.d.ts.map +1 -1
  41. package/dist/Logger/CliLogger.js +151 -122
  42. package/dist/Logger/CliLogger.js.map +1 -1
  43. package/dist/Logger/OneDSWriter.d.ts +8 -8
  44. package/dist/Logger/OneDSWriter.d.ts.map +1 -1
  45. package/dist/Logger/OneDSWriter.js +19 -17
  46. package/dist/Logger/OneDSWriter.js.map +1 -1
  47. package/dist/Types/Cli.types.d.ts +3 -2
  48. package/dist/Types/Cli.types.d.ts.map +1 -1
  49. package/dist/Types/LoggerTypes.types.d.ts +7 -7
  50. package/dist/Types/LoggerTypes.types.d.ts.map +1 -1
  51. package/dist/Utils/EnvironmentRoutingUtils.d.ts +1 -1
  52. package/dist/Utils/EnvironmentRoutingUtils.js +3 -3
  53. package/dist/Utils/EnvironmentRoutingUtils.js.map +1 -1
  54. package/dist/Utils/FindAvailablePort.d.ts +39 -0
  55. package/dist/Utils/FindAvailablePort.d.ts.map +1 -0
  56. package/dist/Utils/FindAvailablePort.js +105 -0
  57. package/dist/Utils/FindAvailablePort.js.map +1 -0
  58. package/dist/Verbs/CreateMaafApp.d.ts.map +1 -1
  59. package/dist/Verbs/CreateMaafApp.js +5 -6
  60. package/dist/Verbs/CreateMaafApp.js.map +1 -1
  61. package/dist/Verbs/DeleteMaafApp.d.ts.map +1 -1
  62. package/dist/Verbs/DeleteMaafApp.js +6 -10
  63. package/dist/Verbs/DeleteMaafApp.js.map +1 -1
  64. package/dist/Verbs/Dev.d.ts.map +1 -1
  65. package/dist/Verbs/Dev.js +37 -8
  66. package/dist/Verbs/Dev.js.map +1 -1
  67. package/dist/Verbs/GetMaafApp.js +2 -3
  68. package/dist/Verbs/GetMaafApp.js.map +1 -1
  69. package/dist/Verbs/Init.d.ts.map +1 -1
  70. package/dist/Verbs/Init.js +2 -2
  71. package/dist/Verbs/Init.js.map +1 -1
  72. package/dist/Verbs/ListMaafApps.d.ts.map +1 -1
  73. package/dist/Verbs/ListMaafApps.js +5 -5
  74. package/dist/Verbs/ListMaafApps.js.map +1 -1
  75. package/dist/Verbs/Login.d.ts.map +1 -1
  76. package/dist/Verbs/Login.js +9 -1
  77. package/dist/Verbs/Login.js.map +1 -1
  78. package/dist/Verbs/MaafAppPermissions.d.ts +1 -1
  79. package/dist/Verbs/MaafAppPermissions.js +15 -14
  80. package/dist/Verbs/MaafAppPermissions.js.map +1 -1
  81. package/dist/Verbs/RefreshGitAuth.d.ts.map +1 -1
  82. package/dist/Verbs/RefreshGitAuth.js +2 -3
  83. package/dist/Verbs/RefreshGitAuth.js.map +1 -1
  84. package/dist/Verbs/VerbConstants.d.ts +4 -0
  85. package/dist/Verbs/VerbConstants.d.ts.map +1 -1
  86. package/dist/Verbs/VerbConstants.js +9 -0
  87. package/dist/Verbs/VerbConstants.js.map +1 -1
  88. package/package.json +4 -4
@@ -0,0 +1,163 @@
1
+ /*!
2
+ * Copyright (C) Microsoft Corporation. All rights reserved.
3
+ */
4
+ import { randomUUID } from 'node:crypto';
5
+ import * as fs from 'node:fs';
6
+ import * as path from 'node:path';
7
+ import { stripSensitiveDataFromObject, TelemetryEventNames, } from '@microsoft/managed-apps-common/telemetry';
8
+ import { TELEMETRY_DIRECTORY } from '../Constants.js';
9
+ import { getScrubbedCustomDimensions } from './Scrubber/ScrubCustomDimensions.js';
10
+ const telemetryJournalFileName = /^telemetry-\d+-[0-9a-f-]+\.json$/i;
11
+ const telemetryJournalRetentionMs = 7 * 24 * 60 * 60 * 1000;
12
+ const maxTelemetryJournals = 20;
13
+ // Captures remote telemetry emitted before the command resolves its environment and cloud,
14
+ // so it can be replayed through the correctly routed OneDS writer afterward.
15
+ export class BufferedTelemetryWriter {
16
+ _events = [];
17
+ trackActivityEvent(eventName, eventData, timestamp) {
18
+ this._events.push({
19
+ method: 'trackActivityEvent',
20
+ args: [eventName, eventData],
21
+ timestamp: timestamp ?? new Date().toISOString(),
22
+ });
23
+ }
24
+ trackErrorEvent(eventName, eventData, timestamp) {
25
+ this._events.push({
26
+ method: 'trackErrorEvent',
27
+ args: [eventName, eventData],
28
+ timestamp: timestamp ?? new Date().toISOString(),
29
+ });
30
+ }
31
+ startScenario(scenarioName, startData, timestamp) {
32
+ this._events.push({
33
+ method: 'startScenario',
34
+ args: [scenarioName, startData],
35
+ timestamp: timestamp ?? new Date().toISOString(),
36
+ });
37
+ }
38
+ progressScenario(scenarioName, progressData, timestamp) {
39
+ this._events.push({
40
+ method: 'progressScenario',
41
+ args: [scenarioName, progressData],
42
+ timestamp: timestamp ?? new Date().toISOString(),
43
+ });
44
+ }
45
+ completeScenario(scenarioName, completeData, timestamp) {
46
+ this._events.push({
47
+ method: 'completeScenario',
48
+ args: [scenarioName, completeData],
49
+ timestamp: timestamp ?? new Date().toISOString(),
50
+ });
51
+ }
52
+ scenarioFailed(scenarioName, failureData, timestamp) {
53
+ this._events.push({
54
+ method: 'scenarioFailed',
55
+ args: [scenarioName, failureData],
56
+ timestamp: timestamp ?? new Date().toISOString(),
57
+ });
58
+ }
59
+ scenarioCompletedWithError(scenarioName, completeWithErrorData, timestamp) {
60
+ this._events.push({
61
+ method: 'scenarioCompletedWithError',
62
+ args: [scenarioName, completeWithErrorData],
63
+ timestamp: timestamp ?? new Date().toISOString(),
64
+ });
65
+ }
66
+ flush() {
67
+ return Promise.resolve();
68
+ }
69
+ setCustomDimension(_key, _value) { }
70
+ replay(writer, transformEventData) {
71
+ for (const event of this._events) {
72
+ const [name, eventData] = event.args;
73
+ writer[event.method](name, transformEventData ? transformEventData(eventData) : eventData, event.timestamp);
74
+ }
75
+ }
76
+ clear() {
77
+ this._events.length = 0;
78
+ }
79
+ writeToTemporaryFile(directory = TELEMETRY_DIRECTORY) {
80
+ if (this._events.length === 0) {
81
+ return undefined;
82
+ }
83
+ try {
84
+ fs.mkdirSync(directory, { recursive: true });
85
+ try {
86
+ cleanupTelemetryJournals(directory);
87
+ }
88
+ catch {
89
+ // Retention is best effort and must not prevent persisting the current journal.
90
+ }
91
+ const filePath = path.join(directory, `telemetry-${Date.now()}-${randomUUID()}.json`);
92
+ const temporaryFilePath = `${filePath}.${process.pid}.tmp`;
93
+ fs.writeFileSync(temporaryFilePath, stripSensitiveDataFromObject({
94
+ events: this._events.map((event) => ({
95
+ ...event,
96
+ args: [event.args[0], getScrubbedEventData(event)],
97
+ })),
98
+ }), 'utf-8');
99
+ fs.renameSync(temporaryFilePath, filePath);
100
+ return filePath;
101
+ }
102
+ catch {
103
+ return undefined;
104
+ }
105
+ }
106
+ }
107
+ function cleanupTelemetryJournals(directory) {
108
+ const expirationTime = Date.now() - telemetryJournalRetentionMs;
109
+ const journals = fs
110
+ .readdirSync(directory, { withFileTypes: true })
111
+ .filter((entry) => entry.isFile() && telemetryJournalFileName.test(entry.name))
112
+ .map((entry) => {
113
+ const filePath = path.join(directory, entry.name);
114
+ return { filePath, modifiedTime: fs.statSync(filePath).mtimeMs };
115
+ });
116
+ const retainedJournals = journals
117
+ .filter((journal) => {
118
+ if (journal.modifiedTime >= expirationTime) {
119
+ return true;
120
+ }
121
+ fs.rmSync(journal.filePath);
122
+ return false;
123
+ })
124
+ .sort((left, right) => left.modifiedTime - right.modifiedTime);
125
+ const journalsToRemove = retainedJournals.slice(0, Math.max(0, retainedJournals.length - (maxTelemetryJournals - 1)));
126
+ for (const journal of journalsToRemove) {
127
+ fs.rmSync(journal.filePath);
128
+ }
129
+ }
130
+ function getScrubbedEventData(event) {
131
+ const [name, eventData] = event.args;
132
+ const eventName = getOneDsEventName(event.method);
133
+ const dimensions = event.method === 'trackActivityEvent' || event.method === 'trackErrorEvent'
134
+ ? { ...eventData, operationName: name }
135
+ : {
136
+ operationName: name,
137
+ ...eventData,
138
+ scenarioName: name,
139
+ ...(event.method === 'completeScenario' && { scenarioResult: 'Success' }),
140
+ ...(event.method === 'scenarioFailed' && { scenarioResult: 'Failed' }),
141
+ ...(event.method === 'scenarioCompletedWithError' && {
142
+ scenarioResult: 'SuccessWithErrors',
143
+ }),
144
+ };
145
+ return getScrubbedCustomDimensions(eventName, name, dimensions);
146
+ }
147
+ function getOneDsEventName(method) {
148
+ switch (method) {
149
+ case 'trackActivityEvent':
150
+ return TelemetryEventNames.Activity;
151
+ case 'trackErrorEvent':
152
+ return TelemetryEventNames.Error;
153
+ case 'startScenario':
154
+ return TelemetryEventNames.StartScenario;
155
+ case 'progressScenario':
156
+ return TelemetryEventNames.ProgressScenario;
157
+ case 'completeScenario':
158
+ case 'scenarioFailed':
159
+ case 'scenarioCompletedWithError':
160
+ return TelemetryEventNames.EndScenario;
161
+ }
162
+ }
163
+ //# sourceMappingURL=BufferedTelemetryWriter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BufferedTelemetryWriter.js","sourceRoot":"","sources":["../../src/Logger/BufferedTelemetryWriter.ts"],"names":[],"mappings":"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"}
@@ -1,69 +1,47 @@
1
1
  /*!
2
2
  * Copyright (C) Microsoft Corporation. All rights reserved.
3
3
  */
4
+ import { type Region } from '@microsoft/managed-apps-common/services';
4
5
  import type { ILogger, ScenarioTracker } from '@microsoft/managed-apps-common/telemetry';
5
- import type { CliHttpClient } from '../HttpClient/CliHttpClient.js';
6
6
  import type { CliSettings, IAuthenticationProvider } from '../Types/Cli.types.js';
7
7
  import { type OneDsHttpForwardOptions } from './OneDSWriter.js';
8
+ export interface TelemetryContext {
9
+ region: Region;
10
+ environmentId?: string;
11
+ }
8
12
  /**
9
13
  * Logger implementation for the CLI.
10
14
  *
11
- * Telemetry is **unauthenticated by default**: the OneDS writer is constructed at
12
- * `create()` time with `telemetryLocation: 'global'` and no `geoLocation`, so events
13
- * start flowing immediately without any auth dependency. The writer is then upgraded to
14
- * the EU-aware authed version via one of two paths:
15
- *
16
- * - `tryEagerUpgradeAsync` — called from `runVerb` after MSAL is initialized. If a
17
- * cached account already exists, the upgrade runs synchronously before the first event
18
- * so signed-in users get authed telemetry for the entire session.
19
- * - `CliHttpClient.onFirstAuth` callback — registered in `create()`, fires (background)
20
- * after the verb's first successful authed HTTP call. Handles users who sign in
21
- * mid-session via MSAL.
22
- *
23
- * Verbs that never make an authed HTTP call and have no cached account stay on the
24
- * unauthenticated writer for their whole lifetime — events still reach 1DS, just on
25
- * the global endpoint with no `geoLocation` or `tenantId`.
26
- *
27
- * 1DS event submission uses its own ingestion credentials (not the Entra bearer token),
28
- * so events can be sent regardless of sign-in state.
15
+ * Remote telemetry is buffered until the command resolves its target environment and region.
16
+ * Console telemetry is initialized immediately when enabled.
29
17
  */
30
18
  export declare class CliLogger implements ILogger {
31
19
  private _telemetryWriters;
32
- private _unauthWriter?;
20
+ private _bufferedWriter?;
21
+ private _remoteWriter?;
33
22
  private readonly _customDimensions;
34
- private _upgradeFired;
35
23
  private readonly _sessionId;
36
- private _upgradeConfig?;
24
+ private readonly _origin;
25
+ private readonly _authenticationProvider;
26
+ private readonly _appId;
27
+ private readonly _httpForwardOptions?;
28
+ private _telemetryContext?;
29
+ private _environmentMetadataPromise?;
30
+ private _isRemoteTelemetrySuppressed;
31
+ private _isEnvironmentlessTelemetry;
37
32
  private constructor();
38
- static create(cliSettings: CliSettings, httpClient: CliHttpClient, authenticationProvider: IAuthenticationProvider, httpForwardOptions?: OneDsHttpForwardOptions): Promise<CliLogger>;
39
- /**
40
- * Eagerly run the upgrade when a cached account already exists at verb-start time,
41
- * so even the first telemetry event of the session lands on the authenticated writer.
42
- * Called from `ArgumentProvider.runVerb` after `authProvider.initAsync(region)` but
43
- * before any event is emitted.
44
- *
45
- * No-op if no cached account is present (we don't want to block CLI startup on a
46
- * network call when the user isn't even signed in), or if the upgrade has already
47
- * fired. For not-signed-in users the lazy `onFirstAuth` path still handles things
48
- * later if the verb itself triggers a sign-in.
49
- *
50
- * Cost: ~100-500ms for signed-in users — the time `getEnvironmentByName` takes.
51
- * Signed-in-but-offline users will block on the default fetch timeout before
52
- * falling through; events still flow to the unauthenticated writer in that case.
53
- */
54
- tryEagerUpgradeAsync(authenticationProvider: IAuthenticationProvider): Promise<void>;
33
+ static create(cliSettings: CliSettings, authenticationProvider: IAuthenticationProvider, httpForwardOptions?: OneDsHttpForwardOptions): Promise<CliLogger>;
34
+ setCustomDimension(_key: string, _value: string): void;
55
35
  /**
56
- * Replaces the unauthenticated OneDS writer with an EU-aware authed one. Called from
57
- * two paths:
58
- * - `tryEagerUpgradeAsync` (blocking, before the first event) when a cached account
59
- * is already present at verb-start time.
60
- * - `CliHttpClient.onFirstAuth` (fire-and-forget) after the verb's first successful
61
- * authed HTTP call, when the user signed in mid-session.
62
- * Best-effort — on any failure the unauthenticated writer stays in place and events
63
- * keep flowing to the global endpoint.
36
+ * Records the command's resolved routing context and activates the remote writer once the
37
+ * environment metadata is available.
64
38
  */
65
- private _upgradeToAuthedWriterAsync;
66
- setCustomDimension(_key: string, _value: string): void;
39
+ setTelemetryContext(context: TelemetryContext): Promise<void>;
40
+ private _getEnvironmentMetadataAsync;
41
+ private _activateRemoteWriter;
42
+ private _activateEnvironmentlessRemoteWriter;
43
+ private _createRemoteWriterAndReplay;
44
+ writeBufferedTelemetryToFile(directory?: string): string | undefined;
67
45
  trackActivityEvent(eventName: string, eventData?: object): void;
68
46
  trackErrorEvent(eventName: string, eventData?: object): void;
69
47
  trackScenario(eventName: string, eventData?: object): ScenarioTracker;
@@ -74,7 +52,7 @@ export declare class CliLogger implements ILogger {
74
52
  }): void;
75
53
  get sessionId(): string | undefined;
76
54
  set sessionId(_value: string);
77
- flush(): Promise<void>;
55
+ flush(retainBufferedTelemetry?: boolean): Promise<void>;
78
56
  withDefaults(_extras: Record<string, string | number | boolean>): ILogger;
79
57
  addDefaults(_extras: Record<string, string | number | boolean>): void;
80
58
  }
@@ -1 +1 @@
1
- {"version":3,"file":"CliLogger.d.ts","sourceRoot":"","sources":["../../src/Logger/CliLogger.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,0CAA0C,CAAC;AAGzF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,KAAK,EACV,WAAW,EACX,uBAAuB,EAExB,MAAM,oBAAoB,CAAC;AAO5B,OAAO,EAAE,KAAK,uBAAuB,EAAe,MAAM,eAAe,CAAC;AAiG1E;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,SAAU,YAAW,OAAO;IACvC,OAAO,CAAC,iBAAiB,CAA0B;IACnD,OAAO,CAAC,aAAa,CAAC,CAAc;IACpC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAA8B;IAChE,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,cAAc,CAAC,CAOrB;IAEF,OAAO;WAKa,MAAM,CACxB,WAAW,EAAE,WAAW,EACxB,UAAU,EAAE,aAAa,EACzB,sBAAsB,EAAE,uBAAuB,EAC/C,kBAAkB,CAAC,EAAE,uBAAuB,GAC3C,OAAO,CAAC,SAAS,CAAC;IAqErB;;;;;;;;;;;;;;OAcG;IACU,oBAAoB,CAC/B,sBAAsB,EAAE,uBAAuB,GAC9C,OAAO,CAAC,IAAI,CAAC;IAShB;;;;;;;;;OASG;YACW,2BAA2B;IAyDlC,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAetD,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;IAW/D,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;IAW5D,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,eAAe;IAuBrE,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI;IAI5D,YAAY,CAAC,SAAS,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAI5E,IAAW,SAAS,IAAI,MAAM,GAAG,SAAS,CAEzC;IAED,IAAW,SAAS,CAAC,MAAM,EAAE,MAAM,EAElC;IAEM,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAMtB,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,GAAG,OAAO;IAOzE,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,GAAG,IAAI;CAG7E"}
1
+ {"version":3,"file":"CliLogger.d.ts","sourceRoot":"","sources":["../../src/Logger/CliLogger.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,EAAE,KAAK,MAAM,EAA2B,MAAM,yCAAyC,CAAC;AAC/F,OAAO,KAAK,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,0CAA0C,CAAC;AAGzF,OAAO,KAAK,EACV,WAAW,EACX,uBAAuB,EAExB,MAAM,oBAAoB,CAAC;AAO5B,OAAO,EAAE,KAAK,uBAAuB,EAAe,MAAM,eAAe,CAAC;AAE1E,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAwID;;;;;GAKG;AACH,qBAAa,SAAU,YAAW,OAAO;IACvC,OAAO,CAAC,iBAAiB,CAA0B;IACnD,OAAO,CAAC,eAAe,CAAC,CAA0B;IAClD,OAAO,CAAC,aAAa,CAAC,CAAc;IACpC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAA8B;IAChE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAA0B;IAClE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAA0B;IAC/D,OAAO,CAAC,iBAAiB,CAAC,CAAmB;IAC7C,OAAO,CAAC,2BAA2B,CAAC,CAElC;IACF,OAAO,CAAC,4BAA4B,CAAS;IAC7C,OAAO,CAAC,2BAA2B,CAAS;IAE5C,OAAO;WAgBa,MAAM,CACxB,WAAW,EAAE,WAAW,EACxB,sBAAsB,EAAE,uBAAuB,EAC/C,kBAAkB,CAAC,EAAE,uBAAuB,GAC3C,OAAO,CAAC,SAAS,CAAC;IA4Cd,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAiB7D;;;OAGG;IACU,mBAAmB,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;YAuB5D,4BAA4B;YAoB5B,qBAAqB;IAuBnC,OAAO,CAAC,oCAAoC;IAc5C,OAAO,CAAC,4BAA4B;IA6C7B,4BAA4B,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAGpE,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;IAW/D,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;IAW5D,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,eAAe;IAuBrE,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI;IAI5D,YAAY,CAAC,SAAS,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAI5E,IAAW,SAAS,IAAI,MAAM,GAAG,SAAS,CAEzC;IAED,IAAW,SAAS,CAAC,MAAM,EAAE,MAAM,EAElC;IAEY,KAAK,CAAC,uBAAuB,UAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAc3D,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,GAAG,OAAO;IAOzE,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,GAAG,IAAI;CAG7E"}
@@ -3,13 +3,39 @@
3
3
  */
4
4
  import { randomUUID } from 'node:crypto';
5
5
  import { getEnvironmentByName } from '@microsoft/managed-apps-actions';
6
- import { cloudToRegionSafe } from '@microsoft/managed-apps-common/services';
6
+ import { regionToClusterCategory } from '@microsoft/managed-apps-common/services';
7
7
  import packageJson from '../../package.json' with { type: 'json' };
8
8
  import { getCliEnvVarBool } from '../Utils/EnvVars.js';
9
+ import { BufferedTelemetryWriter } from './BufferedTelemetryWriter.js';
9
10
  import { ConsoleWriter } from './ConsoleWriter.js';
10
11
  import { EUGeoRegions } from './Constants.js';
11
12
  import { resolveUserTelemetrySettings } from './LoggerSettings.js';
12
13
  import { OneDSWriter } from './OneDSWriter.js';
14
+ const environmentlessIdentifierDimensions = new Set(['environmentid', 'tenantid', 'objectid']);
15
+ function isEnvironmentlessIdentifierDimension(key) {
16
+ return environmentlessIdentifierDimensions.has(key.toLowerCase());
17
+ }
18
+ function removeEnvironmentlessIdentifierDimensions(eventData) {
19
+ if (!eventData) {
20
+ return eventData;
21
+ }
22
+ return Object.fromEntries(Object.entries(eventData).filter(([key]) => !isEnvironmentlessIdentifierDimension(key)));
23
+ }
24
+ /**
25
+ * Returns true if the environment's cluster category is compatible with the region's cluster
26
+ * category. First Release environments are part of the public production cloud.
27
+ * If the environment has no cluster category, it is considered a match for all regions.
28
+ *
29
+ * This is required to handle the edge case when a command is run against a non-prod environment
30
+ * without providing the --cloud param.
31
+ */
32
+ function doesEnvironmentMatchRegion(metadata, region) {
33
+ const environmentClusterCategory = metadata?.properties.cluster?.category?.toLowerCase();
34
+ const regionClusterCategory = regionToClusterCategory(region);
35
+ return (!environmentClusterCategory ||
36
+ environmentClusterCategory === regionClusterCategory ||
37
+ (regionClusterCategory === 'prod' && environmentClusterCategory === 'firstrelease'));
38
+ }
13
39
  function stringifyErrorWithStack(error) {
14
40
  if (!error)
15
41
  return undefined;
@@ -99,169 +125,161 @@ function createScenarioLogger(telemetryWriters, name, data) {
99
125
  /**
100
126
  * Logger implementation for the CLI.
101
127
  *
102
- * Telemetry is **unauthenticated by default**: the OneDS writer is constructed at
103
- * `create()` time with `telemetryLocation: 'global'` and no `geoLocation`, so events
104
- * start flowing immediately without any auth dependency. The writer is then upgraded to
105
- * the EU-aware authed version via one of two paths:
106
- *
107
- * - `tryEagerUpgradeAsync` — called from `runVerb` after MSAL is initialized. If a
108
- * cached account already exists, the upgrade runs synchronously before the first event
109
- * so signed-in users get authed telemetry for the entire session.
110
- * - `CliHttpClient.onFirstAuth` callback — registered in `create()`, fires (background)
111
- * after the verb's first successful authed HTTP call. Handles users who sign in
112
- * mid-session via MSAL.
113
- *
114
- * Verbs that never make an authed HTTP call and have no cached account stay on the
115
- * unauthenticated writer for their whole lifetime — events still reach 1DS, just on
116
- * the global endpoint with no `geoLocation` or `tenantId`.
117
- *
118
- * 1DS event submission uses its own ingestion credentials (not the Entra bearer token),
119
- * so events can be sent regardless of sign-in state.
128
+ * Remote telemetry is buffered until the command resolves its target environment and region.
129
+ * Console telemetry is initialized immediately when enabled.
120
130
  */
121
131
  export class CliLogger {
122
132
  _telemetryWriters = [];
123
- _unauthWriter;
133
+ _bufferedWriter;
134
+ _remoteWriter;
124
135
  _customDimensions = {};
125
- _upgradeFired = false;
126
136
  _sessionId;
127
- _upgradeConfig;
128
- constructor(telemetryWriters, sessionId) {
137
+ _origin;
138
+ _authenticationProvider;
139
+ _appId;
140
+ _httpForwardOptions;
141
+ _telemetryContext;
142
+ _environmentMetadataPromise;
143
+ _isRemoteTelemetrySuppressed = false;
144
+ _isEnvironmentlessTelemetry = false;
145
+ constructor(telemetryWriters, sessionId, origin, authenticationProvider, appId, httpForwardOptions) {
129
146
  this._telemetryWriters = telemetryWriters;
130
147
  this._sessionId = sessionId;
148
+ this._origin = origin;
149
+ this._authenticationProvider = authenticationProvider;
150
+ this._appId = appId ?? '';
151
+ this._httpForwardOptions = httpForwardOptions;
131
152
  }
132
- static async create(cliSettings, httpClient, authenticationProvider, httpForwardOptions) {
133
- const environmentId = cliSettings.appConfig?.environmentId;
134
- const region = cloudToRegionSafe(cliSettings.appConfig?.cloud);
135
- const appId = cliSettings.appConfig?.appId;
153
+ static async create(cliSettings, authenticationProvider, httpForwardOptions) {
136
154
  const sessionId = randomUUID();
137
155
  const telemetryWriters = [];
138
156
  const userTelemetrySettings = resolveUserTelemetrySettings();
139
157
  if (userTelemetrySettings.console) {
140
158
  telemetryWriters.push(new ConsoleWriter(sessionId, cliSettings.origin));
141
159
  }
142
- const instance = new CliLogger(telemetryWriters, sessionId);
160
+ const instance = new CliLogger(telemetryWriters, sessionId, cliSettings.origin, authenticationProvider, cliSettings.appConfig?.appId, httpForwardOptions);
143
161
  // In mocked-backend mode (E2E) never open a real 1DS/remote telemetry channel: it bypasses
144
162
  // the injected mock HttpClient and would attempt live network egress, hanging the CLI on
145
163
  // flush at exit. This keeps "mock all backend calls" complete for E2E runs.
146
164
  const suppressRemoteTelemetry = getCliEnvVarBool('MOCK_BACKEND');
147
- if (userTelemetrySettings.remote && region && environmentId && !suppressRemoteTelemetry) {
148
- // Push the unauthenticated writer immediately. Defaults to the global 1DS endpoint
149
- // and omits `geoLocation`; if/when auth succeeds, the upgrade swaps in the EU-aware
150
- // writer below.
151
- const unauth = new OneDSWriter(sessionId, region, 'global', environmentId, appId ?? '', undefined, packageJson.version, cliSettings.origin, httpForwardOptions);
152
- instance._unauthWriter = unauth;
153
- instance._telemetryWriters.push(unauth);
154
- instance._upgradeConfig = {
155
- sessionId,
156
- region,
157
- environmentId,
158
- appId,
159
- cliSettings,
160
- httpForwardOptions,
161
- };
162
- // Register the lazy upgrade: fires once when the verb makes its first successful
163
- // authed HTTP call. By that point we know auth works, so the metadata fetch can't
164
- // pop a browser.
165
- httpClient.onFirstAuth(() => instance._upgradeToAuthedWriterAsync(authenticationProvider));
165
+ if (userTelemetrySettings.remote && !suppressRemoteTelemetry) {
166
+ instance._bufferedWriter = new BufferedTelemetryWriter();
167
+ instance._telemetryWriters.push(instance._bufferedWriter);
166
168
  }
167
169
  // Emit the first-party embedding host correlation id (if supplied via
168
170
  // MS_CLI_EMBEDDING_HOST_CORRELATION_ID) on every event. It is injected as a default
169
171
  // property here and promoted to a dedicated top-level telemetry column
170
- // (`embeddingHostCorrelationId`) by the OneDS writer's propertyColumnMap. Set after the
171
- // writers exist so it lands on the console + unauth writers immediately and survives the
172
- // authed-writer swap (re-applied from `_customDimensions`). The internal per-invocation
173
- // `sessionId` is intentionally left untouched.
172
+ // (`embeddingHostCorrelationId`) by the OneDS writer's propertyColumnMap. Preserve it until
173
+ // the delayed remote writer is created, while applying it to the console writer immediately.
174
+ // The internal per-invocation `sessionId` is intentionally left untouched.
174
175
  if (cliSettings.embeddingHostCorrelationId) {
175
176
  instance.setCustomDimension('embeddingHostCorrelationId', cliSettings.embeddingHostCorrelationId);
176
177
  }
177
178
  return instance;
178
179
  }
179
- /**
180
- * Eagerly run the upgrade when a cached account already exists at verb-start time,
181
- * so even the first telemetry event of the session lands on the authenticated writer.
182
- * Called from `ArgumentProvider.runVerb` after `authProvider.initAsync(region)` but
183
- * before any event is emitted.
184
- *
185
- * No-op if no cached account is present (we don't want to block CLI startup on a
186
- * network call when the user isn't even signed in), or if the upgrade has already
187
- * fired. For not-signed-in users the lazy `onFirstAuth` path still handles things
188
- * later if the verb itself triggers a sign-in.
189
- *
190
- * Cost: ~100-500ms for signed-in users — the time `getEnvironmentByName` takes.
191
- * Signed-in-but-offline users will block on the default fetch timeout before
192
- * falling through; events still flow to the unauthenticated writer in that case.
193
- */
194
- async tryEagerUpgradeAsync(authenticationProvider) {
195
- if (this._upgradeFired || !this._upgradeConfig) {
196
- return;
180
+ setCustomDimension(_key, _value) {
181
+ try {
182
+ if (!_key || !_value) {
183
+ return;
184
+ }
185
+ if (this._isEnvironmentlessTelemetry && isEnvironmentlessIdentifierDimension(_key)) {
186
+ return;
187
+ }
188
+ this._customDimensions[_key] = _value;
189
+ this._telemetryWriters.forEach((writer) => {
190
+ writer.setCustomDimension(_key, _value);
191
+ });
197
192
  }
198
- if (await authenticationProvider.getCachedAccountAsync()) {
199
- await this._upgradeToAuthedWriterAsync(authenticationProvider);
193
+ catch {
194
+ // No-op. Telemetry failures should not block CLI operations
200
195
  }
201
196
  }
202
197
  /**
203
- * Replaces the unauthenticated OneDS writer with an EU-aware authed one. Called from
204
- * two paths:
205
- * - `tryEagerUpgradeAsync` (blocking, before the first event) when a cached account
206
- * is already present at verb-start time.
207
- * - `CliHttpClient.onFirstAuth` (fire-and-forget) after the verb's first successful
208
- * authed HTTP call, when the user signed in mid-session.
209
- * Best-effort — on any failure the unauthenticated writer stays in place and events
210
- * keep flowing to the global endpoint.
198
+ * Records the command's resolved routing context and activates the remote writer once the
199
+ * environment metadata is available.
211
200
  */
212
- async _upgradeToAuthedWriterAsync(authenticationProvider) {
213
- if (this._upgradeFired || !this._upgradeConfig || !this._unauthWriter) {
201
+ async setTelemetryContext(context) {
202
+ if (!this._telemetryContext) {
203
+ this._telemetryContext = context;
204
+ }
205
+ else if (!this._telemetryContext.environmentId && context.environmentId) {
206
+ // The initial region is authoritative. Environment resolution may happen later.
207
+ this._telemetryContext = {
208
+ ...this._telemetryContext,
209
+ environmentId: context.environmentId,
210
+ };
211
+ }
212
+ else {
213
+ // Context already complete; avoid redundant metadata lookups.
214
214
  return;
215
215
  }
216
- this._upgradeFired = true;
217
- const { sessionId, region, environmentId, appId, cliSettings, httpForwardOptions } = this._upgradeConfig;
218
- try {
219
- const metadata = await getEnvironmentByName(environmentId);
220
- let telemetryLocation = 'global';
221
- if (metadata?.properties?.location && EUGeoRegions.has(metadata.properties.location)) {
222
- telemetryLocation = 'eu';
223
- }
224
- const authed = new OneDSWriter(sessionId, region, telemetryLocation, environmentId, appId ?? '', metadata?.properties?.cluster?.geoShortName, packageJson.version, cliSettings.origin, httpForwardOptions);
225
- // Replace the unauth writer in place. Pre-upgrade events have already been
226
- // dispatched to the unauth writer's channel; we don't re-send them.
227
- const idx = this._telemetryWriters.indexOf(this._unauthWriter);
228
- if (idx >= 0) {
229
- this._telemetryWriters[idx] = authed;
230
- }
231
- else {
232
- this._telemetryWriters.push(authed);
233
- }
234
- this._unauthWriter = authed;
235
- // Re-apply previously set dimensions to the replacement writer.
236
- for (const [key, value] of Object.entries(this._customDimensions)) {
237
- authed.setCustomDimension(key, value);
238
- }
239
- // Set the tenantId dimension AFTER the swap so it lands on the authed writer
240
- // (and any ConsoleWriter that's still in the list), not the discarded unauth one.
241
- const tenantId = authenticationProvider.getUserTenantId();
242
- if (tenantId) {
243
- this.setCustomDimension('tenantId', tenantId);
216
+ if (this._telemetryContext.environmentId) {
217
+ const metadata = await this._getEnvironmentMetadataAsync(this._telemetryContext.environmentId, this._telemetryContext.region);
218
+ await this._activateRemoteWriter(metadata);
219
+ }
220
+ }
221
+ async _getEnvironmentMetadataAsync(environmentId, region) {
222
+ this._environmentMetadataPromise ??= getEnvironmentByName(environmentId)
223
+ .then((metadata) => {
224
+ if (!metadata.location || !doesEnvironmentMatchRegion(metadata, region)) {
225
+ this._isRemoteTelemetrySuppressed = true;
226
+ return undefined;
244
227
  }
228
+ return metadata;
229
+ })
230
+ .catch(() => {
231
+ this._isRemoteTelemetrySuppressed = true;
232
+ return undefined;
233
+ });
234
+ return this._environmentMetadataPromise;
235
+ }
236
+ async _activateRemoteWriter(environmentMetadata) {
237
+ if (!this._bufferedWriter ||
238
+ !this._telemetryContext?.environmentId ||
239
+ !environmentMetadata ||
240
+ !environmentMetadata.location ||
241
+ this._isRemoteTelemetrySuppressed) {
242
+ // Keep unresolved or mismatched-environment events buffered for CLI teardown to persist.
243
+ return;
245
244
  }
246
- catch {
247
- // Best-effort. Unauth writer stays in place; subsequent events keep going to global.
245
+ const telemetryLocation = EUGeoRegions.has(environmentMetadata.location) ? 'eu' : 'global';
246
+ this._createRemoteWriterAndReplay(this._telemetryContext.region, telemetryLocation, this._telemetryContext.environmentId, environmentMetadata.properties.cluster?.geoShortName);
247
+ }
248
+ _activateEnvironmentlessRemoteWriter() {
249
+ if (!this._bufferedWriter || this._telemetryContext?.environmentId) {
250
+ return;
248
251
  }
252
+ this._isEnvironmentlessTelemetry = true;
253
+ this._createRemoteWriterAndReplay(this._telemetryContext?.region ?? 'prod', 'global', undefined, undefined);
249
254
  }
250
- setCustomDimension(_key, _value) {
255
+ _createRemoteWriterAndReplay(region, telemetryLocation, environmentId, geoLocation) {
256
+ if (!this._bufferedWriter || this._remoteWriter) {
257
+ return;
258
+ }
251
259
  try {
252
- if (!_key || !_value) {
253
- return;
260
+ const remoteWriter = new OneDSWriter(this._sessionId, region, telemetryLocation, environmentId, this._appId, geoLocation, packageJson.version, this._origin, this._httpForwardOptions);
261
+ this._remoteWriter = remoteWriter;
262
+ for (const [key, value] of Object.entries(this._customDimensions)) {
263
+ if (!this._isEnvironmentlessTelemetry || !isEnvironmentlessIdentifierDimension(key)) {
264
+ remoteWriter.setCustomDimension(key, value);
265
+ }
254
266
  }
255
- // not guarding on _upgradeFired to preserve dimensions set before the swap is complete
256
- this._customDimensions[_key] = _value;
257
- this._telemetryWriters.forEach((writer) => {
258
- writer.setCustomDimension(_key, _value);
259
- });
267
+ if (!this._isEnvironmentlessTelemetry) {
268
+ const tenantId = this._authenticationProvider.getUserTenantId();
269
+ if (tenantId) {
270
+ remoteWriter.setCustomDimension('tenantId', tenantId);
271
+ }
272
+ }
273
+ this._bufferedWriter.replay(remoteWriter, this._isEnvironmentlessTelemetry ? removeEnvironmentlessIdentifierDimensions : undefined);
274
+ this._telemetryWriters.push(remoteWriter);
260
275
  }
261
276
  catch {
262
- // No-op. Telemetry failures should not block CLI operations
277
+ // Keep the buffer intact; CLI teardown persists it after the command session ends.
263
278
  }
264
279
  }
280
+ writeBufferedTelemetryToFile(directory) {
281
+ return this._bufferedWriter?.writeToTemporaryFile(directory);
282
+ }
265
283
  trackActivityEvent(eventName, eventData) {
266
284
  try {
267
285
  if (this._telemetryWriters.length === 0) {
@@ -319,8 +337,19 @@ export class CliLogger {
319
337
  set sessionId(_value) {
320
338
  // No-op
321
339
  }
322
- flush() {
323
- return Promise.all(this._telemetryWriters.map((writer) => writer.flush())).then(() => undefined);
340
+ async flush(retainBufferedTelemetry = false) {
341
+ if (this._environmentMetadataPromise) {
342
+ // Ensure any in-flight metadata lookup can activate and replay buffered remote telemetry.
343
+ const metadata = await this._environmentMetadataPromise;
344
+ await this._activateRemoteWriter(metadata);
345
+ }
346
+ else {
347
+ this._activateEnvironmentlessRemoteWriter();
348
+ }
349
+ await Promise.all(this._telemetryWriters.map((writer) => writer.flush()));
350
+ if (this._remoteWriter && !retainBufferedTelemetry) {
351
+ this._bufferedWriter?.clear();
352
+ }
324
353
  }
325
354
  withDefaults(_extras) {
326
355
  // No-op — CLI logger does not implement local scope defaults today.