diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..fcde8a8 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "files.exclude": { + "node_modules": true + } +} diff --git a/azure-node-playground.code-workspace b/azure-node-playground.code-workspace new file mode 100644 index 0000000..8bfb72d --- /dev/null +++ b/azure-node-playground.code-workspace @@ -0,0 +1,16 @@ +{ + "folders": [ + { + "path": "." + }, + { + "path": "../sk-az-tools" + }, + { + "path": "../docs-harvester-node" + } + ], + "settings": { + + } +} diff --git a/bin/list-apps.mjs b/bin/list-apps.mjs index ac10f19..12a789e 100755 --- a/bin/list-apps.mjs +++ b/bin/list-apps.mjs @@ -1,5 +1,5 @@ #! /usr/bin/env node -import { loginInteractive } from "../src/azure.js"; +import { loginInteractive } from "sk-az-tools/azure"; import { Client } from "@microsoft/microsoft-graph-client"; const scopes = [ diff --git a/bin/login.mjs b/bin/login.mjs index 6e05828..986cca7 100644 --- a/bin/login.mjs +++ b/bin/login.mjs @@ -2,7 +2,7 @@ import { readFileSync } from "fs"; import { parseArgs } from "util"; -import { getCredential } from "../src/azure.js"; +import { getCredential } from "sk-az-tools/azure"; let config = {}; diff --git a/bin/setup-app.mjs b/bin/setup-app.mjs index e031723..01afd6d 100755 --- a/bin/setup-app.mjs +++ b/bin/setup-app.mjs @@ -9,7 +9,7 @@ const args = parseArgs({ "app-name": { type: "string", short: "a" }, help: { type: "boolean", short: "h" }, "generate-client-secret": { type: "boolean", short: "s" }, - "config": { type: "string", short: "c", default: "config.js" }, + "config": { type: "string", short: "c", default: "config.json" }, "write-config": { type: "string", short: "w" }, }, }); diff --git a/package-lock.json b/package-lock.json index 8a41c7f..dd0575f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,28 @@ "dependencies": { "@azure/identity": "^4.13.0", "@azure/msal-node": "^5.0.2", - "@microsoft/microsoft-graph-client": "^3.0.7" + "@microsoft/microsoft-graph-client": "^3.0.7", + "docs-harvester": "file:../docs-harvester-node", + "sk-az-tools": "file:../sk-az-tools" + }, + "engines": { + "node": ">=24.0.0" + } + }, + "../docs-harvester-node": { + "name": "docs-harvester", + "version": "0.1.0", + "bin": { + "docs-harvester": "src/cli.js" + } + }, + "../sk-az-tools": { + "version": "0.1.0", + "dependencies": { + "@azure/identity": "^4.13.0", + "@azure/msal-node": "^5.0.3", + "@microsoft/microsoft-graph-client": "^3.0.7", + "azure-devops-node-api": "^15.1.2" }, "engines": { "node": ">=24.0.0" @@ -175,12 +196,12 @@ } }, "node_modules/@azure/msal-node": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-5.0.2.tgz", - "integrity": "sha512-3tHeJghckgpTX98TowJoXOjKGuds0L+FKfeHJtoZFl2xvwE6RF65shZJzMQ5EQZWXzh3sE1i9gE+m3aRMachjA==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-5.0.3.tgz", + "integrity": "sha512-DTiu9SEblUVbgiuXWtCXFS2OoIVZPNbPFfK7AVhsWCD4bCHqvHcnDz9uCxk5wnUYJX9Ik0KxjGDFLwH9/lrE+w==", "license": "MIT", "dependencies": { - "@azure/msal-common": "16.0.2", + "@azure/msal-common": "16.0.3", "jsonwebtoken": "^9.0.0", "uuid": "^8.3.0" }, @@ -189,9 +210,9 @@ } }, "node_modules/@azure/msal-node/node_modules/@azure/msal-common": { - "version": "16.0.2", - "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-16.0.2.tgz", - "integrity": "sha512-ZJ/UR7lyqIntURrIJCyvScwJFanM9QhJYcJCheB21jZofGKpP9QxWgvADANo7UkresHKzV+6YwoeZYP7P7HvUg==", + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-16.0.3.tgz", + "integrity": "sha512-3aedNnM0CHVuVZ+BqembdZWgovqe96BJ4YxGoIK0+qhoBZQsAhfwXdhjen72K94pkSQHtzlJ7fAq6w7knFZsng==", "license": "MIT", "engines": { "node": ">=0.8.0" @@ -334,6 +355,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/docs-harvester": { + "resolved": "../docs-harvester-node", + "link": true + }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -570,6 +595,10 @@ "node": ">=10" } }, + "node_modules/sk-az-tools": { + "resolved": "../sk-az-tools", + "link": true + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", diff --git a/package.json b/package.json index ecec45f..64ceb3a 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,8 @@ "dependencies": { "@azure/identity": "^4.13.0", "@azure/msal-node": "^5.0.2", - "@microsoft/microsoft-graph-client": "^3.0.7" + "@microsoft/microsoft-graph-client": "^3.0.7", + "docs-harvester": "file:../docs-harvester-node", + "sk-az-tools": "file:../sk-az-tools" } } diff --git a/src/prototypes/devops.mjs b/src/prototypes/devops.mjs new file mode 100755 index 0000000..70fed82 --- /dev/null +++ b/src/prototypes/devops.mjs @@ -0,0 +1,27 @@ +#!/usr/bin/env node + +// The current Node.js Azure DevOps client uses old deprecated APIs. + +import { config } from "../../public-config.js"; +import { getDevOpsClients } from "sk-az-tools/devops"; + +async function main() { + const { coreClient, gitClient } = await getDevOpsClients( + 'https://dev.azure.com/skoszewski', + config.tenantId, + config.clientId, + ); + + const orgUrl = 'https://dev.azure.com/skoszewski'; + const projects = await coreClient.getProjects(); + console.log(`Projects in organization '${orgUrl}':`); + projects.forEach((project) => { + console.log(`- ${project.name} (ID: ${project.id})`); + }); +} + +await main().catch((e) => { + console.error("Error in main:", e); + console.error(e.stack); + process.exit(1); +}); diff --git a/src/prototypes/graph-calls.mjs b/src/prototypes/graph-calls.mjs index c212712..5316d55 100755 --- a/src/prototypes/graph-calls.mjs +++ b/src/prototypes/graph-calls.mjs @@ -1,82 +1,72 @@ #!/usr/bin/env node -import { loginInteractive } from "sk-az-tools/azure"; -import { config } from "../public-config.js"; -import { createHash } from "crypto"; -import { Client } from "@microsoft/microsoft-graph-client"; +import { config } from "../../public-config.js"; +import { getGraphClient } from "sk-az-tools/graph"; -// const scopes = ["https://management.azure.com/.default"]; -const scopes = ["https://graph.microsoft.com/.default"]; - -let token; - -try { - token = await loginInteractive({ +async function main() { + const { client } = await getGraphClient({ tenantId: config.tenantId, clientId: config.clientId, - scopes, }); -} catch (e) { - console.error("Login failed:", e); + + // Let's find the application by its display name + let result; + + // First get info about me. + result = await client.api("/me").get(); + console.log("Logged in as:"); + console.log(` Display Name: ${result.displayName}`); + console.log(` User Principal Name: ${result.userPrincipalName}`); + console.log(` ID: ${result.id}`); + console.log(""); + + // Now let's find the application by its display name + + result = await client + .api("/applications") + .filter("displayName eq 'Azure Node Playground Public'") + .get(); + + const apps = result.value ?? []; + console.log( + `Registered applications with the name 'Azure Node Playground' (${apps.length}):`, + ); + + if (apps.length !== 1) { + console.error( + "Expected exactly one application with the name 'Azure Node Playground'. Please ensure it is registered in your Azure AD tenant.", + ); + process.exit(1); + } + + console.log(`Application ID: ${apps[0].appId}`); + + // Let's find the service principals for this application + result = await client + .api("/servicePrincipals") + .filter(`appId eq '${apps[0].appId}'`) + .get(); + + const sps = result.value ?? []; + console.log(`Service principals for the application (${sps.length}):`); + + if (sps.length === 0) { + console.error( + "No service principals found for the application. Please ensure the application is properly configured in your Azure AD tenant.", + ); + process.exit(1); + } + + sps.forEach((sp, index) => { + console.log(`Service Principal ${index + 1}:`); + console.log(` ID: ${sp.id}`); + console.log(` App ID: ${sp.appId}`); + console.log(` Display Name: ${sp.displayName}`); + }); +} + +await main().catch((e) => { + console.error("Error in main:", e); console.error(e.stack); process.exit(1); -} - -console.log("Access token acquired."); - -// Print SHA-256 hash of the access token for verification purposes -const hash = createHash("sha256").update(token.accessToken).digest("hex"); -console.log("SHA-256 hash of access token:", hash); -console.log("Token expires on:", token.expiresOn); - -const client = Client.init({ - authProvider: (done) => { - done(null, token.accessToken); - }, -}); - -let result; - -result = await client - .api("/applications") - .filter("displayName eq 'Azure Node Playground Public'") - .get(); - -const apps = result.value ?? []; -console.log( - `Registered applications with the name 'Azure Node Playground' (${apps.length}):`, -); - -if (apps.length !== 1) { - console.error( - "Expected exactly one application with the name 'Azure Node Playground'. Please ensure it is registered in your Azure AD tenant.", - ); - process.exit(1); -} - -console.log(`Application ID: ${apps[0].appId}`); - -// Let's find the service principals for this application -result = await client - .api("/servicePrincipals") - .filter(`appId eq '${apps[0].appId}'`) - .get(); - -const sps = result.value ?? []; -console.log( - `Service principals for the application (${sps.length}):`, -); - -if (sps.length === 0) { - console.error( - "No service principals found for the application. Please ensure the application is properly configured in your Azure AD tenant.", - ); - process.exit(1); -} - -sps.forEach((sp, index) => { - console.log(`Service Principal ${index + 1}:`); - console.log(` ID: ${sp.id}`); - console.log(` App ID: ${sp.appId}`); - console.log(` Display Name: ${sp.displayName}`); });