diff --git a/actions-install/action.yml b/actions-install/action.yml index 15669711..23ca2d69 100644 --- a/actions-install/action.yml +++ b/actions-install/action.yml @@ -24,6 +24,10 @@ inputs: description: 'Path to a pac.exe (windows) or pac (linux/mac) already installed on the action runner.' required: false + add-tools-to-path: + description: 'Enables you to use pac cli from script tasks without needing to set up the path manually.' + required: false + runs: using: 'node16' main: '../dist/actions/actions-install/index.js' diff --git a/src/actions/actions-install/index.ts b/src/actions/actions-install/index.ts index 80fbb9b7..1d12421c 100644 --- a/src/actions/actions-install/index.ts +++ b/src/actions/actions-install/index.ts @@ -16,7 +16,17 @@ const argName = { nugetFeedOverride: 'nuget-feed-override', nugetFeedUsername: 'nuget-feed-username', nugetFeedPassword: 'nuget-feed-password', - localPacPath: 'use-preinstalled-pac' + localPacPath: 'use-preinstalled-pac', + addToolsToPath: 'add-tools-to-path' +}; + +let args: { + nugetFeedPassword: string; + localPacPath: string; + nugetFeedOverride: string; + versionOverride: string; + nugetFeedUsername: string; + addToolsToPath: boolean; }; class MutuallyExclusiveArgsError extends Error { @@ -38,11 +48,12 @@ class MutuallyExclusiveArgsError extends Error { export async function main(): Promise { core.startGroup('actions-install:'); - const args = { + args = { versionOverride: core.getInput(argName.versionOverride, { required: false }), nugetFeedOverride: core.getInput(argName.nugetFeedOverride, { required: false }), nugetFeedUsername: core.getInput(argName.nugetFeedUsername, { required: false }), nugetFeedPassword: core.getInput(argName.nugetFeedPassword, { required: false }), + addToolsToPath: core.getInput(argName.addToolsToPath, { required: false }) === 'true', localPacPath: core.getInput(argName.localPacPath, { required: false }) }; @@ -83,6 +94,10 @@ export async function main(): Promise { core.endGroup(); } +function removePacFromPath(path:string): string { + return path.replace(/(\/|\\)(pac.exe|pac)$/, ''); +} + async function usingPreinstalledPac(localPacPath: string): Promise { const absolutePath = path.resolve(localPacPath); core.info(`Using preinstalled pac from ${absolutePath}`) @@ -100,6 +115,9 @@ async function usingPreinstalledPac(localPacPath: string): Promise { core.exportVariable(PacInstalledEnvVarName, 'true'); core.exportVariable(PacPathEnvVarName, absolutePath); + if (args.addToolsToPath) { + core.addPath(removePacFromPath(absolutePath)); + } core.warning(`Actions built targetting PAC ${PacInfo.PacPackageVersion}, so Action and PAC parameters might not match if preinstalled pac is a different version.`); } @@ -125,9 +143,13 @@ async function nugetInstall(packageName: string, packageVersion: string, nugetFe await exec.getExecOutput('nuget', installArgs); - const pacPath = resolve(toolpath, packageName + '.' + packageVersion, 'tools', 'pac.exe'); + const pacPathWithoutExecutable = resolve(toolpath, packageName + '.' + packageVersion, 'tools'); + const pacPath = resolve(pacPathWithoutExecutable, 'pac.exe'); core.exportVariable(PacInstalledEnvVarName, 'true'); core.exportVariable(PacPathEnvVarName, pacPath); + if (args.addToolsToPath) { + core.addPath(pacPathWithoutExecutable); + } } finally { if (nugetConfigFile) { await fs.rm(nugetConfigFile); @@ -153,7 +175,11 @@ async function dotnetInstall(packageName: string, packageVersion: string, nugetF await exec.getExecOutput('dotnet', installArgs); core.exportVariable(PacInstalledEnvVarName, 'true'); - core.exportVariable(PacPathEnvVarName, path.join(toolpath, os.platform() === 'win32' ? 'pac.exe' : 'pac')); + const pacPath = path.join(toolpath, os.platform() === 'win32' ? 'pac.exe' : 'pac'); + core.exportVariable(PacPathEnvVarName, pacPath); + if (args.addToolsToPath) { + core.addPath(removePacFromPath(toolpath)); + } core.info(`pac installed to ${process.env[PacPathEnvVarName]}`); } finally { if (nugetConfigFile) { @@ -185,7 +211,7 @@ async function createNugetConfigViaDotnet(toolDirectory: string, nugetFeedOverri await exec.getExecOutput('dotnet', ['new', 'nugetconfig', '-o', toolDirectory]); await exec.getExecOutput('dotnet', ['nuget', 'remove', 'source', 'nuget', '--configfile', filename]); - const nugetSourceArgs = ['nuget', 'add', 'source', nugetFeedOverride, '--name', 'pacNugetFeed', '--configfile', filename ]; + const nugetSourceArgs = ['nuget', 'add', 'source', nugetFeedOverride, '--name', 'pacNugetFeed', '--configfile', filename]; nugetFeedUsername && nugetSourceArgs.push('--username', nugetFeedUsername); nugetFeedPassword && nugetSourceArgs.push('--password', nugetFeedPassword); diff --git a/src/test/actionsInstall.test.ts b/src/test/actionsInstall.test.ts new file mode 100644 index 00000000..89c4813e --- /dev/null +++ b/src/test/actionsInstall.test.ts @@ -0,0 +1,115 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +import { assert, should, use } from "chai"; +import * as sinonChai from "sinon-chai"; +import rewiremock from "./rewiremock"; +import { restore, stub } from "sinon"; +import { runnerParameters } from "../lib/runnerParameters"; +import Sinon = require("sinon"); +import * as os from 'os'; + +should(); +use(sinonChai); + +describe("actions-install tests", () => { + let pathValue: string; + const addToolsToPath = 'add-tools-to-path'; + const usePreinstalledPac = 'use-preinstalled-pac'; + let inputs: { [key: string]: string }; + const actionsInstallStub: Sinon.SinonStub = stub(); + let osPlatformStub: Sinon.SinonStub = stub(); + + beforeEach(() => { + pathValue = ''; + inputs = {}; + osPlatformStub = Sinon.stub(os, 'platform'); + }); + afterEach(() => { + osPlatformStub.restore(); + restore(); + }); + + async function callActionWithMocks(): Promise { + const toolInstaller = await rewiremock.around( + () => import("../actions/actions-install/index"), + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (mock: any) => { + mock(() => import("@microsoft/powerplatform-cli-wrapper/dist/actions")).with({ + actionsInstall: actionsInstallStub + }); + mock(() => import("@actions/core")).with({ + getInput: (name: string) => inputs[name], + startGroup: () => undefined, + endGroup: () => undefined, + info: () => undefined, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + exportVariable: (name: string, val: string) => undefined, + addPath: (path: string) => pathValue = path, + warning: () => undefined + }); + mock(() => import("@actions/io")).with({ + which: () => Promise.resolve("path/to/pac") + }); + mock(() => import("@actions/exec")).with({ + getExecOutput: () => undefined + }); + mock(() => import("../lib/runnerParameters")).with({ + runnerParameters: runnerParameters + }); + }); + await toolInstaller.main(); + } + + function setupTest(platform: string, addTools: string, preinstalledPac?: string) { + osPlatformStub.returns(platform); + inputs[addToolsToPath] = addTools; + if (preinstalledPac) { + inputs[usePreinstalledPac] = preinstalledPac; + } + } + + function assertPathNotEmpty() { + assert.isNotEmpty(pathValue); + assert.isTrue(!pathValue.endsWith('pac') && !pathValue.endsWith('pac.exe')); + } + + function assertPathEmpty() { + assert.isEmpty(pathValue); + } + + it("calls actions-install with add-tools-to-path true and calls addPath", async function () { + setupTest('win32', 'true'); + await callActionWithMocks(); + assertPathNotEmpty(); + }); + + it("calls actions-install with add-tools-to-path false and does not call addPath", async function () { + setupTest('win32', 'false'); + await callActionWithMocks(); + assertPathEmpty(); + }); + + it("calls actions-install with add-tools-to-path true + use-preinstalled-pac value and calls addPath", async function () { + setupTest('win32', 'true', 'out/pac/tools/pac.exe'); + await callActionWithMocks(); + assertPathNotEmpty(); + }); + + it("calls actions-install with add-tools-to-path false + use-preinstalled-pac value and does not call addPath", async function () { + setupTest('win32', 'false', 'out/pac/tools/pac.exe'); + await callActionWithMocks(); + assertPathEmpty(); + }); + + it("calls actions-install using dotnetInstall with add-tools-to-path true and calls addPath", async function () { + setupTest('linux', 'true'); + await callActionWithMocks(); + assertPathNotEmpty(); + }); + + it("calls actions-install using dotnetInstall with add-tools-to-path false and does nots addPath", async function () { + setupTest('linux', 'false'); + await callActionWithMocks(); + assertPathEmpty(); + }); +});