azp-cli 0.0.1

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 (77) hide show
  1. package/.vscode/extensions.json +3 -0
  2. package/.vscode/launch.json +25 -0
  3. package/.vscode/settings.json +25 -0
  4. package/.vscode/tasks.json +28 -0
  5. package/README.md +203 -0
  6. package/dist/auth.d.ts +11 -0
  7. package/dist/auth.d.ts.map +1 -0
  8. package/dist/auth.js +52 -0
  9. package/dist/auth.js.map +1 -0
  10. package/dist/azure-pim.d.ts +44 -0
  11. package/dist/azure-pim.d.ts.map +1 -0
  12. package/dist/azure-pim.js +183 -0
  13. package/dist/azure-pim.js.map +1 -0
  14. package/dist/cli.d.ts +5 -0
  15. package/dist/cli.d.ts.map +1 -0
  16. package/dist/cli.js +339 -0
  17. package/dist/cli.js.map +1 -0
  18. package/dist/index.d.ts +3 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +54 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/ui/App.d.ts +9 -0
  23. package/dist/ui/App.d.ts.map +1 -0
  24. package/dist/ui/App.js +85 -0
  25. package/dist/ui/App.js.map +1 -0
  26. package/dist/ui/components.d.ts +18 -0
  27. package/dist/ui/components.d.ts.map +1 -0
  28. package/dist/ui/components.js +13 -0
  29. package/dist/ui/components.js.map +1 -0
  30. package/dist/ui/router.d.ts +22 -0
  31. package/dist/ui/router.d.ts.map +1 -0
  32. package/dist/ui/router.js +19 -0
  33. package/dist/ui/router.js.map +1 -0
  34. package/dist/ui/screens/ActivateFlow.d.ts +25 -0
  35. package/dist/ui/screens/ActivateFlow.d.ts.map +1 -0
  36. package/dist/ui/screens/ActivateFlow.js +195 -0
  37. package/dist/ui/screens/ActivateFlow.js.map +1 -0
  38. package/dist/ui/screens/DeactivateFlow.d.ts +22 -0
  39. package/dist/ui/screens/DeactivateFlow.d.ts.map +1 -0
  40. package/dist/ui/screens/DeactivateFlow.js +115 -0
  41. package/dist/ui/screens/DeactivateFlow.js.map +1 -0
  42. package/dist/ui/useExitConfirmation.d.ts +7 -0
  43. package/dist/ui/useExitConfirmation.d.ts.map +1 -0
  44. package/dist/ui/useExitConfirmation.js +17 -0
  45. package/dist/ui/useExitConfirmation.js.map +1 -0
  46. package/dist/ui/widgets/CheckboxList.d.ts +14 -0
  47. package/dist/ui/widgets/CheckboxList.d.ts.map +1 -0
  48. package/dist/ui/widgets/CheckboxList.js +59 -0
  49. package/dist/ui/widgets/CheckboxList.js.map +1 -0
  50. package/dist/ui/widgets/ConfirmPrompt.d.ts +8 -0
  51. package/dist/ui/widgets/ConfirmPrompt.d.ts.map +1 -0
  52. package/dist/ui/widgets/ConfirmPrompt.js +19 -0
  53. package/dist/ui/widgets/ConfirmPrompt.js.map +1 -0
  54. package/dist/ui/widgets/NumberPrompt.d.ts +10 -0
  55. package/dist/ui/widgets/NumberPrompt.d.ts.map +1 -0
  56. package/dist/ui/widgets/NumberPrompt.js +38 -0
  57. package/dist/ui/widgets/NumberPrompt.js.map +1 -0
  58. package/dist/ui/widgets/SelectList.d.ts +14 -0
  59. package/dist/ui/widgets/SelectList.d.ts.map +1 -0
  60. package/dist/ui/widgets/SelectList.js +34 -0
  61. package/dist/ui/widgets/SelectList.js.map +1 -0
  62. package/dist/ui/widgets/TextPrompt.d.ts +10 -0
  63. package/dist/ui/widgets/TextPrompt.d.ts.map +1 -0
  64. package/dist/ui/widgets/TextPrompt.js +30 -0
  65. package/dist/ui/widgets/TextPrompt.js.map +1 -0
  66. package/dist/ui.d.ts +26 -0
  67. package/dist/ui.d.ts.map +1 -0
  68. package/dist/ui.js +183 -0
  69. package/dist/ui.js.map +1 -0
  70. package/package.json +47 -0
  71. package/pnpm-workspace.yaml +2 -0
  72. package/src/auth.ts +66 -0
  73. package/src/azure-pim.ts +262 -0
  74. package/src/cli.ts +401 -0
  75. package/src/index.ts +65 -0
  76. package/src/ui.ts +253 -0
  77. package/tsconfig.json +45 -0
