diff --git a/change/change-4c12df77-7abf-469a-b9fc-dbfdca11d96e.json b/change/change-4c12df77-7abf-469a-b9fc-dbfdca11d96e.json new file mode 100644 index 00000000..2280e2c3 --- /dev/null +++ b/change/change-4c12df77-7abf-469a-b9fc-dbfdca11d96e.json @@ -0,0 +1,25 @@ +{ + "changes": [ + { + "type": "none", + "comment": "Add platform-specific binary package for azureauth", + "packageName": "@microsoft/azureauth-darwin-arm64", + "email": "sverre.johansen@microsoft.com", + "dependentChangeType": "none" + }, + { + "type": "none", + "comment": "Add platform-specific binary package for azureauth", + "packageName": "@microsoft/azureauth-darwin-x64", + "email": "sverre.johansen@microsoft.com", + "dependentChangeType": "none" + }, + { + "type": "none", + "comment": "Add platform-specific binary package for azureauth", + "packageName": "@microsoft/azureauth-win32-x64", + "email": "sverre.johansen@microsoft.com", + "dependentChangeType": "none" + } + ] +} \ No newline at end of file diff --git a/change/change-7b213891-7a87-43e9-8e50-b3c5772dd1ca.json b/change/change-7b213891-7a87-43e9-8e50-b3c5772dd1ca.json new file mode 100644 index 00000000..d54bf0de --- /dev/null +++ b/change/change-7b213891-7a87-43e9-8e50-b3c5772dd1ca.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "type": "patch", + "comment": "Add optionalDependencies support for platform-specific binaries (esbuild pattern)", + "packageName": "azureauth", + "email": "sverre.johansen@microsoft.com", + "dependentChangeType": "patch" + } + ] +} \ No newline at end of file diff --git a/packages/azureauth-darwin-arm64/.gitignore b/packages/azureauth-darwin-arm64/.gitignore new file mode 100644 index 00000000..499ed7f4 --- /dev/null +++ b/packages/azureauth-darwin-arm64/.gitignore @@ -0,0 +1,2 @@ +# Binary files are downloaded during publish, not committed +bin/ diff --git a/packages/azureauth-darwin-arm64/LICENSE b/packages/azureauth-darwin-arm64/LICENSE new file mode 100644 index 00000000..22aed37e --- /dev/null +++ b/packages/azureauth-darwin-arm64/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Microsoft Corporation. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/azureauth-darwin-arm64/package.json b/packages/azureauth-darwin-arm64/package.json new file mode 100644 index 00000000..22dd32a3 --- /dev/null +++ b/packages/azureauth-darwin-arm64/package.json @@ -0,0 +1,20 @@ +{ + "name": "@microsoft/azureauth-darwin-arm64", + "version": "0.8.4", + "description": "The macOS ARM64 binary for azureauth", + "repository": { + "url": "https://github.com/microsoft/ado-npm-auth" + }, + "license": "MIT", + "preferUnplugged": true, + "os": [ + "darwin" + ], + "cpu": [ + "arm64" + ], + "files": [ + "bin", + "LICENSE" + ] +} diff --git a/packages/azureauth-darwin-x64/.gitignore b/packages/azureauth-darwin-x64/.gitignore new file mode 100644 index 00000000..499ed7f4 --- /dev/null +++ b/packages/azureauth-darwin-x64/.gitignore @@ -0,0 +1,2 @@ +# Binary files are downloaded during publish, not committed +bin/ diff --git a/packages/azureauth-darwin-x64/LICENSE b/packages/azureauth-darwin-x64/LICENSE new file mode 100644 index 00000000..22aed37e --- /dev/null +++ b/packages/azureauth-darwin-x64/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Microsoft Corporation. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/azureauth-darwin-x64/package.json b/packages/azureauth-darwin-x64/package.json new file mode 100644 index 00000000..ea991d65 --- /dev/null +++ b/packages/azureauth-darwin-x64/package.json @@ -0,0 +1,20 @@ +{ + "name": "@microsoft/azureauth-darwin-x64", + "version": "0.8.4", + "description": "The macOS x64 binary for azureauth", + "repository": { + "url": "https://github.com/microsoft/ado-npm-auth" + }, + "license": "MIT", + "preferUnplugged": true, + "os": [ + "darwin" + ], + "cpu": [ + "x64" + ], + "files": [ + "bin", + "LICENSE" + ] +} diff --git a/packages/azureauth-win32-x64/.gitignore b/packages/azureauth-win32-x64/.gitignore new file mode 100644 index 00000000..499ed7f4 --- /dev/null +++ b/packages/azureauth-win32-x64/.gitignore @@ -0,0 +1,2 @@ +# Binary files are downloaded during publish, not committed +bin/ diff --git a/packages/azureauth-win32-x64/LICENSE b/packages/azureauth-win32-x64/LICENSE new file mode 100644 index 00000000..22aed37e --- /dev/null +++ b/packages/azureauth-win32-x64/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Microsoft Corporation. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/azureauth-win32-x64/package.json b/packages/azureauth-win32-x64/package.json new file mode 100644 index 00000000..b24ad76b --- /dev/null +++ b/packages/azureauth-win32-x64/package.json @@ -0,0 +1,20 @@ +{ + "name": "@microsoft/azureauth-win32-x64", + "version": "0.8.4", + "description": "The Windows x64 binary for azureauth", + "repository": { + "url": "https://github.com/microsoft/ado-npm-auth" + }, + "license": "MIT", + "preferUnplugged": true, + "os": [ + "win32" + ], + "cpu": [ + "x64" + ], + "files": [ + "bin", + "LICENSE" + ] +} diff --git a/packages/node-azureauth/package.json b/packages/node-azureauth/package.json index f5d725fd..6c17b874 100644 --- a/packages/node-azureauth/package.json +++ b/packages/node-azureauth/package.json @@ -11,14 +11,17 @@ "type": "module", "scripts": { "build": "tsc", - "bundle": "yarn bundleInstall && yarn bundleCli && yarn bundleIndex", + "bundle": "yarn bundleInstall && yarn bundleCli && yarn bundleIndex && yarn bundlePublish", "bundleInstall": "esbuild --sourcemap --bundle --minify --platform=node --outfile=dist/install.cjs src/install.ts", "bundleCli": "esbuild --sourcemap --bundle --minify --platform=node cli.js --outfile=dist/cli.cjs", "bundleIndex": "esbuild --sourcemap --bundle --minify --platform=node --format=esm src/index.ts --outfile=dist/index.js", + "bundlePublish": "esbuild --sourcemap --bundle --minify --platform=node --outfile=dist/publish-all.cjs src/publish-all.ts", "lint": "prettier --check src/**/*.ts ./cli.js ", "start": "tsc --watch", "test": "vitest run src", - "postinstall": "node ./scripts/install.cjs" + "postinstall": "node ./scripts/install.cjs", + "publish-all": "node ./dist/publish-all.cjs", + "publish-all:dry-run": "node ./dist/publish-all.cjs --dry-run" }, "files": [ "dist/install.cjs", diff --git a/packages/node-azureauth/src/azure-auth-command.ts b/packages/node-azureauth/src/azure-auth-command.ts index daf51bed..c1d23f87 100644 --- a/packages/node-azureauth/src/azure-auth-command.ts +++ b/packages/node-azureauth/src/azure-auth-command.ts @@ -1,7 +1,56 @@ import path from "node:path"; import process from "node:process"; +import fs from "node:fs"; +import { createRequire } from "node:module"; +import { PLATFORM_PACKAGES } from "./constants.js"; + +const require = createRequire(import.meta.url); + +/** + * Try to find the binary from the platform-specific optional dependency. + * Returns the path to the binary if found, null otherwise. + */ +function getBinaryFromOptionalDep(): string | null { + const platform = process.platform; + const arch = process.arch; + + const packageName = PLATFORM_PACKAGES[platform]?.[arch]; + if (!packageName) { + return null; + } + + const binaryName = platform === "win32" ? "azureauth.exe" : "azureauth"; + + try { + // Resolve the package directory + const packageJson = require.resolve(`${packageName}/package.json`); + const packageDir = path.dirname(packageJson); + const binaryPath = path.join(packageDir, "bin", binaryName); + + if (fs.existsSync(binaryPath)) { + return binaryPath; + } + } catch { + // Package not installed, fall through to return null + } + + return null; +} + +/** + * Get the path to the azureauth binary. + * First checks for platform-specific optional dependency, + * then falls back to the postinstall-downloaded binary. + */ export const azureAuthCommand = () => { + // First, try the platform-specific optional dependency + const optionalDepBinary = getBinaryFromOptionalDep(); + if (optionalDepBinary) { + return optionalDepBinary; + } + + // Fall back to the postinstall-downloaded binary let azureauth = path.join(__dirname, "..", "bin", "azureauth", "azureauth"); if (process.platform === "win32") { diff --git a/packages/node-azureauth/src/constants.ts b/packages/node-azureauth/src/constants.ts new file mode 100644 index 00000000..9117a6c3 --- /dev/null +++ b/packages/node-azureauth/src/constants.ts @@ -0,0 +1,11 @@ +export const AZURE_AUTH_VERSION = "0.8.4"; + +export const PLATFORM_PACKAGES: Record> = { + darwin: { + arm64: "@microsoft/azureauth-darwin-arm64", + x64: "@microsoft/azureauth-darwin-x64", + }, + win32: { + x64: "@microsoft/azureauth-win32-x64", + }, +}; diff --git a/packages/node-azureauth/src/install.ts b/packages/node-azureauth/src/install.ts index 54e27843..28b4a1fa 100644 --- a/packages/node-azureauth/src/install.ts +++ b/packages/node-azureauth/src/install.ts @@ -4,7 +4,33 @@ import fs from "node:fs"; import { DownloaderHelper } from "node-downloader-helper"; import decompress from "decompress"; -const AZURE_AUTH_VERSION = "0.8.4"; +import { AZURE_AUTH_VERSION, PLATFORM_PACKAGES } from "./constants.js"; + +declare const require: NodeRequire; + +export { AZURE_AUTH_VERSION }; + +/** + * Check if the binary is available via optional dependency. + */ +function isOptionalDepInstalled(): boolean { + const platformPackage = PLATFORM_PACKAGES[process.platform]?.[process.arch]; + if (!platformPackage) { + return false; + } + + const binaryName = + process.platform === "win32" ? "azureauth.exe" : "azureauth"; + + try { + const resolvedPath = require.resolve(`${platformPackage}/package.json`); + const packageDir = path.dirname(resolvedPath); + const binaryPath = path.join(packageDir, "bin", binaryName); + return fs.existsSync(binaryPath); + } catch { + return false; + } +} async function download(url: string, saveDirectory: string): Promise { const downloader = new DownloaderHelper(url, saveDirectory); @@ -46,6 +72,14 @@ export const AZUREAUTH_NAME = : AZUREAUTH_NAME_MAP.def; export const install = async () => { + // Check if binary is available via optional dependency (esbuild pattern) + if (isOptionalDepInstalled()) { + console.log( + "azureauth binary available via platform-specific package, skipping download", + ); + return; + } + const OUTPUT_DIR = path.join(__dirname, "..", "bin"); const fileExist = (pathToCheck: string) => { try { diff --git a/packages/node-azureauth/src/publish-all.ts b/packages/node-azureauth/src/publish-all.ts new file mode 100644 index 00000000..62ecd440 --- /dev/null +++ b/packages/node-azureauth/src/publish-all.ts @@ -0,0 +1,188 @@ +#!/usr/bin/env node + +/** + * Publish script for azureauth packages. + * + * Usage: + * node dist/publish-all.cjs [--dry-run] + * + * This script: + * 1. Downloads and packages platform-specific binaries + * 2. Publishes platform packages to npm + * 3. Adds optionalDependencies to main package + * 4. Publishes main package to npm + * + * Prerequisites: + * - Must be logged in to npm (`npm login`) + * - Must have publish access to @microsoft scope + */ + +import fs from "node:fs"; +import path from "node:path"; +import { Readable } from "node:stream"; +import { pipeline } from "node:stream/promises"; +import { createWriteStream } from "node:fs"; +import { execSync } from "node:child_process"; + +import { AZURE_AUTH_VERSION } from "./install.js"; + +const PACKAGES_DIR = path.resolve(__dirname, "..", ".."); +const MAIN_PACKAGE_DIR = path.resolve(__dirname, ".."); + +const DRY_RUN = process.argv.includes("--dry-run"); + +const GITHUB_RELEASE_URL = `https://github.com/AzureAD/microsoft-authentication-cli/releases/download/${AZURE_AUTH_VERSION}`; + +interface PlatformConfig { + package: string; + archive: string; + binary: string; +} + +const PLATFORMS: PlatformConfig[] = [ + { + package: "azureauth-darwin-arm64", + archive: `azureauth-${AZURE_AUTH_VERSION}-osx-arm64.tar.gz`, + binary: "azureauth", + }, + { + package: "azureauth-darwin-x64", + archive: `azureauth-${AZURE_AUTH_VERSION}-osx-x64.tar.gz`, + binary: "azureauth", + }, + { + package: "azureauth-win32-x64", + archive: `azureauth-${AZURE_AUTH_VERSION}-win10-x64.zip`, + binary: "azureauth.exe", + }, +]; + +async function downloadFile(url: string, destPath: string): Promise { + console.log(` Downloading ${url}...`); + const response = await fetch(url, { redirect: "follow" }); + if (!response.ok) { + throw new Error( + `Failed to download ${url}: ${response.status} ${response.statusText}`, + ); + } + const nodeStream = Readable.fromWeb(response.body as any); + await pipeline(nodeStream, createWriteStream(destPath)); +} + +function extractArchive(archivePath: string, destDir: string): void { + if (archivePath.endsWith(".tar.gz")) { + execSync(`tar -xzf "${archivePath}" -C "${destDir}"`, { stdio: "inherit" }); + } else if (archivePath.endsWith(".zip")) { + execSync(`unzip -o "${archivePath}" -d "${destDir}"`, { stdio: "inherit" }); + } +} + +async function packagePlatform(platformConfig: PlatformConfig): Promise { + const { package: packageName, archive, binary } = platformConfig; + const packageDir = path.join(PACKAGES_DIR, packageName); + const binDir = path.join(packageDir, "bin"); + const archivePath = path.join(packageDir, archive); + const binaryPath = path.join(binDir, binary); + + // Skip if binary already exists + if (fs.existsSync(binaryPath)) { + console.log(` Binary already exists at ${binaryPath}`); + return; + } + + // Create bin directory + if (!fs.existsSync(binDir)) { + fs.mkdirSync(binDir, { recursive: true }); + } + + // Download and extract + const url = `${GITHUB_RELEASE_URL}/${archive}`; + await downloadFile(url, archivePath); + extractArchive(archivePath, binDir); + + // Set permissions and cleanup + if (binary === "azureauth") { + fs.chmodSync(binaryPath, 0o755); + } + fs.unlinkSync(archivePath); + + if (!fs.existsSync(binaryPath)) { + throw new Error(`Binary not found at ${binaryPath}`); + } +} + +function npmPublish(packageDir: string, isScoped: boolean = false): void { + const args = ["publish"]; + if (isScoped) args.push("--access", "public"); + if (DRY_RUN) args.push("--dry-run"); + + console.log(` Running: npm ${args.join(" ")} in ${packageDir}`); + execSync(`npm ${args.join(" ")}`, { cwd: packageDir, stdio: "inherit" }); +} + +async function main(): Promise { + console.log("=".repeat(60)); + console.log("Azureauth Publish Script"); + console.log("=".repeat(60)); + console.log(`Binary version: ${AZURE_AUTH_VERSION}`); + console.log(`Dry run: ${DRY_RUN}`); + console.log(); + + // Step 1: Package binaries + console.log("Step 1: Packaging platform binaries..."); + for (const platform of PLATFORMS) { + console.log(`\n[${platform.package}]`); + await packagePlatform(platform); + } + + // Step 2: Publish platform packages + console.log("\n" + "=".repeat(60)); + console.log("Step 2: Publishing platform packages..."); + for (const platform of PLATFORMS) { + const packageDir = path.join(PACKAGES_DIR, platform.package); + console.log(`\n[${platform.package}]`); + npmPublish(packageDir, true); + } + + // Step 3: Update main package with optionalDependencies + console.log("\n" + "=".repeat(60)); + console.log("Step 3: Updating main package..."); + const mainPkgPath = path.join(MAIN_PACKAGE_DIR, "package.json"); + const mainPkg = JSON.parse(fs.readFileSync(mainPkgPath, "utf-8")); + + mainPkg.optionalDependencies = { + "@microsoft/azureauth-darwin-arm64": AZURE_AUTH_VERSION, + "@microsoft/azureauth-darwin-x64": AZURE_AUTH_VERSION, + "@microsoft/azureauth-win32-x64": AZURE_AUTH_VERSION, + }; + + fs.writeFileSync(mainPkgPath, JSON.stringify(mainPkg, null, 2) + "\n"); + console.log(" Added optionalDependencies to package.json"); + + // Step 4: Publish main package + console.log("\n" + "=".repeat(60)); + console.log("Step 4: Publishing main package..."); + npmPublish(MAIN_PACKAGE_DIR, false); + + // Step 5: Cleanup (remove optionalDependencies for development) + console.log("\n" + "=".repeat(60)); + console.log("Step 5: Cleaning up..."); + delete mainPkg.optionalDependencies; + fs.writeFileSync(mainPkgPath, JSON.stringify(mainPkg, null, 2) + "\n"); + console.log( + " Removed optionalDependencies from package.json (for development)", + ); + + console.log("\n" + "=".repeat(60)); + console.log("Done!"); + if (DRY_RUN) { + console.log( + "\nThis was a dry run. Run without --dry-run to actually publish.", + ); + } +} + +main().catch((err) => { + console.error("\nError:", err.message); + process.exit(1); +}); diff --git a/yarn.lock b/yarn.lock index d0856fae..5830176b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -691,6 +691,24 @@ __metadata: languageName: node linkType: hard +"@microsoft/azureauth-darwin-arm64@workspace:packages/azureauth-darwin-arm64": + version: 0.0.0-use.local + resolution: "@microsoft/azureauth-darwin-arm64@workspace:packages/azureauth-darwin-arm64" + languageName: unknown + linkType: soft + +"@microsoft/azureauth-darwin-x64@workspace:packages/azureauth-darwin-x64": + version: 0.0.0-use.local + resolution: "@microsoft/azureauth-darwin-x64@workspace:packages/azureauth-darwin-x64" + languageName: unknown + linkType: soft + +"@microsoft/azureauth-win32-x64@workspace:packages/azureauth-win32-x64": + version: 0.0.0-use.local + resolution: "@microsoft/azureauth-win32-x64@workspace:packages/azureauth-win32-x64" + languageName: unknown + linkType: soft + "@microsoft/eslint-plugin-sdl@npm:^1.1.0": version: 1.1.0 resolution: "@microsoft/eslint-plugin-sdl@npm:1.1.0"