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.
- package/.vscode/extensions.json +3 -0
- package/.vscode/launch.json +25 -0
- package/.vscode/settings.json +25 -0
- package/.vscode/tasks.json +28 -0
- package/README.md +203 -0
- package/dist/auth.d.ts +11 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +52 -0
- package/dist/auth.js.map +1 -0
- package/dist/azure-pim.d.ts +44 -0
- package/dist/azure-pim.d.ts.map +1 -0
- package/dist/azure-pim.js +183 -0
- package/dist/azure-pim.js.map +1 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +339 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +54 -0
- package/dist/index.js.map +1 -0
- package/dist/ui/App.d.ts +9 -0
- package/dist/ui/App.d.ts.map +1 -0
- package/dist/ui/App.js +85 -0
- package/dist/ui/App.js.map +1 -0
- package/dist/ui/components.d.ts +18 -0
- package/dist/ui/components.d.ts.map +1 -0
- package/dist/ui/components.js +13 -0
- package/dist/ui/components.js.map +1 -0
- package/dist/ui/router.d.ts +22 -0
- package/dist/ui/router.d.ts.map +1 -0
- package/dist/ui/router.js +19 -0
- package/dist/ui/router.js.map +1 -0
- package/dist/ui/screens/ActivateFlow.d.ts +25 -0
- package/dist/ui/screens/ActivateFlow.d.ts.map +1 -0
- package/dist/ui/screens/ActivateFlow.js +195 -0
- package/dist/ui/screens/ActivateFlow.js.map +1 -0
- package/dist/ui/screens/DeactivateFlow.d.ts +22 -0
- package/dist/ui/screens/DeactivateFlow.d.ts.map +1 -0
- package/dist/ui/screens/DeactivateFlow.js +115 -0
- package/dist/ui/screens/DeactivateFlow.js.map +1 -0
- package/dist/ui/useExitConfirmation.d.ts +7 -0
- package/dist/ui/useExitConfirmation.d.ts.map +1 -0
- package/dist/ui/useExitConfirmation.js +17 -0
- package/dist/ui/useExitConfirmation.js.map +1 -0
- package/dist/ui/widgets/CheckboxList.d.ts +14 -0
- package/dist/ui/widgets/CheckboxList.d.ts.map +1 -0
- package/dist/ui/widgets/CheckboxList.js +59 -0
- package/dist/ui/widgets/CheckboxList.js.map +1 -0
- package/dist/ui/widgets/ConfirmPrompt.d.ts +8 -0
- package/dist/ui/widgets/ConfirmPrompt.d.ts.map +1 -0
- package/dist/ui/widgets/ConfirmPrompt.js +19 -0
- package/dist/ui/widgets/ConfirmPrompt.js.map +1 -0
- package/dist/ui/widgets/NumberPrompt.d.ts +10 -0
- package/dist/ui/widgets/NumberPrompt.d.ts.map +1 -0
- package/dist/ui/widgets/NumberPrompt.js +38 -0
- package/dist/ui/widgets/NumberPrompt.js.map +1 -0
- package/dist/ui/widgets/SelectList.d.ts +14 -0
- package/dist/ui/widgets/SelectList.d.ts.map +1 -0
- package/dist/ui/widgets/SelectList.js +34 -0
- package/dist/ui/widgets/SelectList.js.map +1 -0
- package/dist/ui/widgets/TextPrompt.d.ts +10 -0
- package/dist/ui/widgets/TextPrompt.d.ts.map +1 -0
- package/dist/ui/widgets/TextPrompt.js +30 -0
- package/dist/ui/widgets/TextPrompt.js.map +1 -0
- package/dist/ui.d.ts +26 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +183 -0
- package/dist/ui.js.map +1 -0
- package/package.json +47 -0
- package/pnpm-workspace.yaml +2 -0
- package/src/auth.ts +66 -0
- package/src/azure-pim.ts +262 -0
- package/src/cli.ts +401 -0
- package/src/index.ts +65 -0
- package/src/ui.ts +253 -0
- 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
|
+
}
|