package/src/index.ts ADDED
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from "commander";
4
+ import { version } from "../package.json";
5
+ import { authenticate } from "./auth";
6
+ import { showMainMenu } from "./cli";
7
+ import { logBlank, logDim, logError, showHeader } from "./ui";
8
+
9
+ const program = new Command();
10
+
11
+ program.name("azp-cli").description("Azure PIM CLI - A CLI tool for Azure Privilege Identity Management (PIM)").version(version);
12
+
13
+ program
14
+ .command("activate", { isDefault: true })
15
+ .description("Activate a role in Azure PIM")
16
+ .alias("a")
17
+ .action(async () => {
18
+ try {
19
+ // Show header
20
+ showHeader();
21
+
22
+ // Authenticate
23
+ const authContext = await authenticate();
24
+
25
+ // show mainmenu
26
+ await showMainMenu(authContext);
27
+ } catch (error: any) {
28
+ const errorMessage = error instanceof Error ? error.message : String(error);
29
+ logBlank();
30
+ logError(`An error occurred: ${errorMessage}`);
31
+
32
+ if (errorMessage.includes("AADSTS")) {
33
+ logBlank();
34
+ logError("Authentication error detected. Please ensure you have the necessary permissions and try again.");
35
+ logDim("Tip: Make sure you are logged in with 'az login' before running this command.");
36
+ }
37
+
38
+ if (errorMessage.includes("Azure CLI not found") || errorMessage.includes("AzureCliCredential")) {
39
+ logBlank();
40
+ logDim("Tip: Make sure Azure CLI is installed and you are logged in with 'az login'.");
41
+ }
42
+
43
+ logBlank();
44
+ process.exit(1);
45
+ }
46
+ });
47
+
48
+ program
49
+ .command("deactivate")
50
+ .description("Deactivate a role in Azure PIM")
51
+ .alias("d")
52
+ .action(async () => {
53
+ showHeader();
54
+ logDim("Deactivate role command invoked - use the main menu to deactivate roles.");
55
+ });
56
+
57
+ program
58
+ .command("help")
59
+ .description("Display help information about azp-cli commands")
60
+ .action(() => {
61
+ showHeader();
62
+ program.outputHelp();
63
+ });
64
+
65
+ program.parse();
package/src/ui.ts ADDED
@@ -0,0 +1,253 @@
1
+ import chalk from "chalk";
2
+ import ora, { type Ora } from "ora";
3
+
4
+ // ===============================
5
+ // Spinner Management
6
+ // ===============================
7
+
8
+ let currentSpinner: Ora | null = null;
9
+
10
+ /**
11
+ * Starts a new spinner with the given text.
12
+ * If a spinner is already running, it will be stopped first.
13
+ */
14
+ export const startSpinner = (text: string): Ora => {
15
+ if (currentSpinner) {
16
+ currentSpinner.stop();
17
+ }
18
+ currentSpinner = ora({
19
+ text: chalk.cyan(text),
20
+ color: "cyan",
21
+ }).start();
22
+ return currentSpinner;
23
+ };
24
+
25
+ /**
26
+ * Updates the text of the current spinner.
27
+ */
28
+ export const updateSpinner = (text: string): void => {
29
+ if (currentSpinner) {
30
+ currentSpinner.text = chalk.cyan(text);
31
+ }
32
+ };
33
+
34
+ /**
35
+ * Stops the current spinner with a success message.
36
+ */
37
+ export const succeedSpinner = (text?: string): void => {
38
+ if (currentSpinner) {
39
+ if (text) {
40
+ currentSpinner.succeed(chalk.green(text));
41
+ } else {
42
+ currentSpinner.succeed();
43
+ }
44
+ currentSpinner = null;
45
+ }
46
+ };
47
+
48
+ /**
49
+ * Stops the current spinner with a failure message.
50
+ */
51
+ export const failSpinner = (text?: string): void => {
52
+ if (currentSpinner) {
53
+ if (text) {
54
+ currentSpinner.fail(chalk.red(text));
55
+ } else {
56
+ currentSpinner.fail();
57
+ }
58
+ currentSpinner = null;
59
+ }
60
+ };
61
+
62
+ /**
63
+ * Stops the current spinner with a warning message.
64
+ */
65
+ export const warnSpinner = (text?: string): void => {
66
+ if (currentSpinner) {
67
+ if (text) {
68
+ currentSpinner.warn(chalk.yellow(text));
69
+ } else {
70
+ currentSpinner.warn();
71
+ }
72
+ currentSpinner = null;
73
+ }
74
+ };
75
+
76
+ /**
77
+ * Stops the current spinner with an info message.
78
+ */
79
+ export const infoSpinner = (text?: string): void => {
80
+ if (currentSpinner) {
81
+ if (text) {
82
+ currentSpinner.info(chalk.blue(text));
83
+ } else {
84
+ currentSpinner.info();
85
+ }
86
+ currentSpinner = null;
87
+ }
88
+ };
89
+
90
+ /**
91
+ * Stops the current spinner without persisting any text.
92
+ */
93
+ export const stopSpinner = (): void => {
94
+ if (currentSpinner) {
95
+ currentSpinner.stop();
96
+ currentSpinner = null;
97
+ }
98
+ };
99
+
100
+ // ===============================
101
+ // Console Log Helpers
102
+ // ===============================
103
+
104
+ /**
105
+ * Logs an info message with a blue info icon.
106
+ */
107
+ export const logInfo = (message: string): void => {
108
+ console.log(chalk.blue("ℹ"), chalk.blue(message));
109
+ };
110
+
111
+ /**
112
+ * Logs a success message with a green checkmark.
113
+ */
114
+ export const logSuccess = (message: string): void => {
115
+ console.log(chalk.green("✔"), chalk.green(message));
116
+ };
117
+
118
+ /**
119
+ * Logs an error message with a red cross.
120
+ */
121
+ export const logError = (message: string): void => {
122
+ console.log(chalk.red("✖"), chalk.red(message));
123
+ };
124
+
125
+ /**
126
+ * Logs a warning message with a yellow warning icon.
127
+ */
128
+ export const logWarning = (message: string): void => {
129
+ console.log(chalk.yellow("⚠"), chalk.yellow(message));
130
+ };
131
+
132
+ /**
133
+ * Logs a dimmed/secondary message.
134
+ */
135
+ export const logDim = (message: string): void => {
136
+ console.log(chalk.dim(message));
137
+ };
138
+
139
+ /**
140
+ * Logs a blank line.
141
+ */
142
+ export const logBlank = (): void => {
143
+ console.log();
144
+ };
145
+
146
+ // ===============================
147
+ // UI Elements
148
+ // ===============================
149
+
150
+ /**
151
+ * Displays the application header/banner.
152
+ */
153
+ export const showHeader = (): void => {
154
+ logBlank();
155
+ console.log(chalk.cyan.bold("╔════════════════════════════════════════════════════╗"));
156
+ console.log(chalk.cyan.bold("║") + chalk.white.bold(" Azure PIM CLI - Role Activation Manager ") + chalk.cyan.bold("║"));
157
+ console.log(chalk.cyan.bold("╚════════════════════════════════════════════════════╝"));
158
+ logBlank();
159
+ };
160
+
161
+ /**
162
+ * Displays a section divider.
163
+ */
164
+ export const showDivider = (): void => {
165
+ console.log(chalk.dim("─".repeat(54)));
166
+ };
167
+
168
+ /**
169
+ * Formats a role display string.
170
+ */
171
+ export const formatRole = (roleName: string, scopeDisplayName: string): string => {
172
+ return `${chalk.white.bold(roleName)} ${chalk.dim("@")} ${chalk.cyan(scopeDisplayName)}`;
173
+ };
174
+
175
+ /**
176
+ * Formats an active role display string with additional metadata.
177
+ */
178
+ export const formatActiveRole = (roleName: string, scopeDisplayName: string, subscriptionName: string, startDateTime: string): string => {
179
+ const startDate = new Date(startDateTime).toLocaleString();
180
+ return `${chalk.white.bold(roleName)} ${chalk.dim("@")} ${chalk.cyan(scopeDisplayName)} ${chalk.dim(`(${subscriptionName})`)} ${chalk.dim(
181
+ `[Started: ${startDate}]`
182
+ )}`;
183
+ };
184
+
185
+ /**
186
+ * Formats a subscription display string.
187
+ */
188
+ export const formatSubscription = (displayName: string, subscriptionId: string): string => {
189
+ return `${chalk.white.bold(displayName)} ${chalk.dim(`(${subscriptionId})`)}`;
190
+ };
191
+
192
+ /**
193
+ * Formats a status display string based on status type.
194
+ */
195
+ export const formatStatus = (status: string): string => {
196
+ switch (status.toLowerCase()) {
197
+ case "approved":
198
+ case "provisioned":
199
+ case "activated":
200
+ return chalk.green.bold(`✔ ${status}`);
201
+ case "denied":
202
+ case "failed":
203
+ return chalk.red.bold(`✖ ${status}`);
204
+ case "pendingapproval":
205
+ case "pending":
206
+ return chalk.yellow.bold(`⏳ ${status}`);
207
+ default:
208
+ return chalk.blue.bold(`ℹ ${status}`);
209
+ }
210
+ };
211
+
212
+ /**
213
+ * Displays a summary box for activation/deactivation results.
214
+ */
215
+ export const showSummary = (title: string, items: { label: string; value: string }[]): void => {
216
+ const boxWidth = 54;
217
+ const titleWidth = Math.max(0, boxWidth - 4 - title.length);
218
+ logBlank();
219
+ console.log(chalk.cyan.bold(`┌─ ${title} ${"─".repeat(titleWidth)}`));
220
+ items.forEach((item) => {
221
+ console.log(chalk.cyan("│ ") + chalk.dim(`${item.label}: `) + chalk.white(item.value));
222
+ });
223
+ console.log(chalk.cyan.bold("└" + "─".repeat(boxWidth)));
224
+ logBlank();
225
+ };
226
+
227
+ // ===============================
228
+ // Async Operation Wrapper
229
+ // ===============================
230
+
231
+ /**
232
+ * Wraps an async operation with a spinner.
233
+ * Shows spinner while operation is in progress, then shows success/failure.
234
+ */
235
+ export const withSpinner = async <T>(text: string, operation: () => Promise<T>, successText?: string, failText?: string): Promise<T> => {
236
+ startSpinner(text);
237
+ try {
238
+ const result = await operation();
239
+ if (successText) {
240
+ succeedSpinner(successText);
241
+ } else {
242
+ succeedSpinner();
243
+ }
244
+ return result;
245
+ } catch (error) {
246
+ if (failText) {
247
+ failSpinner(failText);
248
+ } else {
249
+ failSpinner();
250
+ }
251
+ throw error;
252
+ }
253
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "compilerOptions": {
3
+ /* Language and Environment */
4
+ "target": "ES2022",
5
+ "lib": ["ES2022"],
6
+
7
+ /* Modules */
8
+ "module": "NodeNext",
9
+ "moduleResolution": "NodeNext",
10
+ "rootDir": "./src",
11
+ "baseUrl": "./src",
12
+ "paths": {
13
+ "@/*": ["./*"]
14
+ },
15
+
16
+ /* Emit */
17
+ "outDir": "./dist",
18
+ "removeComments": true,
19
+ "sourceMap": true,
20
+ "declaration": true,
21
+ "declarationMap": true,
22
+
23
+ /* Interop Constraints */
24
+ "esModuleInterop": true,
25
+ "allowSyntheticDefaultImports": true,
26
+ "forceConsistentCasingInFileNames": true,
27
+ "isolatedModules": true,
28
+
29
+ /* Type Checking */
30
+ "strict": true,
31
+ "noUnusedLocals": true,
32
+ "noUnusedParameters": true,
33
+ "noImplicitReturns": true,
34
+ "noFallthroughCasesInSwitch": true,
35
+ "noUncheckedIndexedAccess": true,
36
+ "allowUnusedLabels": false,
37
+ "allowUnreachableCode": false,
38
+
39
+ /* Completeness */
40
+ "skipLibCheck": true,
41
+ "resolveJsonModule": true
42
+ },
43
+ "include": ["src/**/*"],
44
+ "exclude": ["node_modules", "dist", "**/*.spec.ts", "**/*.test.ts"]
45
+ }