diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index e1d0cee..3ecc61b 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -29,13 +29,12 @@ on: jobs: test-service-account: - name: Service Account (${{ matrix.os }}, ${{ matrix.version }}, export-env=${{ matrix.export-env }}) + name: Service Account (${{ matrix.os }}, export-env=${{ matrix.export-env }}) runs-on: ${{ matrix.os }} strategy: fail-fast: true matrix: os: [ubuntu-latest, macos-latest, windows-latest] - version: [latest, 2.30.0] export-env: [true, false] steps: - name: Checkout @@ -86,7 +85,6 @@ jobs: id: load_secrets uses: ./ with: - version: ${{ matrix.version }} export-env: ${{ matrix.export-env }} env: SECRET: op://${{ secrets.VAULT }}/test-secret/password @@ -147,7 +145,6 @@ jobs: id: load_secrets_by_vault_id uses: ./ with: - version: ${{ matrix.version }} export-env: ${{ matrix.export-env }} env: SECRET: op://${{ secrets.VAULT_ID }}/test-secret/password @@ -174,13 +171,12 @@ jobs: run: ./tests/assert-env-set.sh test-connect: - name: Connect (ubuntu-latest, ${{ matrix.version }}, export-env=${{ matrix.export-env }}) + name: Connect (ubuntu-latest, export-env=${{ matrix.export-env }}) runs-on: ubuntu-latest strategy: fail-fast: true matrix: os: [ubuntu-latest, macos-latest, windows-latest] - version: [latest, 2.30.0] export-env: [true, false] steps: - name: Checkout @@ -237,7 +233,6 @@ jobs: id: load_secrets uses: ./ with: - version: ${{ matrix.version }} export-env: ${{ matrix.export-env }} env: SECRET: op://${{ secrets.VAULT }}/test-secret/password @@ -295,7 +290,6 @@ jobs: id: load_secrets_by_vault_id uses: ./ with: - version: ${{ matrix.version }} export-env: ${{ matrix.export-env }} env: SECRET: op://${{ secrets.VAULT_ID }}/test-secret/password diff --git a/README.md b/README.md index 6a0ac5f..eaf5156 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ When loading SSH keys, you can specify the format using the `ssh-format` query p SSH_PRIVATE_KEY: op://vault/item/private key?ssh-format=openssh ``` -For more details on secret reference syntax, see the [1Password CLI documentation](https://developer.1password.com/docs/cli/secret-reference-syntax/#ssh-format-parameter). +For more details on secret reference syntax, see the [1Password documentation](https://developer.1password.com/docs/cli/secret-reference-syntax/#ssh-format-parameter). ## 💙 Community & Support diff --git a/action.yml b/action.yml index fac55de..27418a9 100644 --- a/action.yml +++ b/action.yml @@ -11,9 +11,6 @@ inputs: export-env: description: Export the secrets as environment variables default: "false" - version: - description: Specify which 1Password CLI version to install. Defaults to "latest". - default: "latest" runs: using: "node20" main: "dist/index.js" diff --git a/package-lock.json b/package-lock.json index 2900279..3f091df 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,6 @@ "license": "MIT", "dependencies": { "@1password/connect": "^1.4.2", - "@1password/op-js": "^0.1.11", "@1password/sdk": "^0.4.0", "@actions/core": "^1.10.1", "@actions/exec": "^1.1.1", @@ -67,16 +66,6 @@ "typescript": "^5.0.0" } }, - "node_modules/@1password/op-js": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/@1password/op-js/-/op-js-0.1.13.tgz", - "integrity": "sha512-ZZBLxVqywFdvIbLv2xWw2N1ImSi183rRKf90vV19KRMReNyLwuD0dv6IrKrIdrJU33IuV3Gz85Z4K2a1PJTBDg==", - "license": "MIT", - "dependencies": { - "lookpath": "^1.2.2", - "semver": "^7.6.2" - } - }, "node_modules/@1password/prettier-config": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@1password/prettier-config/-/prettier-config-1.2.0.tgz", @@ -6161,18 +6150,6 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/lookpath": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/lookpath/-/lookpath-1.2.2.tgz", - "integrity": "sha512-k2Gmn8iV6qdME3ztZC2spubmQISimFOPLuQKiPaLcVdRz0IpdxrNClVepMlyTJlhodm/zG/VfbkWERm3kUIh+Q==", - "license": "MIT", - "bin": { - "lookpath": "bin/lookpath.js" - }, - "engines": { - "npm": ">=6.13.4" - } - }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -7149,6 +7126,7 @@ "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" diff --git a/package.json b/package.json index 5c9e775..77c340d 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,6 @@ }, "homepage": "https://github.com/1Password/load-secrets-action#readme", "dependencies": { - "@1password/op-js": "^0.1.11", "@1password/sdk": "^0.4.0", "@1password/connect": "^1.4.2", "@actions/core": "^1.10.1", diff --git a/src/index.ts b/src/index.ts index 712cadc..0543035 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,7 +14,7 @@ const loadSecretsAction = async () => { unsetPrevious(); } - // Validate that a proper authentication configuration is set for the CLI + // Validate that a proper authentication configuration is set (Connect or service account) validateAuth(); // Set environment variables from OP_ENV_FILE diff --git a/src/op-cli-installer/github-action/cli-installer/cli-installer.ts b/src/op-cli-installer/github-action/cli-installer/cli-installer.ts deleted file mode 100644 index 820e179..0000000 --- a/src/op-cli-installer/github-action/cli-installer/cli-installer.ts +++ /dev/null @@ -1,58 +0,0 @@ -import os from "os"; - -import * as core from "@actions/core"; -import * as tc from "@actions/tool-cache"; - -export type SupportedPlatform = Extract< - NodeJS.Platform, - "linux" | "darwin" | "win32" ->; - -// maps OS architecture names to 1Password CLI installer architecture names -export const archMap: Record = { - ia32: "386", - x64: "amd64", - arm: "arm", - arm64: "arm64", -}; - -// Builds the download URL for the 1Password CLI based on the platform and version. -export const cliUrlBuilder: Record< - SupportedPlatform, - (version: string, arch?: string) => string -> = { - linux: (version, arch) => - `https://cache.agilebits.com/dist/1P/op2/pkg/${version}/op_linux_${arch}_${version}.zip`, - darwin: (version) => - `https://cache.agilebits.com/dist/1P/op2/pkg/${version}/op_apple_universal_${version}.pkg`, - win32: (version, arch) => - `https://cache.agilebits.com/dist/1P/op2/pkg/${version}/op_windows_${arch}_${version}.zip`, -}; - -export class CliInstaller { - public readonly version: string; - public readonly arch: string; - - public constructor(version: string) { - this.version = version; - this.arch = this.getArch(); - } - - public async install(url: string): Promise { - console.info(`Downloading 1Password CLI from: ${url}`); - const downloadPath = await tc.downloadTool(url); - console.info("Installing 1Password CLI"); - const extractedPath = await tc.extractZip(downloadPath); - core.addPath(extractedPath); - core.info("1Password CLI installed"); - } - - private getArch(): string { - const arch = archMap[os.arch()]; - if (!arch) { - throw new Error("Unsupported architecture"); - } - - return arch; - } -} diff --git a/src/op-cli-installer/github-action/cli-installer/index.ts b/src/op-cli-installer/github-action/cli-installer/index.ts deleted file mode 100644 index 4b071e6..0000000 --- a/src/op-cli-installer/github-action/cli-installer/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { type Installer, newCliInstaller } from "./installer"; diff --git a/src/op-cli-installer/github-action/cli-installer/installer.test.ts b/src/op-cli-installer/github-action/cli-installer/installer.test.ts deleted file mode 100644 index f94b8f2..0000000 --- a/src/op-cli-installer/github-action/cli-installer/installer.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -import os from "os"; - -import { newCliInstaller } from "./installer"; -import { LinuxInstaller } from "./linux"; -import { MacOsInstaller } from "./macos"; -import { WindowsInstaller } from "./windows"; - -afterEach(() => { - jest.restoreAllMocks(); -}); - -describe("newCliInstaller", () => { - const version = "1.0.0"; - - afterEach(() => { - jest.resetAllMocks(); - }); - - it("should return LinuxInstaller for linux platform", () => { - jest.spyOn(os, "platform").mockReturnValue("linux"); - const installer = newCliInstaller(version); - expect(installer).toBeInstanceOf(LinuxInstaller); - }); - - it("should return MacOsInstaller for darwin platform", () => { - jest.spyOn(os, "platform").mockReturnValue("darwin"); - const installer = newCliInstaller(version); - expect(installer).toBeInstanceOf(MacOsInstaller); - }); - - it("should return WindowsInstaller for win32 platform", () => { - jest.spyOn(os, "platform").mockReturnValue("win32"); - const installer = newCliInstaller(version); - expect(installer).toBeInstanceOf(WindowsInstaller); - }); - - it("should throw error for unsupported platform", () => { - jest.spyOn(os, "platform").mockReturnValue("sunos"); - expect(() => newCliInstaller(version)).toThrow( - "Unsupported platform: sunos", - ); - }); -}); diff --git a/src/op-cli-installer/github-action/cli-installer/installer.ts b/src/op-cli-installer/github-action/cli-installer/installer.ts deleted file mode 100644 index 1e8f649..0000000 --- a/src/op-cli-installer/github-action/cli-installer/installer.ts +++ /dev/null @@ -1,23 +0,0 @@ -import os from "os"; - -import { LinuxInstaller } from "./linux"; -import { MacOsInstaller } from "./macos"; -import { WindowsInstaller } from "./windows"; - -export interface Installer { - installCli(): Promise; -} - -export const newCliInstaller = (version: string): Installer => { - const platform = os.platform(); - switch (platform) { - case "linux": - return new LinuxInstaller(version); - case "darwin": - return new MacOsInstaller(version); - case "win32": - return new WindowsInstaller(version); - default: - throw new Error(`Unsupported platform: ${platform}`); - } -}; diff --git a/src/op-cli-installer/github-action/cli-installer/linux.test.ts b/src/op-cli-installer/github-action/cli-installer/linux.test.ts deleted file mode 100644 index db8cd7a..0000000 --- a/src/op-cli-installer/github-action/cli-installer/linux.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -import os from "os"; - -import { - archMap, - CliInstaller, - cliUrlBuilder, - type SupportedPlatform, -} from "./cli-installer"; -import { LinuxInstaller } from "./linux"; - -afterEach(() => { - jest.restoreAllMocks(); -}); - -describe("LinuxInstaller", () => { - const version = "1.2.3"; - const arch: NodeJS.Architecture = "arm64"; - - it("should construct with given version and architecture", () => { - jest.spyOn(os, "arch").mockReturnValue(arch); - const installer = new LinuxInstaller(version); - expect(installer.version).toEqual(version); - expect(installer.arch).toEqual(archMap[arch]); - }); - - it("should call install with correct URL", async () => { - const installer = new LinuxInstaller(version); - const installMock = jest - .spyOn(CliInstaller.prototype, "install") - .mockResolvedValue(); - - await installer.installCli(); - - const builder = cliUrlBuilder["linux" as SupportedPlatform]; - const url = builder(version, installer.arch); - expect(installMock).toHaveBeenCalledWith(url); - }); -}); diff --git a/src/op-cli-installer/github-action/cli-installer/linux.ts b/src/op-cli-installer/github-action/cli-installer/linux.ts deleted file mode 100644 index 8d7c8ed..0000000 --- a/src/op-cli-installer/github-action/cli-installer/linux.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { - CliInstaller, - cliUrlBuilder, - type SupportedPlatform, -} from "./cli-installer"; -import type { Installer } from "./installer"; - -export class LinuxInstaller extends CliInstaller implements Installer { - private readonly platform: SupportedPlatform = "linux"; // Node.js platform identifier for Linux - - public constructor(version: string) { - super(version); - } - - public async installCli(): Promise { - const urlBuilder = cliUrlBuilder[this.platform]; - await super.install(urlBuilder(this.version, this.arch)); - } -} diff --git a/src/op-cli-installer/github-action/cli-installer/macos.test.ts b/src/op-cli-installer/github-action/cli-installer/macos.test.ts deleted file mode 100644 index ebbc64f..0000000 --- a/src/op-cli-installer/github-action/cli-installer/macos.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -import os from "os"; - -import { - archMap, - cliUrlBuilder, - type SupportedPlatform, -} from "./cli-installer"; -import { MacOsInstaller } from "./macos"; - -afterEach(() => { - jest.restoreAllMocks(); -}); - -describe("MacOsInstaller", () => { - const version = "1.2.3"; - const arch: NodeJS.Architecture = "x64"; - - it("should construct with given version and architecture", () => { - jest.spyOn(os, "arch").mockReturnValue(arch); - const installer = new MacOsInstaller(version); - expect(installer.version).toEqual(version); - expect(installer.arch).toEqual(archMap[arch]); - }); - - it("should call install with correct URL", async () => { - const installer = new MacOsInstaller(version); - const installMock = jest.spyOn(installer, "install").mockResolvedValue(); - - await installer.installCli(); - - const builder = cliUrlBuilder["darwin" as SupportedPlatform]; - const url = builder(version, installer.arch); - expect(installMock).toHaveBeenCalledWith(url); - }); -}); diff --git a/src/op-cli-installer/github-action/cli-installer/macos.ts b/src/op-cli-installer/github-action/cli-installer/macos.ts deleted file mode 100644 index f5cd7e5..0000000 --- a/src/op-cli-installer/github-action/cli-installer/macos.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { execFile } from "child_process"; -import * as fs from "fs"; -import * as path from "path"; -import { promisify } from "util"; - -import * as core from "@actions/core"; -import * as tc from "@actions/tool-cache"; - -import { - CliInstaller, - cliUrlBuilder, - type SupportedPlatform, -} from "./cli-installer"; -import { type Installer } from "./installer"; - -const execFileAsync = promisify(execFile); - -export class MacOsInstaller extends CliInstaller implements Installer { - private readonly platform: SupportedPlatform = "darwin"; // Node.js platform identifier for macOS - - public constructor(version: string) { - super(version); - } - - public async installCli(): Promise { - const urlBuilder = cliUrlBuilder[this.platform]; - await this.install(urlBuilder(this.version)); - } - - // @actions/tool-cache package does not support .pkg files, so we need to handle the installation manually - public override async install(downloadUrl: string): Promise { - console.info(`Downloading 1Password CLI from: ${downloadUrl}`); - const pkgPath = await tc.downloadTool(downloadUrl); - const pkgWithExtension = `${pkgPath}.pkg`; - fs.renameSync(pkgPath, pkgWithExtension); - - const expandDir = "temp-pkg"; - await execFileAsync("pkgutil", ["--expand", pkgWithExtension, expandDir]); - const payloadPath = path.join(expandDir, "op.pkg", "Payload"); - console.info("Installing 1Password CLI"); - const cliPath = await tc.extractTar(payloadPath); - core.addPath(cliPath); - - fs.rmSync(expandDir, { recursive: true, force: true }); - fs.rmSync(pkgPath, { force: true }); - - core.info("1Password CLI installed"); - } -} diff --git a/src/op-cli-installer/github-action/cli-installer/windows.test.ts b/src/op-cli-installer/github-action/cli-installer/windows.test.ts deleted file mode 100644 index 7b91d3d..0000000 --- a/src/op-cli-installer/github-action/cli-installer/windows.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -import os from "os"; - -import { - archMap, - CliInstaller, - cliUrlBuilder, - type SupportedPlatform, -} from "./cli-installer"; -import { WindowsInstaller } from "./windows"; - -afterEach(() => { - jest.restoreAllMocks(); -}); - -describe("WindowsInstaller", () => { - const version = "1.2.3"; - const arch: NodeJS.Architecture = "x64"; - - it("should construct with given version and architecture", () => { - jest.spyOn(os, "arch").mockReturnValue(arch); - const installer = new WindowsInstaller(version); - expect(installer.version).toEqual(version); - expect(installer.arch).toEqual(archMap[arch]); - }); - - it("should call install with correct URL", async () => { - const installer = new WindowsInstaller(version); - const installMock = jest - .spyOn(CliInstaller.prototype, "install") - .mockResolvedValue(); - - await installer.installCli(); - - const builder = cliUrlBuilder["win32" as SupportedPlatform]; - const url = builder(version, installer.arch); - expect(installMock).toHaveBeenCalledWith(url); - }); -}); diff --git a/src/op-cli-installer/github-action/cli-installer/windows.ts b/src/op-cli-installer/github-action/cli-installer/windows.ts deleted file mode 100644 index b1d37cb..0000000 --- a/src/op-cli-installer/github-action/cli-installer/windows.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { - CliInstaller, - cliUrlBuilder, - type SupportedPlatform, -} from "./cli-installer"; -import type { Installer } from "./installer"; - -export class WindowsInstaller extends CliInstaller implements Installer { - private readonly platform: SupportedPlatform = "win32"; // Node.js platform identifier for Windows - - public constructor(version: string) { - super(version); - } - - public async installCli(): Promise { - const urlBuilder = cliUrlBuilder[this.platform]; - await super.install(urlBuilder(this.version, this.arch)); - } -} diff --git a/src/op-cli-installer/github-action/index.ts b/src/op-cli-installer/github-action/index.ts deleted file mode 100644 index 85f242f..0000000 --- a/src/op-cli-installer/github-action/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -import * as core from "@actions/core"; - -import { ReleaseChannel, VersionResolver } from "../version"; - -import { newCliInstaller } from "./cli-installer"; - -// Installs the 1Password CLI on a GitHub Action runner. -export const installCliOnGithubActionRunner = async ( - version?: string, -): Promise => { - // Get the version from parameter, if not passed - from the job input. Defaults to latest if no version is provided - const providedVersion = - version || core.getInput("version") || ReleaseChannel.latest; - const versionResolver = new VersionResolver(providedVersion); - await versionResolver.resolve(); - const installer = newCliInstaller(versionResolver.get()); - await installer.installCli(); -}; diff --git a/src/op-cli-installer/index.test.ts b/src/op-cli-installer/index.test.ts deleted file mode 100644 index b2120d7..0000000 --- a/src/op-cli-installer/index.test.ts +++ /dev/null @@ -1,81 +0,0 @@ -import * as core from "@actions/core"; - -import { newCliInstaller } from "./github-action/cli-installer"; -import { - installCliOnGithubActionRunner, - ReleaseChannel, - VersionResolver, -} from "./index"; - -jest.mock("./github-action/cli-installer", () => ({ - newCliInstaller: jest.fn().mockImplementation((_resolved: string) => ({ - installCli: jest.fn(), - })), -})); - -beforeEach(() => { - jest.restoreAllMocks(); -}); - -describe("installCliOnGithubActionRunner", () => { - it("should defaults to `latest` when nothing is passed", async () => { - jest.spyOn(core, "getInput").mockReturnValue(""); - jest.spyOn(VersionResolver.prototype, "resolve").mockResolvedValue(); - jest - .spyOn(VersionResolver.prototype, "get") - .mockReturnValue(ReleaseChannel.latest); - - await installCliOnGithubActionRunner(); - - expect(newCliInstaller).toHaveBeenCalledWith(ReleaseChannel.latest); - }); - - it("should defaults to `latest` when undefined is passed", async () => { - jest.spyOn(core, "getInput").mockReturnValue(""); - jest.spyOn(VersionResolver.prototype, "resolve").mockResolvedValue(); - jest - .spyOn(VersionResolver.prototype, "get") - .mockReturnValue(ReleaseChannel.latest); - - await installCliOnGithubActionRunner(undefined); - - expect(newCliInstaller).toHaveBeenCalledWith(ReleaseChannel.latest); - }); - - it("should set provided explicit version", async () => { - const providedVersion = "1.2.3"; - jest.spyOn(core, "getInput").mockReturnValue(""); - jest.spyOn(VersionResolver.prototype, "resolve").mockResolvedValue(); - jest - .spyOn(VersionResolver.prototype, "get") - .mockReturnValue(providedVersion); - - await installCliOnGithubActionRunner(providedVersion); - - expect(newCliInstaller).toHaveBeenCalledWith(providedVersion); - }); - - it("should set version provided as job input", async () => { - const providedVersion = "3.0.0"; - jest.spyOn(core, "getInput").mockReturnValue(providedVersion); - jest.spyOn(VersionResolver.prototype, "resolve").mockResolvedValue(); - jest - .spyOn(VersionResolver.prototype, "get") - .mockReturnValue(providedVersion); - - await installCliOnGithubActionRunner(); - - expect(newCliInstaller).toHaveBeenCalledWith(providedVersion); - }); - - it("should throw error for invalid version", async () => { - const providedVersion = "invalid"; - jest.spyOn(core, "getInput").mockReturnValue(providedVersion); - jest.spyOn(VersionResolver.prototype, "resolve").mockResolvedValue(); - jest - .spyOn(VersionResolver.prototype, "get") - .mockReturnValue(providedVersion); - - await expect(installCliOnGithubActionRunner()).rejects.toThrow(); - }); -}); diff --git a/src/op-cli-installer/index.ts b/src/op-cli-installer/index.ts deleted file mode 100644 index 852f623..0000000 --- a/src/op-cli-installer/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { installCliOnGithubActionRunner } from "./github-action"; -export { ReleaseChannel, VersionResolver } from "./version"; diff --git a/src/op-cli-installer/version/constants.ts b/src/op-cli-installer/version/constants.ts deleted file mode 100644 index cf59f5d..0000000 --- a/src/op-cli-installer/version/constants.ts +++ /dev/null @@ -1,13 +0,0 @@ -export enum ReleaseChannel { - latest = "latest", - latestBeta = "latest-beta", -} - -export interface VersionResponse { - // eslint disabled next line as CLI2 is expected in getting CLI versions response - /* eslint-disable-next-line @typescript-eslint/naming-convention */ - CLI2: { - release: { version: string }; - beta: { version: string }; - }; -} diff --git a/src/op-cli-installer/version/helper.test.ts b/src/op-cli-installer/version/helper.test.ts deleted file mode 100644 index c6f0185..0000000 --- a/src/op-cli-installer/version/helper.test.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { ReleaseChannel } from "./constants"; -import { getLatestVersion } from "./helper"; - -describe("getLatestVersion", () => { - beforeEach(() => { - jest.restoreAllMocks(); - }); - - it("should return latest stable version", async () => { - const mockResponse = { - // eslint-disable-next-line @typescript-eslint/naming-convention - CLI2: { - release: { version: "2.31.0" }, - beta: { version: "2.32.0-beta.01" }, - }, - }; - - jest.spyOn(global, "fetch").mockResolvedValueOnce({ - // eslint-disable-next-line @typescript-eslint/require-await - json: async () => mockResponse, - } as Response); - - const version = await getLatestVersion(ReleaseChannel.latest); - expect(version).toBe("2.31.0"); - }); - - it("should return latest beta version", async () => { - const mockResponse = { - // eslint-disable-next-line @typescript-eslint/naming-convention - CLI2: { - release: { version: "2.31.0" }, - beta: { version: "2.32.0-beta.01" }, - }, - }; - - jest.spyOn(global, "fetch").mockResolvedValueOnce({ - // eslint-disable-next-line @typescript-eslint/require-await - json: async () => mockResponse, - } as Response); - - const version = await getLatestVersion(ReleaseChannel.latestBeta); - expect(version).toBe("2.32.0-beta.01"); - }); - - it("should throw if no CLI2 field", async () => { - jest.spyOn(global, "fetch").mockResolvedValueOnce({ - // eslint-disable-next-line @typescript-eslint/require-await - json: async () => ({}), - } as Response); - - await expect(getLatestVersion(ReleaseChannel.latest)).rejects.toThrow( - `No ${ReleaseChannel.latest} versions found`, - ); - }); - - it("should throw if no stable version found", async () => { - const mockResponse = { - // eslint-disable-next-line @typescript-eslint/naming-convention - CLI2: { - beta: { version: "2.32.0-beta.01" }, - }, - }; - - jest.spyOn(global, "fetch").mockResolvedValueOnce({ - // eslint-disable-next-line @typescript-eslint/require-await - json: async () => mockResponse, - } as Response); - - await expect(getLatestVersion(ReleaseChannel.latest)).rejects.toThrow( - `No ${ReleaseChannel.latest} versions found`, - ); - }); - - it("should throw if no beta version found", async () => { - const mockResponse = { - // eslint-disable-next-line @typescript-eslint/naming-convention - CLI2: { - release: { version: "2.32.0" }, - }, - }; - - jest.spyOn(global, "fetch").mockResolvedValueOnce({ - // eslint-disable-next-line @typescript-eslint/require-await - json: async () => mockResponse, - } as Response); - - await expect(getLatestVersion(ReleaseChannel.latestBeta)).rejects.toThrow( - `No ${ReleaseChannel.latestBeta} versions found`, - ); - }); -}); diff --git a/src/op-cli-installer/version/helper.ts b/src/op-cli-installer/version/helper.ts deleted file mode 100644 index 1d246dc..0000000 --- a/src/op-cli-installer/version/helper.ts +++ /dev/null @@ -1,23 +0,0 @@ -import * as core from "@actions/core"; - -import { ReleaseChannel, type VersionResponse } from "./constants"; - -// Returns the latest version of the 1Password CLI based on the specified channel. -export const getLatestVersion = async ( - channel: ReleaseChannel, -): Promise => { - core.info(`Getting ${channel} version number`); - const res = await fetch("https://app-updates.agilebits.com/latest"); - const json = (await res.json()) as VersionResponse; - const latestStable = json?.CLI2?.release?.version; - const latestBeta = json?.CLI2?.beta?.version; - const version = - channel === ReleaseChannel.latestBeta ? latestBeta : latestStable; - - if (!version) { - core.error(`No ${channel} versions found`); - throw new Error(`No ${channel} versions found`); - } - - return version; -}; diff --git a/src/op-cli-installer/version/index.ts b/src/op-cli-installer/version/index.ts deleted file mode 100644 index 387cf2a..0000000 --- a/src/op-cli-installer/version/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { VersionResolver } from "./version-resolver"; -export { ReleaseChannel } from "./constants"; diff --git a/src/op-cli-installer/version/validate.test.ts b/src/op-cli-installer/version/validate.test.ts deleted file mode 100644 index ff4a201..0000000 --- a/src/op-cli-installer/version/validate.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { describe, expect, it } from "@jest/globals"; - -import { validateVersion } from "./validate"; - -describe("validateVersion", () => { - it('should not throw for "latest"', () => { - expect(() => validateVersion("latest")).not.toThrow(); - }); - - it('should not throw for "latest-beta"', () => { - expect(() => validateVersion("latest-beta")).not.toThrow(); - }); - - it('should not throw for valid semver version "2.18.0"', () => { - expect(() => validateVersion("2.18.0")).not.toThrow(); - }); - - it('should throw for partial version "2"', () => { - expect(() => validateVersion("2")).toThrow(); - }); - - it('should throw for partial version "2.1"', () => { - expect(() => validateVersion("2.1")).toThrow(); - }); - - it('should not throw for valid beta "2.19.0-beta.01"', () => { - expect(() => validateVersion("2.19.0-beta.01")).not.toThrow(); - }); - - it('should not throw for valid beta "2.19.3-beta.12"', () => { - expect(() => validateVersion("2.19.3-beta.12")).not.toThrow(); - }); - - it('should not throw for coerced version "v2.19.0"', () => { - expect(() => validateVersion("v2.19.0")).not.toThrow(); - }); - - it('should throw for invalid version "latest-abc"', () => { - expect(() => validateVersion("latest-abc")).toThrow(); - }); - - it("should throw for empty string", () => { - expect(() => validateVersion("")).toThrow(); - }); -}); diff --git a/src/op-cli-installer/version/validate.ts b/src/op-cli-installer/version/validate.ts deleted file mode 100644 index 39967dd..0000000 --- a/src/op-cli-installer/version/validate.ts +++ /dev/null @@ -1,23 +0,0 @@ -import semver from "semver"; - -import { ReleaseChannel } from "./constants"; - -// Validates if the provided version type is a valid enum value or a valid semver version. -export const validateVersion = (input: string): void => { - if (Object.values(ReleaseChannel).includes(input as ReleaseChannel)) { - return; - } - - // 1Password beta releases (aka 2.19.0-beta.01) are not semver compliant. - // According to semver, it should be "2.19.0-beta.1". - // That's why we need to normalize them before validating. - // Accepts valid semver versions like "2.18.0" or beta-releases like "2.19.0-beta.01" - // or versions with 'v' prefix like "v2.19.0" - const normalized = input.replace(/-beta\.0*(\d+)/, "-beta.$1"); - const normInput = new semver.SemVer(normalized); - if (semver.valid(normInput)) { - return; - } - - throw new Error(`Invalid version input: ${input}`); -}; diff --git a/src/op-cli-installer/version/version-resolver.test.ts b/src/op-cli-installer/version/version-resolver.test.ts deleted file mode 100644 index 5166607..0000000 --- a/src/op-cli-installer/version/version-resolver.test.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { expect } from "@jest/globals"; - -import { ReleaseChannel } from "./constants"; -import { VersionResolver } from "./version-resolver"; - -describe("VersionResolver", () => { - test("should throw error when invalid version provided", () => { - expect(() => new VersionResolver("vv")).toThrow(); - }); - - test("should throw error when version is empty", () => { - expect(() => new VersionResolver("")).toThrow(); - }); - - test("should throw error for major version only", () => { - expect(() => new VersionResolver("1")).toThrow(); - }); - - test("should throw error for major and minor version only", () => { - expect(() => new VersionResolver("1.0")).toThrow(); - }); - - test("should resolve latest stable version", async () => { - const versionResolver = new VersionResolver(ReleaseChannel.latest); - await versionResolver.resolve(); - expect(versionResolver.get()).toBeDefined(); - }); - - test("should resolve latest beta version", async () => { - const versionResolver = new VersionResolver(ReleaseChannel.latestBeta); - await versionResolver.resolve(); - expect(versionResolver.get()).toBeDefined(); - }); - - test("should resolve version without 'v' prefix", async () => { - const versionResolver = new VersionResolver("1.0.0"); - await versionResolver.resolve(); - expect(versionResolver.get()).toBe("v1.0.0"); - }); - - test("should resolve version with 'v' prefix", async () => { - const versionResolver = new VersionResolver("v1.0.0"); - await versionResolver.resolve(); - expect(versionResolver.get()).toBe("v1.0.0"); - }); - - test("should resolve beta version without 'v' prefix", async () => { - const versionResolver = new VersionResolver("2.19.0-beta.01"); - await versionResolver.resolve(); - expect(versionResolver.get()).toBe("v2.19.0-beta.01"); - }); - - test("should resolve beta version with 'v' prefix", async () => { - const versionResolver = new VersionResolver("v2.19.0-beta.01"); - await versionResolver.resolve(); - expect(versionResolver.get()).toBe("v2.19.0-beta.01"); - }); -}); diff --git a/src/op-cli-installer/version/version-resolver.ts b/src/op-cli-installer/version/version-resolver.ts deleted file mode 100644 index d74ba4f..0000000 --- a/src/op-cli-installer/version/version-resolver.ts +++ /dev/null @@ -1,45 +0,0 @@ -import * as core from "@actions/core"; - -import { ReleaseChannel } from "./constants"; -import { getLatestVersion } from "./helper"; -import { validateVersion } from "./validate"; - -export class VersionResolver { - private version: string; - - public constructor(version: string) { - this.validate(version); - this.version = version; - } - - public get(): string { - return this.version; - } - - public async resolve(): Promise { - core.info(`Resolving version: ${this.version}`); - if (!this.version) { - core.error("Version is not provided"); - throw new Error("Version is not provided"); - } - - if (this.isReleaseChannel(this.version)) { - this.version = await getLatestVersion(this.version); - } - - // add `v` prefix if not already present - this.version = this.version.startsWith("v") - ? this.version - : `v${this.version}`; - } - - private validate(version: string) { - core.info(`Validating version number: '${version}'`); - validateVersion(version); - core.info(`Version number '${version}' is valid`); - } - - private isReleaseChannel(value: string): value is ReleaseChannel { - return Object.values(ReleaseChannel).includes(value as ReleaseChannel); - } -} diff --git a/src/utils.test.ts b/src/utils.test.ts index 85966de..51005f1 100644 --- a/src/utils.test.ts +++ b/src/utils.test.ts @@ -1,10 +1,8 @@ import * as core from "@actions/core"; import * as exec from "@actions/exec"; -import { read } from "@1password/op-js"; import { createClient, Secrets } from "@1password/sdk"; import { OnePasswordConnect, FullItem } from "@1password/connect"; import { - extractSecret, loadSecrets, unsetPrevious, validateAuth, @@ -27,7 +25,6 @@ jest.mock("@actions/exec", () => ({ stdout: "MOCK_SECRET", })), })); -jest.mock("@1password/op-js"); jest.mock("@1password/sdk", () => ({ createClient: jest.fn(), // eslint-disable-next-line @typescript-eslint/naming-convention @@ -86,77 +83,6 @@ describe("validateAuth", () => { }); }); -describe("extractSecret", () => { - const envTestSecretEnv = "TEST_SECRET"; - const testSecretRef = "op://vault/item/secret"; - const testSecretValue = "Secret1@3$"; - - read.parse = jest.fn().mockReturnValue(testSecretValue); - - process.env[envTestSecretEnv] = testSecretRef; - - it("should set secret as step output", () => { - extractSecret(envTestSecretEnv, false); - expect(core.exportVariable).not.toHaveBeenCalledWith( - envTestSecretEnv, - testSecretValue, - ); - expect(core.setOutput).toHaveBeenCalledWith( - envTestSecretEnv, - testSecretValue, - ); - expect(core.setSecret).toHaveBeenCalledWith(testSecretValue); - }); - - it("should set secret as environment variable", () => { - extractSecret(envTestSecretEnv, true); - expect(core.exportVariable).toHaveBeenCalledWith( - envTestSecretEnv, - testSecretValue, - ); - expect(core.setOutput).not.toHaveBeenCalledWith( - envTestSecretEnv, - testSecretValue, - ); - expect(core.setSecret).toHaveBeenCalledWith(testSecretValue); - }); - - describe("when secret value is empty string", () => { - const emptySecretValue = ""; - - beforeEach(() => { - (read.parse as jest.Mock).mockReturnValue(emptySecretValue); - }); - - afterEach(() => { - (read.parse as jest.Mock).mockReturnValue(testSecretValue); - }); - - it("should set empty string as step output", () => { - extractSecret(envTestSecretEnv, false); - expect(core.setOutput).toHaveBeenCalledWith( - envTestSecretEnv, - emptySecretValue, - ); - expect(core.exportVariable).not.toHaveBeenCalled(); - }); - - it("should set empty string as environment variable", () => { - extractSecret(envTestSecretEnv, true); - expect(core.exportVariable).toHaveBeenCalledWith( - envTestSecretEnv, - emptySecretValue, - ); - expect(core.setOutput).not.toHaveBeenCalled(); - }); - - it("should not call setSecret for empty string", () => { - extractSecret(envTestSecretEnv, false); - expect(core.setSecret).not.toHaveBeenCalled(); - }); - }); -}); - describe("loadSecrets when using Connect", () => { beforeEach(() => { process.env[envConnectHost] = "https://connect.example"; @@ -413,11 +339,6 @@ describe("loadSecrets when using Service Account", () => { mockResolve.mockResolvedValue("resolved-secret-value"); }); - it("does not call op env ls when using Service Account", async () => { - await loadSecrets(false); - expect(exec.getExecOutput).not.toHaveBeenCalled(); - }); - it("sets step output with resolved value when export-env is false", async () => { await loadSecrets(false); expect(core.setOutput).toHaveBeenCalledTimes(1); diff --git a/src/utils.ts b/src/utils.ts index 2b6f0f1..e2a3a1f 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,5 +1,4 @@ import * as core from "@actions/core"; -import { read } from "@1password/op-js"; import { createClient, Secrets } from "@1password/sdk"; import { OnePasswordConnect, FullItem, OPConnect } from "@1password/connect"; import { version } from "../package.json"; @@ -320,23 +319,6 @@ export const validateAuth = (): void => { core.info(`Authenticated with ${authType}.`); }; -export const extractSecret = ( - envName: string, - shouldExportEnv: boolean, -): void => { - const ref = process.env[envName]; - if (!ref) { - return; - } - - const secretValue = read.parse(ref); - if (secretValue === null || secretValue === undefined) { - return; - } - - setResolvedSecret(envName, secretValue, shouldExportEnv); -}; - export const unsetPrevious = (): void => { if (process.env[envManagedVariables]) { core.info("Unsetting previous values ...");