From aa6f9e24f8c82df60511b2c7f77143bf6b561080 Mon Sep 17 00:00:00 2001 From: Slawomir Koszewski Date: Sat, 7 Mar 2026 15:18:46 +0100 Subject: [PATCH] Refactored configuration loading function. --- package-lock.json | 36 +++++++++++++++++---- package.json | 5 +-- src/cli/commands/auth.ts | 1 + src/cli/commands/get-token.ts | 10 ++---- src/cli/commands/login.ts | 4 +-- src/cli/commands/logout.ts | 4 +-- src/cli/commands/rest.ts | 10 ++---- src/cli/commands/shared.ts | 4 +-- src/index.ts | 60 +++++++++++------------------------ 9 files changed, 63 insertions(+), 71 deletions(-) create mode 100644 src/cli/commands/auth.ts diff --git a/package-lock.json b/package-lock.json index 5682352..802fe90 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@slawek/sk-az-tools", - "version": "0.4.3", + "version": "0.4.4", "lockfileVersion": 3, "requires": true, "packages": { @@ -16,7 +16,9 @@ "@slawek/sk-tools": ">=0.1.0", "azure-devops-node-api": "^15.1.2", "minimatch": "^10.1.2", - "open": "^10.1.0" + "open": "^10.1.0", + "semver": "^7.7.2", + "uuid": "^11.1.0" }, "bin": { "sk-az-tools": "dist/cli.js" @@ -154,6 +156,15 @@ "node": ">=16" } }, + "node_modules/@azure/identity/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@azure/logger": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.3.0.tgz", @@ -233,6 +244,15 @@ "node": ">=0.8.0" } }, + "node_modules/@azure/msal-node/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@babel/runtime": { "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", @@ -1576,12 +1596,16 @@ "license": "MIT" }, "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], "license": "MIT", "bin": { - "uuid": "dist/bin/uuid" + "uuid": "dist/esm/bin/uuid" } }, "node_modules/wrappy": { diff --git a/package.json b/package.json index d6a0a5a..fdcda4d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@slawek/sk-az-tools", - "version": "0.4.3", + "version": "0.4.4", "type": "module", "files": [ "dist", @@ -27,7 +27,8 @@ "azure-devops-node-api": "^15.1.2", "minimatch": "^10.1.2", "open": "^10.1.0", - "semver": "^7.7.2" + "semver": "^7.7.2", + "uuid": "^11.1.0" }, "devDependencies": { "@types/node": ">=24.0.0", diff --git a/src/cli/commands/auth.ts b/src/cli/commands/auth.ts new file mode 100644 index 0000000..8096cb4 --- /dev/null +++ b/src/cli/commands/auth.ts @@ -0,0 +1 @@ +// SPDX-License-Identifier: MIT diff --git a/src/cli/commands/get-token.ts b/src/cli/commands/get-token.ts index 65b8ef1..776a4a1 100644 --- a/src/cli/commands/get-token.ts +++ b/src/cli/commands/get-token.ts @@ -2,7 +2,7 @@ import { acquireResourceTokenFromLogin } from "../../azure/index.ts"; import { getDevOpsApiToken } from "../../devops/index.ts"; -import { loadPublicConfig } from "../../index.ts"; +import { loadConfig } from "../../index.ts"; import type { CommandValues } from "./types.ts"; @@ -19,13 +19,7 @@ export async function runGetTokenCommand(values: CommandValues): Promise { - const config = await loadPublicConfig(); + const config = await loadConfig("public-config"); return login({ tenantId: config.tenantId, clientId: config.clientId, diff --git a/src/cli/commands/logout.ts b/src/cli/commands/logout.ts index b980a76..2304b19 100644 --- a/src/cli/commands/logout.ts +++ b/src/cli/commands/logout.ts @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT import { logout } from "../../azure/index.ts"; -import { loadPublicConfig } from "../../index.ts"; +import { loadConfig } from "../../index.ts"; import type { CommandValues } from "./types.ts"; @@ -13,7 +13,7 @@ Options: } export async function runLogoutCommand(values: CommandValues): Promise { - const config = await loadPublicConfig(); + const config = await loadConfig("public-config"); return logout({ tenantId: config.tenantId, clientId: config.clientId, diff --git a/src/cli/commands/rest.ts b/src/cli/commands/rest.ts index 7bb4f90..84608f6 100644 --- a/src/cli/commands/rest.ts +++ b/src/cli/commands/rest.ts @@ -2,7 +2,7 @@ import { acquireResourceTokenFromLogin } from "../../azure/index.ts"; import { getDevOpsApiToken } from "../../devops/index.ts"; -import { loadPublicConfig } from "../../index.ts"; +import { loadConfig } from "../../index.ts"; import type { CommandValues } from "./types.ts"; @@ -54,13 +54,7 @@ async function getAutoAuthorizationHeader(url: URL): Promise { return null; } - const config = await loadPublicConfig(); - if (!config.tenantId) { - throw new Error("tenantId is required"); - } - if (!config.clientId) { - throw new Error("clientId is required"); - } + const config = await loadConfig("public-config"); if (host === "management.azure.com") { const result = await acquireResourceTokenFromLogin({ diff --git a/src/cli/commands/shared.ts b/src/cli/commands/shared.ts index 8573a55..bd931de 100644 --- a/src/cli/commands/shared.ts +++ b/src/cli/commands/shared.ts @@ -2,7 +2,7 @@ import { minimatch } from "minimatch"; -import { loadPublicConfig } from "../../index.ts"; +import { loadConfig } from "../../index.ts"; import { getGraphClient } from "../../graph/auth.ts"; type PermissionRow = { @@ -28,7 +28,7 @@ export function filterByDisplayName(rows: T[], pattern } export async function getGraphClientFromPublicConfig(): Promise<{ client: any }> { - const config = await loadPublicConfig(); + const config = await loadConfig("public-config"); return getGraphClient({ tenantId: config.tenantId, clientId: config.clientId, diff --git a/src/index.ts b/src/index.ts index 3396585..b06e3e9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,58 +1,36 @@ // SPDX-License-Identifier: MIT -import { readFile } from "node:fs/promises"; -import os from "node:os"; -import path from "node:path"; +import { validate as validateUuid } from "uuid"; +import { getConfig } from "@slawek/sk-tools"; type Config = { - tenantId?: string; - clientId?: string; + tenantId: string; + clientId: string; }; -type ConfigCandidate = { - tenantId?: unknown; - clientId?: unknown; -}; - -export function getUserConfigDir(): string { - if (process.platform === "win32") { - return process.env.LOCALAPPDATA ?? path.join(os.homedir(), "AppData", "Local"); - } - - return process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), ".config"); -} - -async function loadConfig(configFileName: string): Promise { - if (typeof configFileName !== "string" || configFileName.trim() === "") { +export async function loadConfig(configName: string): Promise { + if (typeof configName !== "string" || configName.trim() === "") { throw new Error( - 'Invalid config file name. Expected a non-empty string like "public-config.json" or "confidential-config.json".', + 'Invalid config name. Expected a non-empty string like "public-config" or "confidential-config".', ); } - const envConfig: Config = { + const envConfig = { tenantId: process.env.AZURE_TENANT_ID, clientId: process.env.AZURE_CLIENT_ID, }; - const configPath = path.join(getUserConfigDir(), "sk-az-tools", configFileName); - return readFile(configPath, "utf8") - .then((configJson) => JSON.parse(configJson) as ConfigCandidate) - .catch((err: unknown) => { - if ((err as { code?: string } | null)?.code === "ENOENT") { - return {} as ConfigCandidate; - } - throw err; - }) - .then((json) => ({ - tenantId: typeof json.tenantId === "string" && json.tenantId ? json.tenantId : envConfig.tenantId, - clientId: typeof json.clientId === "string" && json.clientId ? json.clientId : envConfig.clientId, - })); -} + const json = (await getConfig("sk-az-tools", configName)) as Record; -export function loadPublicConfig(): Promise { - return loadConfig("public-config.json"); -} + const tenantId = (typeof json.tenantId === "string" && json.tenantId ? json.tenantId : envConfig.tenantId) ?? ""; + const clientId = (typeof json.clientId === "string" && json.clientId ? json.clientId : envConfig.clientId) ?? ""; -export function loadConfidentialConfig(): Promise { - return loadConfig("confidential-config.json"); + if (!validateUuid(tenantId ?? "") || !validateUuid(clientId ?? "")) { + throw new Error("tenantId and clientId must be valid GUIDs."); + } + + return { + tenantId, + clientId, + }; }