Compare commits
28 Commits
v3.2.0
...
jill/depre
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be2bd38fd6 | ||
|
|
9bdf6cdcfc | ||
|
|
85fc7ef953 | ||
|
|
5523b3fd67 | ||
|
|
44ef890925 | ||
|
|
d7959a3396 | ||
|
|
ba2e69a32e | ||
|
|
e5946f890f | ||
|
|
6afd0621a7 | ||
|
|
72c110fb96 | ||
|
|
b590204659 | ||
|
|
6490b7af0e | ||
|
|
676313036e | ||
|
|
43c1f24739 | ||
|
|
3c643fe809 | ||
|
|
41f600a118 | ||
|
|
a03b151beb | ||
|
|
9ef8ce29fa | ||
|
|
cc33b584d1 | ||
|
|
21385b0c31 | ||
|
|
2a0e01171e | ||
|
|
8f91e40957 | ||
|
|
d4fc305bfa | ||
|
|
cb3e4f29eb | ||
|
|
f4ee2a9d76 | ||
|
|
59b7671409 | ||
|
|
d7da1c3ae2 | ||
|
|
ffffc2db51 |
123
.github/workflows/e2e-tests.yml
vendored
123
.github/workflows/e2e-tests.yml
vendored
@@ -21,7 +21,10 @@ on:
|
|||||||
OP_SERVICE_ACCOUNT_TOKEN:
|
OP_SERVICE_ACCOUNT_TOKEN:
|
||||||
required: true
|
required: true
|
||||||
VAULT:
|
VAULT:
|
||||||
description: "1Password vault name or UUID"
|
description: "1Password vault name"
|
||||||
|
required: true
|
||||||
|
VAULT_ID:
|
||||||
|
description: "1Password vault UUID"
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -55,9 +58,23 @@ jobs:
|
|||||||
- name: Generate .env.tpl
|
- name: Generate .env.tpl
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
|
mkdir -p tests
|
||||||
echo "FILE_SECRET=op://${{ secrets.VAULT }}/test-secret/password" > tests/.env.tpl
|
echo "FILE_SECRET=op://${{ secrets.VAULT }}/test-secret/password" > tests/.env.tpl
|
||||||
echo "FILE_SECRET_IN_SECTION=op://${{ secrets.VAULT }}/test-secret/test-section/password" >> tests/.env.tpl
|
echo "FILE_SECRET_IN_SECTION=op://${{ secrets.VAULT }}/test-secret/test-section/password" >> tests/.env.tpl
|
||||||
echo "FILE_MULTILINE_SECRET=op://${{ secrets.VAULT }}/multiline-secret/notesPlain" >> tests/.env.tpl
|
echo "FILE_MULTILINE_SECRET=op://${{ secrets.VAULT }}/multiline-secret/notesPlain" >> tests/.env.tpl
|
||||||
|
echo "SECRET_WITH_FILE=op://${{ secrets.VAULT }}/file-secret/test.txt" >> tests/.env.tpl
|
||||||
|
echo "SECRET_WITH_FILE_IN_SECTION=op://${{ secrets.VAULT }}/file-secret/file section/test.txt" >> tests/.env.tpl
|
||||||
|
echo "DOUBLE_SECTION_SECRET=op://${{ secrets.VAULT }}/double-section-secret/test-section/password" >> tests/.env.tpl
|
||||||
|
|
||||||
|
- name: Generate .vaultId_env.tpl
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "FILE_SECRET=op://${{ secrets.VAULT_ID }}/test-secret/password" > tests/.vaultId_env.tpl
|
||||||
|
echo "FILE_SECRET_IN_SECTION=op://${{ secrets.VAULT_ID }}/test-secret/test-section/password" >> tests/.vaultId_env.tpl
|
||||||
|
echo "FILE_MULTILINE_SECRET=op://${{ secrets.VAULT_ID }}/multiline-secret/notesPlain" >> tests/.vaultId_env.tpl
|
||||||
|
echo "SECRET_WITH_FILE=op://${{ secrets.VAULT_ID }}/file-secret/test.txt" >> tests/.vaultId_env.tpl
|
||||||
|
echo "SECRET_WITH_FILE_IN_SECTION=op://${{ secrets.VAULT_ID }}/file-secret/file section/test.txt" >> tests/.vaultId_env.tpl
|
||||||
|
echo "DOUBLE_SECTION_SECRET=op://${{ secrets.VAULT_ID }}/double-section-secret/test-section/password" >> tests/.vaultId_env.tpl
|
||||||
|
|
||||||
- name: Configure Service account
|
- name: Configure Service account
|
||||||
uses: ./configure
|
uses: ./configure
|
||||||
@@ -73,6 +90,9 @@ jobs:
|
|||||||
SECRET: op://${{ secrets.VAULT }}/test-secret/password
|
SECRET: op://${{ secrets.VAULT }}/test-secret/password
|
||||||
SECRET_IN_SECTION: op://${{ secrets.VAULT }}/test-secret/test-section/password
|
SECRET_IN_SECTION: op://${{ secrets.VAULT }}/test-secret/test-section/password
|
||||||
MULTILINE_SECRET: op://${{ secrets.VAULT }}/multiline-secret/notesPlain
|
MULTILINE_SECRET: op://${{ secrets.VAULT }}/multiline-secret/notesPlain
|
||||||
|
SECRET_WITH_FILE: op://${{ secrets.VAULT }}/file-secret/test.txt
|
||||||
|
SECRET_WITH_FILE_IN_SECTION: op://${{ secrets.VAULT }}/file-secret/file section/test.txt
|
||||||
|
DOUBLE_SECTION_SECRET: op://${{ secrets.VAULT }}/double-section-secret/test-section/password
|
||||||
OP_ENV_FILE: ./tests/.env.tpl
|
OP_ENV_FILE: ./tests/.env.tpl
|
||||||
|
|
||||||
- name: Assert test secret values [step output]
|
- name: Assert test secret values [step output]
|
||||||
@@ -85,6 +105,9 @@ jobs:
|
|||||||
FILE_SECRET: ${{ steps.load_secrets.outputs.FILE_SECRET }}
|
FILE_SECRET: ${{ steps.load_secrets.outputs.FILE_SECRET }}
|
||||||
FILE_SECRET_IN_SECTION: ${{ steps.load_secrets.outputs.FILE_SECRET_IN_SECTION }}
|
FILE_SECRET_IN_SECTION: ${{ steps.load_secrets.outputs.FILE_SECRET_IN_SECTION }}
|
||||||
FILE_MULTILINE_SECRET: ${{ steps.load_secrets.outputs.FILE_MULTILINE_SECRET }}
|
FILE_MULTILINE_SECRET: ${{ steps.load_secrets.outputs.FILE_MULTILINE_SECRET }}
|
||||||
|
SECRET_WITH_FILE: ${{ steps.load_secrets.outputs.SECRET_WITH_FILE }}
|
||||||
|
SECRET_WITH_FILE_IN_SECTION: ${{ steps.load_secrets.outputs.SECRET_WITH_FILE_IN_SECTION }}
|
||||||
|
DOUBLE_SECTION_SECRET: ${{ steps.load_secrets.outputs.DOUBLE_SECTION_SECRET }}
|
||||||
run: ./tests/assert-env-set.sh
|
run: ./tests/assert-env-set.sh
|
||||||
|
|
||||||
- name: Assert test secret values [exported env]
|
- name: Assert test secret values [exported env]
|
||||||
@@ -109,7 +132,6 @@ jobs:
|
|||||||
uses: ./
|
uses: ./
|
||||||
env:
|
env:
|
||||||
BAD_REF: "op://x"
|
BAD_REF: "op://x"
|
||||||
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
|
|
||||||
with:
|
with:
|
||||||
export-env: true
|
export-env: true
|
||||||
|
|
||||||
@@ -119,14 +141,42 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
STEP_OUTCOME: ${{ steps.load_invalid.outcome }}
|
STEP_OUTCOME: ${{ steps.load_invalid.outcome }}
|
||||||
|
|
||||||
|
- name: Load secrets by vault ID
|
||||||
|
id: load_secrets_by_vault_id
|
||||||
|
uses: ./
|
||||||
|
with:
|
||||||
|
export-env: ${{ matrix.export-env }}
|
||||||
|
env:
|
||||||
|
SECRET: op://${{ secrets.VAULT_ID }}/test-secret/password
|
||||||
|
SECRET_IN_SECTION: op://${{ secrets.VAULT_ID }}/test-secret/test-section/password
|
||||||
|
MULTILINE_SECRET: op://${{ secrets.VAULT_ID }}/multiline-secret/notesPlain
|
||||||
|
SECRET_WITH_FILE: op://${{ secrets.VAULT_ID }}/file-secret/test.txt
|
||||||
|
SECRET_WITH_FILE_IN_SECTION: op://${{ secrets.VAULT_ID }}/file-secret/file section/test.txt
|
||||||
|
DOUBLE_SECTION_SECRET: op://${{ secrets.VAULT_ID }}/double-section-secret/test-section/password
|
||||||
|
OP_ENV_FILE: ./tests/.vaultId_env.tpl
|
||||||
|
|
||||||
|
- name: Assert test secret values [vault by ID]
|
||||||
|
if: ${{ !matrix.export-env }}
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
SECRET: ${{ steps.load_secrets_by_vault_id.outputs.SECRET }}
|
||||||
|
SECRET_IN_SECTION: ${{ steps.load_secrets_by_vault_id.outputs.SECRET_IN_SECTION }}
|
||||||
|
MULTILINE_SECRET: ${{ steps.load_secrets_by_vault_id.outputs.MULTILINE_SECRET }}
|
||||||
|
FILE_SECRET: ${{ steps.load_secrets_by_vault_id.outputs.FILE_SECRET }}
|
||||||
|
FILE_SECRET_IN_SECTION: ${{ steps.load_secrets_by_vault_id.outputs.FILE_SECRET_IN_SECTION }}
|
||||||
|
FILE_MULTILINE_SECRET: ${{ steps.load_secrets_by_vault_id.outputs.FILE_MULTILINE_SECRET }}
|
||||||
|
SECRET_WITH_FILE: ${{ steps.load_secrets_by_vault_id.outputs.SECRET_WITH_FILE }}
|
||||||
|
SECRET_WITH_FILE_IN_SECTION: ${{ steps.load_secrets_by_vault_id.outputs.SECRET_WITH_FILE_IN_SECTION }}
|
||||||
|
DOUBLE_SECTION_SECRET: ${{ steps.load_secrets_by_vault_id.outputs.DOUBLE_SECTION_SECRET }}
|
||||||
|
run: ./tests/assert-env-set.sh
|
||||||
|
|
||||||
test-connect:
|
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
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
version: [latest, 2.30.0]
|
|
||||||
export-env: [true, false]
|
export-env: [true, false]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@@ -153,13 +203,25 @@ jobs:
|
|||||||
echo "FILE_SECRET=op://${{ secrets.VAULT }}/test-secret/password" > tests/.env.tpl
|
echo "FILE_SECRET=op://${{ secrets.VAULT }}/test-secret/password" > tests/.env.tpl
|
||||||
echo "FILE_SECRET_IN_SECTION=op://${{ secrets.VAULT }}/test-secret/test-section/password" >> tests/.env.tpl
|
echo "FILE_SECRET_IN_SECTION=op://${{ secrets.VAULT }}/test-secret/test-section/password" >> tests/.env.tpl
|
||||||
echo "FILE_MULTILINE_SECRET=op://${{ secrets.VAULT }}/multiline-secret/notesPlain" >> tests/.env.tpl
|
echo "FILE_MULTILINE_SECRET=op://${{ secrets.VAULT }}/multiline-secret/notesPlain" >> tests/.env.tpl
|
||||||
|
echo "SECRET_WITH_FILE=op://${{ secrets.VAULT }}/file-secret/test.txt" >> tests/.env.tpl
|
||||||
|
echo "SECRET_WITH_FILE_IN_SECTION=op://${{ secrets.VAULT }}/file-secret/file section/test.txt" >> tests/.env.tpl
|
||||||
|
echo "DOUBLE_SECTION_SECRET=op://${{ secrets.VAULT }}/double-section-secret/test-section/password" >> tests/.env.tpl
|
||||||
|
|
||||||
|
- name: Generate .vaultId_env.tpl
|
||||||
|
run: |
|
||||||
|
echo "FILE_SECRET=op://${{ secrets.VAULT_ID }}/test-secret/password" > tests/.vaultId_env.tpl
|
||||||
|
echo "FILE_SECRET_IN_SECTION=op://${{ secrets.VAULT_ID }}/test-secret/test-section/password" >> tests/.vaultId_env.tpl
|
||||||
|
echo "FILE_MULTILINE_SECRET=op://${{ secrets.VAULT_ID }}/multiline-secret/notesPlain" >> tests/.vaultId_env.tpl
|
||||||
|
echo "SECRET_WITH_FILE=op://${{ secrets.VAULT_ID }}/file-secret/test.txt" >> tests/.vaultId_env.tpl
|
||||||
|
echo "SECRET_WITH_FILE_IN_SECTION=op://${{ secrets.VAULT_ID }}/file-secret/file section/test.txt" >> tests/.vaultId_env.tpl
|
||||||
|
echo "DOUBLE_SECTION_SECRET=op://${{ secrets.VAULT_ID }}/double-section-secret/test-section/password" >> tests/.vaultId_env.tpl
|
||||||
|
|
||||||
- name: Launch 1Password Connect instance
|
- name: Launch 1Password Connect instance
|
||||||
env:
|
env:
|
||||||
OP_CONNECT_CREDENTIALS: ${{ secrets.OP_CONNECT_CREDENTIALS }}
|
OP_CONNECT_CREDENTIALS: ${{ secrets.OP_CONNECT_CREDENTIALS }}
|
||||||
run: |
|
run: |
|
||||||
echo "$OP_CONNECT_CREDENTIALS" > 1password-credentials.json
|
echo "$OP_CONNECT_CREDENTIALS" > 1password-credentials.json
|
||||||
docker compose -f tests/fixtures/docker-compose.yml up -d && sleep 10
|
docker compose -f tests/fixtures/docker-compose.yml up -d && sleep 30
|
||||||
|
|
||||||
- name: Configure 1Password Connect
|
- name: Configure 1Password Connect
|
||||||
uses: ./configure
|
uses: ./configure
|
||||||
@@ -171,12 +233,14 @@ jobs:
|
|||||||
id: load_secrets
|
id: load_secrets
|
||||||
uses: ./
|
uses: ./
|
||||||
with:
|
with:
|
||||||
version: ${{ matrix.version }}
|
|
||||||
export-env: ${{ matrix.export-env }}
|
export-env: ${{ matrix.export-env }}
|
||||||
env:
|
env:
|
||||||
SECRET: op://${{ secrets.VAULT }}/test-secret/password
|
SECRET: op://${{ secrets.VAULT }}/test-secret/password
|
||||||
SECRET_IN_SECTION: op://${{ secrets.VAULT }}/test-secret/test-section/password
|
SECRET_IN_SECTION: op://${{ secrets.VAULT }}/test-secret/test-section/password
|
||||||
MULTILINE_SECRET: op://${{ secrets.VAULT }}/multiline-secret/notesPlain
|
MULTILINE_SECRET: op://${{ secrets.VAULT }}/multiline-secret/notesPlain
|
||||||
|
SECRET_WITH_FILE: op://${{ secrets.VAULT }}/file-secret/test.txt
|
||||||
|
SECRET_WITH_FILE_IN_SECTION: op://${{ secrets.VAULT }}/file-secret/file section/test.txt
|
||||||
|
DOUBLE_SECTION_SECRET: op://${{ secrets.VAULT }}/double-section-secret/test-section/password
|
||||||
OP_ENV_FILE: ./tests/.env.tpl
|
OP_ENV_FILE: ./tests/.env.tpl
|
||||||
|
|
||||||
- name: Assert test secret values [step output]
|
- name: Assert test secret values [step output]
|
||||||
@@ -188,6 +252,9 @@ jobs:
|
|||||||
FILE_SECRET: ${{ steps.load_secrets.outputs.FILE_SECRET }}
|
FILE_SECRET: ${{ steps.load_secrets.outputs.FILE_SECRET }}
|
||||||
FILE_SECRET_IN_SECTION: ${{ steps.load_secrets.outputs.FILE_SECRET_IN_SECTION }}
|
FILE_SECRET_IN_SECTION: ${{ steps.load_secrets.outputs.FILE_SECRET_IN_SECTION }}
|
||||||
FILE_MULTILINE_SECRET: ${{ steps.load_secrets.outputs.FILE_MULTILINE_SECRET }}
|
FILE_MULTILINE_SECRET: ${{ steps.load_secrets.outputs.FILE_MULTILINE_SECRET }}
|
||||||
|
SECRET_WITH_FILE: ${{ steps.load_secrets.outputs.SECRET_WITH_FILE }}
|
||||||
|
SECRET_WITH_FILE_IN_SECTION: ${{ steps.load_secrets.outputs.SECRET_WITH_FILE_IN_SECTION }}
|
||||||
|
DOUBLE_SECTION_SECRET: ${{ steps.load_secrets.outputs.DOUBLE_SECTION_SECRET }}
|
||||||
run: ./tests/assert-env-set.sh
|
run: ./tests/assert-env-set.sh
|
||||||
|
|
||||||
- name: Assert test secret values [exported env]
|
- name: Assert test secret values [exported env]
|
||||||
@@ -203,3 +270,47 @@ jobs:
|
|||||||
- name: Assert removed secrets [exported env]
|
- name: Assert removed secrets [exported env]
|
||||||
if: ${{ matrix.export-env }}
|
if: ${{ matrix.export-env }}
|
||||||
run: ./tests/assert-env-unset.sh
|
run: ./tests/assert-env-unset.sh
|
||||||
|
|
||||||
|
- name: Load secrets (invalid ref - expect failure)
|
||||||
|
id: load_invalid
|
||||||
|
continue-on-error: true
|
||||||
|
uses: ./
|
||||||
|
env:
|
||||||
|
BAD_REF: "op://x"
|
||||||
|
with:
|
||||||
|
export-env: true
|
||||||
|
|
||||||
|
- name: Assert invalid ref failed
|
||||||
|
shell: bash
|
||||||
|
run: ./tests/assert-invalid-ref-failed.sh
|
||||||
|
env:
|
||||||
|
STEP_OUTCOME: ${{ steps.load_invalid.outcome }}
|
||||||
|
|
||||||
|
- name: Load secrets by vault ID
|
||||||
|
id: load_secrets_by_vault_id
|
||||||
|
uses: ./
|
||||||
|
with:
|
||||||
|
export-env: ${{ matrix.export-env }}
|
||||||
|
env:
|
||||||
|
SECRET: op://${{ secrets.VAULT_ID }}/test-secret/password
|
||||||
|
SECRET_IN_SECTION: op://${{ secrets.VAULT_ID }}/test-secret/test-section/password
|
||||||
|
MULTILINE_SECRET: op://${{ secrets.VAULT_ID }}/multiline-secret/notesPlain
|
||||||
|
SECRET_WITH_FILE: op://${{ secrets.VAULT_ID }}/file-secret/test.txt
|
||||||
|
SECRET_WITH_FILE_IN_SECTION: op://${{ secrets.VAULT_ID }}/file-secret/file section/test.txt
|
||||||
|
DOUBLE_SECTION_SECRET: op://${{ secrets.VAULT_ID }}/double-section-secret/test-section/password
|
||||||
|
OP_ENV_FILE: ./tests/.vaultId_env.tpl
|
||||||
|
|
||||||
|
- name: Assert test secret values [vault by ID]
|
||||||
|
if: ${{ !matrix.export-env }}
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
SECRET: ${{ steps.load_secrets_by_vault_id.outputs.SECRET }}
|
||||||
|
SECRET_IN_SECTION: ${{ steps.load_secrets_by_vault_id.outputs.SECRET_IN_SECTION }}
|
||||||
|
MULTILINE_SECRET: ${{ steps.load_secrets_by_vault_id.outputs.MULTILINE_SECRET }}
|
||||||
|
FILE_SECRET: ${{ steps.load_secrets_by_vault_id.outputs.FILE_SECRET }}
|
||||||
|
FILE_SECRET_IN_SECTION: ${{ steps.load_secrets_by_vault_id.outputs.FILE_SECRET_IN_SECTION }}
|
||||||
|
FILE_MULTILINE_SECRET: ${{ steps.load_secrets_by_vault_id.outputs.FILE_MULTILINE_SECRET }}
|
||||||
|
SECRET_WITH_FILE: ${{ steps.load_secrets_by_vault_id.outputs.SECRET_WITH_FILE }}
|
||||||
|
SECRET_WITH_FILE_IN_SECTION: ${{ steps.load_secrets_by_vault_id.outputs.SECRET_WITH_FILE_IN_SECTION }}
|
||||||
|
DOUBLE_SECTION_SECRET: ${{ steps.load_secrets_by_vault_id.outputs.DOUBLE_SECTION_SECRET }}
|
||||||
|
run: ./tests/assert-env-set.sh
|
||||||
|
|||||||
1
.github/workflows/test-e2e.yml
vendored
1
.github/workflows/test-e2e.yml
vendored
@@ -91,6 +91,7 @@ jobs:
|
|||||||
OP_CONNECT_TOKEN: ${{ secrets.OP_CONNECT_TOKEN }}
|
OP_CONNECT_TOKEN: ${{ secrets.OP_CONNECT_TOKEN }}
|
||||||
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
|
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
|
||||||
VAULT: ${{ secrets.VAULT }}
|
VAULT: ${{ secrets.VAULT }}
|
||||||
|
VAULT_ID: ${{ secrets.VAULT_ID }}
|
||||||
|
|
||||||
# Post comment on fork PRs after /ok-to-test
|
# Post comment on fork PRs after /ok-to-test
|
||||||
comment-pr:
|
comment-pr:
|
||||||
|
|||||||
@@ -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
|
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
|
## 💙 Community & Support
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,9 @@ inputs:
|
|||||||
export-env:
|
export-env:
|
||||||
description: Export the secrets as environment variables
|
description: Export the secrets as environment variables
|
||||||
default: "false"
|
default: "false"
|
||||||
|
# Backward-compatible no-op input (intentionally ignored)
|
||||||
version:
|
version:
|
||||||
description: Specify which 1Password CLI version to install. Defaults to "latest".
|
description: "(Deprecated) No longer used. Kept for backwards compatibility."
|
||||||
default: "latest"
|
default: "latest"
|
||||||
runs:
|
runs:
|
||||||
using: "node20"
|
using: "node20"
|
||||||
|
|||||||
@@ -10,11 +10,6 @@ const jestConfig = {
|
|||||||
rootDir: "../src/",
|
rootDir: "../src/",
|
||||||
testEnvironment: "node",
|
testEnvironment: "node",
|
||||||
testRegex: "(/__tests__/.*|(\\.|/)test)\\.ts",
|
testRegex: "(/__tests__/.*|(\\.|/)test)\\.ts",
|
||||||
moduleNameMapper: {
|
|
||||||
"^@actions/core$": "<rootDir>/__mocks__/actions-core.ts",
|
|
||||||
"^@actions/tool-cache$": "<rootDir>/__mocks__/actions-tool-cache.ts",
|
|
||||||
"^@actions/exec$": "<rootDir>/__mocks__/actions-exec.ts",
|
|
||||||
},
|
|
||||||
transform: {
|
transform: {
|
||||||
".ts": [
|
".ts": [
|
||||||
"ts-jest",
|
"ts-jest",
|
||||||
@@ -30,4 +25,4 @@ const jestConfig = {
|
|||||||
verbose: true,
|
verbose: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = jestConfig;
|
export default jestConfig;
|
||||||
|
|||||||
32714
configure/dist/index.js
vendored
32714
configure/dist/index.js
vendored
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
|
|||||||
import * as core from "@actions/core";
|
const core = require("@actions/core");
|
||||||
|
|
||||||
const configure = () => {
|
const configure = () => {
|
||||||
const OP_CONNECT_HOST =
|
const OP_CONNECT_HOST =
|
||||||
|
|||||||
BIN
dist/core_bg.wasm
vendored
BIN
dist/core_bg.wasm
vendored
Binary file not shown.
38762
dist/index.js
vendored
38762
dist/index.js
vendored
File diff suppressed because one or more lines are too long
@@ -17,7 +17,8 @@ This document explains how to run e2e tests locally using `act`.
|
|||||||
| Secret | Description |
|
| Secret | Description |
|
||||||
| -------------------------- | --------------------- |
|
| -------------------------- | --------------------- |
|
||||||
| `OP_SERVICE_ACCOUNT_TOKEN` | Service Account token |
|
| `OP_SERVICE_ACCOUNT_TOKEN` | Service Account token |
|
||||||
| `VAULT` | Vault name or UUID |
|
| `VAULT` | Vault name |
|
||||||
|
| `VAULT_ID` | Vault UUID |
|
||||||
|
|
||||||
## Building Before Testing
|
## Building Before Testing
|
||||||
|
|
||||||
|
|||||||
294
package-lock.json
generated
294
package-lock.json
generated
@@ -1,19 +1,19 @@
|
|||||||
{
|
{
|
||||||
"name": "load-secrets-action",
|
"name": "load-secrets-action",
|
||||||
"version": "3.2.0",
|
"version": "3.1.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "load-secrets-action",
|
"name": "load-secrets-action",
|
||||||
"version": "3.2.0",
|
"version": "3.1.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@1password/op-js": "^0.1.11",
|
"@1password/connect": "^1.4.2",
|
||||||
"@1password/sdk": "^0.4.0",
|
"@1password/sdk": "^0.4.0",
|
||||||
"@actions/core": "^3.0.0",
|
"@actions/core": "^1.10.1",
|
||||||
"@actions/exec": "^3.0.0",
|
"@actions/exec": "^1.1.1",
|
||||||
"@actions/tool-cache": "^4.0.0",
|
"@actions/tool-cache": "^2.0.2",
|
||||||
"dotenv": "^17.2.2"
|
"dotenv": "^17.2.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -29,6 +29,19 @@
|
|||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@1password/connect": {
|
||||||
|
"version": "1.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@1password/connect/-/connect-1.4.2.tgz",
|
||||||
|
"integrity": "sha512-CxcDQIr76nloWwGWRrmz/U7DuU65WKrN/yarq45LrC3L6b/pC7bZyskvougadG32fRwBieLJX143lTI8T1bAtQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.10.0",
|
||||||
|
"debug": "^4.4.1",
|
||||||
|
"lodash.clonedeep": "^4.5.0",
|
||||||
|
"slugify": "^1.6.6",
|
||||||
|
"uuid": "^9.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@1password/eslint-config": {
|
"node_modules/@1password/eslint-config": {
|
||||||
"version": "4.3.1",
|
"version": "4.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/@1password/eslint-config/-/eslint-config-4.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/@1password/eslint-config/-/eslint-config-4.3.1.tgz",
|
||||||
@@ -53,16 +66,6 @@
|
|||||||
"typescript": "^5.0.0"
|
"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": {
|
"node_modules/@1password/prettier-config": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@1password/prettier-config/-/prettier-config-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@1password/prettier-config/-/prettier-config-1.2.0.tgz",
|
||||||
@@ -89,49 +92,58 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@actions/core": {
|
"node_modules/@actions/core": {
|
||||||
"version": "3.0.0",
|
"version": "1.11.1",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz",
|
||||||
"integrity": "sha512-zYt6cz+ivnTmiT/ksRVriMBOiuoUpDCJJlZ5KPl2/FRdvwU3f7MPh9qftvbkXJThragzUZieit2nyHUyw53Seg==",
|
"integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/exec": "^3.0.0",
|
"@actions/exec": "^1.1.1",
|
||||||
"@actions/http-client": "^4.0.0"
|
"@actions/http-client": "^2.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@actions/exec": {
|
"node_modules/@actions/exec": {
|
||||||
"version": "3.0.0",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz",
|
||||||
"integrity": "sha512-6xH/puSoNBXb72VPlZVm7vQ+svQpFyA96qdDBvhB8eNZOE8LtPf9L4oAsfzK/crCL8YZ+19fKYVnM63Sl+Xzlw==",
|
"integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/io": "^3.0.2"
|
"@actions/io": "^1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@actions/http-client": {
|
"node_modules/@actions/http-client": {
|
||||||
"version": "4.0.0",
|
"version": "2.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.3.tgz",
|
||||||
"integrity": "sha512-QuwPsgVMsD6qaPD57GLZi9sqzAZCtiJT8kVBCDpLtxhL5MydQ4gS+DrejtZZPdIYyB1e95uCK9Luyds7ybHI3g==",
|
"integrity": "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tunnel": "^0.0.6",
|
"tunnel": "^0.0.6",
|
||||||
"undici": "^6.23.0"
|
"undici": "^5.25.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@actions/io": {
|
"node_modules/@actions/io": {
|
||||||
"version": "3.0.2",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/io/-/io-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz",
|
||||||
"integrity": "sha512-nRBchcMM+QK1pdjO7/idu86rbJI5YHUKCvKs0KxnSYbVe3F51UfGxuZX4Qy/fWlp6l7gWFwIkrOzN+oUK03kfw=="
|
"integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==",
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@actions/tool-cache": {
|
"node_modules/@actions/tool-cache": {
|
||||||
"version": "4.0.0",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/tool-cache/-/tool-cache-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/tool-cache/-/tool-cache-2.0.2.tgz",
|
||||||
"integrity": "sha512-L8P9HbXvpvqjZDveb/fdsa55IVC0trfPgQ4ZwGo6r5af6YDVdM9vMGPZ7rgY2fAT9gGj4PSYd6bYlg3p3jD78A==",
|
"integrity": "sha512-fBhNNOWxuoLxztQebpOaWu6WeVmuwa77Z+DxIZ1B+OYvGkGQon6kTVg6Z32Cb13WCuw0szqonK+hh03mJV7Z6w==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^3.0.0",
|
"@actions/core": "^1.11.1",
|
||||||
"@actions/exec": "^3.0.0",
|
"@actions/exec": "^1.0.0",
|
||||||
"@actions/http-client": "^4.0.0",
|
"@actions/http-client": "^2.0.1",
|
||||||
"@actions/io": "^3.0.0",
|
"@actions/io": "^1.1.1",
|
||||||
"semver": "^7.7.3"
|
"semver": "^6.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@actions/tool-cache/node_modules/semver": {
|
||||||
|
"version": "6.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||||
|
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||||
|
"bin": {
|
||||||
|
"semver": "bin/semver.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@ampproject/remapping": {
|
"node_modules/@ampproject/remapping": {
|
||||||
@@ -766,6 +778,15 @@
|
|||||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@fastify/busboy": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@humanwhocodes/config-array": {
|
"node_modules/@humanwhocodes/config-array": {
|
||||||
"version": "0.13.0",
|
"version": "0.13.0",
|
||||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
|
||||||
@@ -1962,6 +1983,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/asynckit": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/available-typed-arrays": {
|
"node_modules/available-typed-arrays": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
|
||||||
@@ -1988,6 +2015,17 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/axios": {
|
||||||
|
"version": "1.13.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz",
|
||||||
|
"integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"follow-redirects": "^1.15.11",
|
||||||
|
"form-data": "^4.0.5",
|
||||||
|
"proxy-from-env": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/axobject-query": {
|
"node_modules/axobject-query": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
||||||
@@ -2254,7 +2292,6 @@
|
|||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz",
|
||||||
"integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==",
|
"integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-errors": "^1.3.0",
|
"es-errors": "^1.3.0",
|
||||||
@@ -2499,6 +2536,18 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/combined-stream": {
|
||||||
|
"version": "1.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
|
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"delayed-stream": "~1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/commander": {
|
"node_modules/commander": {
|
||||||
"version": "12.1.0",
|
"version": "12.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
|
||||||
@@ -2632,10 +2681,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.4.0",
|
"version": "4.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||||
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
|
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ms": "^2.1.3"
|
"ms": "^2.1.3"
|
||||||
@@ -2718,6 +2766,15 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/delayed-stream": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/detect-newline": {
|
"node_modules/detect-newline": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
|
||||||
@@ -2781,7 +2838,6 @@
|
|||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind-apply-helpers": "^1.0.1",
|
"call-bind-apply-helpers": "^1.0.1",
|
||||||
@@ -2925,7 +2981,6 @@
|
|||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@@ -2935,7 +2990,6 @@
|
|||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@@ -2972,7 +3026,6 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz",
|
||||||
"integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==",
|
"integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-errors": "^1.3.0"
|
"es-errors": "^1.3.0"
|
||||||
@@ -2982,15 +3035,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/es-set-tostringtag": {
|
"node_modules/es-set-tostringtag": {
|
||||||
"version": "2.0.3",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||||
"integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==",
|
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"get-intrinsic": "^1.2.4",
|
"es-errors": "^1.3.0",
|
||||||
|
"get-intrinsic": "^1.2.6",
|
||||||
"has-tostringtag": "^1.0.2",
|
"has-tostringtag": "^1.0.2",
|
||||||
"hasown": "^2.0.1"
|
"hasown": "^2.0.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@@ -3823,6 +3876,26 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
|
"node_modules/follow-redirects": {
|
||||||
|
"version": "1.15.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
||||||
|
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"debug": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/for-each": {
|
"node_modules/for-each": {
|
||||||
"version": "0.3.3",
|
"version": "0.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
|
||||||
@@ -3833,6 +3906,22 @@
|
|||||||
"is-callable": "^1.1.3"
|
"is-callable": "^1.1.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/form-data": {
|
||||||
|
"version": "4.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
|
||||||
|
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"asynckit": "^0.4.0",
|
||||||
|
"combined-stream": "^1.0.8",
|
||||||
|
"es-set-tostringtag": "^2.1.0",
|
||||||
|
"hasown": "^2.0.2",
|
||||||
|
"mime-types": "^2.1.12"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fs.realpath": {
|
"node_modules/fs.realpath": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||||
@@ -3859,7 +3948,6 @@
|
|||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
@@ -3932,7 +4020,6 @@
|
|||||||
"version": "1.2.6",
|
"version": "1.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz",
|
||||||
"integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==",
|
"integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind-apply-helpers": "^1.0.1",
|
"call-bind-apply-helpers": "^1.0.1",
|
||||||
@@ -4089,7 +4176,6 @@
|
|||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@@ -4175,7 +4261,6 @@
|
|||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@@ -4188,7 +4273,6 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"has-symbols": "^1.0.3"
|
"has-symbols": "^1.0.3"
|
||||||
@@ -4204,7 +4288,6 @@
|
|||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"function-bind": "^1.1.2"
|
"function-bind": "^1.1.2"
|
||||||
@@ -5935,6 +6018,12 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.clonedeep": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
|
||||||
|
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/lodash.memoize": {
|
"node_modules/lodash.memoize": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
||||||
@@ -6061,18 +6150,6 @@
|
|||||||
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
"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": {
|
"node_modules/loose-envify": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||||
@@ -6133,7 +6210,6 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.0.0.tgz",
|
||||||
"integrity": "sha512-4MqMiKP90ybymYvsut0CH2g4XWbfLtmlCkXmtmdcDCxNB+mQcu1w/1+L/VD7vi/PSv7X2JYV7SCcR+jiPXnQtA==",
|
"integrity": "sha512-4MqMiKP90ybymYvsut0CH2g4XWbfLtmlCkXmtmdcDCxNB+mQcu1w/1+L/VD7vi/PSv7X2JYV7SCcR+jiPXnQtA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@@ -6170,6 +6246,27 @@
|
|||||||
"node": ">=8.6"
|
"node": ">=8.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/mime-db": {
|
||||||
|
"version": "1.52.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||||
|
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mime-types": {
|
||||||
|
"version": "2.1.35",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||||
|
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"mime-db": "1.52.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/mimic-fn": {
|
"node_modules/mimic-fn": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
|
||||||
@@ -6220,7 +6317,6 @@
|
|||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/natural-compare": {
|
"node_modules/natural-compare": {
|
||||||
@@ -6706,6 +6802,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/proxy-from-env": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/punycode": {
|
"node_modules/punycode": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||||
@@ -7021,9 +7123,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/semver": {
|
"node_modules/semver": {
|
||||||
"version": "7.7.4",
|
"version": "7.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
||||||
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
|
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
|
||||||
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"bin": {
|
"bin": {
|
||||||
"semver": "bin/semver.js"
|
"semver": "bin/semver.js"
|
||||||
@@ -7219,6 +7322,15 @@
|
|||||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/slugify": {
|
||||||
|
"version": "1.6.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz",
|
||||||
|
"integrity": "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/source-map": {
|
"node_modules/source-map": {
|
||||||
"version": "0.6.1",
|
"version": "0.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||||
@@ -7890,12 +8002,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/undici": {
|
"node_modules/undici": {
|
||||||
"version": "6.23.0",
|
"version": "5.29.0",
|
||||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz",
|
"resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz",
|
||||||
"integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==",
|
"integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@fastify/busboy": "^2.0.0"
|
||||||
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.17"
|
"node": ">=14.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/undici-types": {
|
"node_modules/undici-types": {
|
||||||
@@ -7947,6 +8062,19 @@
|
|||||||
"punycode": "^2.1.0"
|
"punycode": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/uuid": {
|
||||||
|
"version": "9.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
|
||||||
|
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
|
||||||
|
"funding": [
|
||||||
|
"https://github.com/sponsors/broofa",
|
||||||
|
"https://github.com/sponsors/ctavan"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"uuid": "dist/bin/uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/v8-to-istanbul": {
|
"node_modules/v8-to-istanbul": {
|
||||||
"version": "9.3.0",
|
"version": "9.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
|
||||||
|
|||||||
10
package.json
10
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "load-secrets-action",
|
"name": "load-secrets-action",
|
||||||
"version": "3.2.0",
|
"version": "3.1.0",
|
||||||
"description": "Load Secrets from 1Password",
|
"description": "Load Secrets from 1Password",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"directories": {
|
"directories": {
|
||||||
@@ -40,11 +40,11 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/1Password/load-secrets-action#readme",
|
"homepage": "https://github.com/1Password/load-secrets-action#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@1password/op-js": "^0.1.11",
|
|
||||||
"@1password/sdk": "^0.4.0",
|
"@1password/sdk": "^0.4.0",
|
||||||
"@actions/core": "^3.0.0",
|
"@1password/connect": "^1.4.2",
|
||||||
"@actions/exec": "^3.0.0",
|
"@actions/core": "^1.10.1",
|
||||||
"@actions/tool-cache": "^4.0.0",
|
"@actions/exec": "^1.1.1",
|
||||||
|
"@actions/tool-cache": "^2.0.2",
|
||||||
"dotenv": "^17.2.2"
|
"dotenv": "^17.2.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
getInput: jest.fn(() => ""),
|
|
||||||
getBooleanInput: jest.fn(() => false),
|
|
||||||
setOutput: jest.fn(),
|
|
||||||
setSecret: jest.fn(),
|
|
||||||
exportVariable: jest.fn(),
|
|
||||||
setFailed: jest.fn(),
|
|
||||||
info: jest.fn(),
|
|
||||||
warning: jest.fn(),
|
|
||||||
error: jest.fn(),
|
|
||||||
debug: jest.fn(),
|
|
||||||
addPath: jest.fn(),
|
|
||||||
isDebug: jest.fn(() => false),
|
|
||||||
};
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
getExecOutput: jest.fn(() => ({
|
|
||||||
stdout: "MOCK_SECRET",
|
|
||||||
})),
|
|
||||||
};
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
downloadTool: jest.fn(),
|
|
||||||
extractTar: jest.fn(),
|
|
||||||
extractZip: jest.fn(),
|
|
||||||
cacheDir: jest.fn<Promise<string>, [string]>(async (dir) => {
|
|
||||||
await Promise.resolve();
|
|
||||||
return dir;
|
|
||||||
}),
|
|
||||||
find: jest.fn<string, [string, string?, string?]>(() => ""),
|
|
||||||
};
|
|
||||||
27
src/index.ts
27
src/index.ts
@@ -1,9 +1,7 @@
|
|||||||
import dotenv from "dotenv";
|
import dotenv from "dotenv";
|
||||||
import * as core from "@actions/core";
|
import * as core from "@actions/core";
|
||||||
import { validateCli } from "@1password/op-js";
|
|
||||||
import { installCliOnGithubActionRunner } from "./op-cli-installer";
|
|
||||||
import { loadSecrets, unsetPrevious, validateAuth } from "./utils";
|
import { loadSecrets, unsetPrevious, validateAuth } from "./utils";
|
||||||
import { envFilePath, envConnectHost, envConnectToken } from "./constants";
|
import { envFilePath } from "./constants";
|
||||||
|
|
||||||
const loadSecretsAction = async () => {
|
const loadSecretsAction = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -16,7 +14,7 @@ const loadSecretsAction = async () => {
|
|||||||
unsetPrevious();
|
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();
|
validateAuth();
|
||||||
|
|
||||||
// Set environment variables from OP_ENV_FILE
|
// Set environment variables from OP_ENV_FILE
|
||||||
@@ -26,13 +24,6 @@ const loadSecretsAction = async () => {
|
|||||||
dotenv.config({ path: file });
|
dotenv.config({ path: file });
|
||||||
}
|
}
|
||||||
|
|
||||||
const isConnect =
|
|
||||||
process.env[envConnectHost] && process.env[envConnectToken];
|
|
||||||
// If Connect is used, download and install the CLI
|
|
||||||
if (isConnect) {
|
|
||||||
await installCLI();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load secrets
|
// Load secrets
|
||||||
await loadSecrets(shouldExportEnv);
|
await loadSecrets(shouldExportEnv);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -43,22 +34,10 @@ const loadSecretsAction = async () => {
|
|||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
message = error.message;
|
message = error.message;
|
||||||
} else {
|
} else {
|
||||||
String(error);
|
message = String(error);
|
||||||
}
|
}
|
||||||
core.setFailed(message);
|
core.setFailed(message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// This function's name is an exception from the naming convention
|
|
||||||
// since we refer to the 1Password CLI here.
|
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
||||||
const installCLI = async (): Promise<void> => {
|
|
||||||
// validateCli checks if there's an existing 1Password CLI installed on the runner.
|
|
||||||
// If there's no CLI installed, then validateCli will throw an error, which we will use
|
|
||||||
// as an indicator that we need to execute the installation script.
|
|
||||||
await validateCli().catch(async () => {
|
|
||||||
await installCliOnGithubActionRunner();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
void loadSecretsAction();
|
void loadSecretsAction();
|
||||||
|
|||||||
@@ -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<string, string> = {
|
|
||||||
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<void> {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { type Installer, newCliInstaller } from "./installer";
|
|
||||||
@@ -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",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
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}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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<void> {
|
|
||||||
const urlBuilder = cliUrlBuilder[this.platform];
|
|
||||||
await super.install(urlBuilder(this.version, this.arch));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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<void> {
|
|
||||||
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<void> {
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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<void> {
|
|
||||||
const urlBuilder = cliUrlBuilder[this.platform];
|
|
||||||
await super.install(urlBuilder(this.version, this.arch));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<void> => {
|
|
||||||
// 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();
|
|
||||||
};
|
|
||||||
@@ -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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
export { installCliOnGithubActionRunner } from "./github-action";
|
|
||||||
export { ReleaseChannel, VersionResolver } from "./version";
|
|
||||||
@@ -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 };
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -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`,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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<string> => {
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
export { VersionResolver } from "./version-resolver";
|
|
||||||
export { ReleaseChannel } from "./constants";
|
|
||||||
@@ -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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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}`);
|
|
||||||
};
|
|
||||||
@@ -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");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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<void> {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,15 @@
|
|||||||
import * as core from "@actions/core";
|
import * as core from "@actions/core";
|
||||||
import * as exec from "@actions/exec";
|
import * as exec from "@actions/exec";
|
||||||
import { read, setClientInfo } from "@1password/op-js";
|
|
||||||
import { createClient, Secrets } from "@1password/sdk";
|
import { createClient, Secrets } from "@1password/sdk";
|
||||||
|
import { OnePasswordConnect, FullItem } from "@1password/connect";
|
||||||
import {
|
import {
|
||||||
extractSecret,
|
|
||||||
loadSecrets,
|
loadSecrets,
|
||||||
unsetPrevious,
|
unsetPrevious,
|
||||||
validateAuth,
|
validateAuth,
|
||||||
|
findMatchingFieldAndFile,
|
||||||
|
findSectionIdsByQuery,
|
||||||
|
parseOpRef,
|
||||||
|
getEnvVarNamesWithSecretRefs,
|
||||||
} from "./utils";
|
} from "./utils";
|
||||||
import {
|
import {
|
||||||
authErr,
|
authErr,
|
||||||
@@ -16,7 +19,12 @@ import {
|
|||||||
envServiceAccountToken,
|
envServiceAccountToken,
|
||||||
} from "./constants";
|
} from "./constants";
|
||||||
|
|
||||||
jest.mock("@1password/op-js");
|
jest.mock("@actions/core");
|
||||||
|
jest.mock("@actions/exec", () => ({
|
||||||
|
getExecOutput: jest.fn(() => ({
|
||||||
|
stdout: "MOCK_SECRET",
|
||||||
|
})),
|
||||||
|
}));
|
||||||
jest.mock("@1password/sdk", () => ({
|
jest.mock("@1password/sdk", () => ({
|
||||||
createClient: jest.fn(),
|
createClient: jest.fn(),
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
@@ -24,6 +32,7 @@ jest.mock("@1password/sdk", () => ({
|
|||||||
validateSecretReference: jest.fn(),
|
validateSecretReference: jest.fn(),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
jest.mock("@1password/connect");
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
@@ -74,111 +83,227 @@ 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", () => {
|
describe("loadSecrets when using Connect", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
process.env[envConnectHost] = "https://localhost:8000";
|
process.env[envConnectHost] = "https://connect.example";
|
||||||
process.env[envConnectToken] = "token";
|
process.env[envConnectToken] = "test-token";
|
||||||
process.env[envServiceAccountToken] = "";
|
process.env[envServiceAccountToken] = "";
|
||||||
|
|
||||||
|
Object.keys(process.env).forEach((key) => {
|
||||||
|
if (
|
||||||
|
typeof process.env[key] === "string" &&
|
||||||
|
process.env[key]?.startsWith("op://")
|
||||||
|
) {
|
||||||
|
delete process.env[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
process.env.MY_SECRET = "op://vault/item/field";
|
||||||
|
|
||||||
|
(OnePasswordConnect as jest.Mock).mockReturnValue({
|
||||||
|
getVault: jest.fn().mockResolvedValue({ id: "vault-id-123" }),
|
||||||
|
getItem: jest.fn().mockResolvedValue({
|
||||||
|
fields: [
|
||||||
|
{ label: "field", value: "resolved-via-connect", section: undefined },
|
||||||
|
],
|
||||||
|
sections: [],
|
||||||
|
}),
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("sets the client info and gets the executed output", async () => {
|
it("resolves ref via Connect SDK and exports secret", async () => {
|
||||||
await loadSecrets(true);
|
await loadSecrets(true);
|
||||||
|
|
||||||
expect(setClientInfo).toHaveBeenCalledWith({
|
|
||||||
name: "1Password GitHub Action",
|
|
||||||
id: "GHA",
|
|
||||||
});
|
|
||||||
expect(exec.getExecOutput).toHaveBeenCalledWith('sh -c "op env ls"');
|
|
||||||
expect(core.exportVariable).toHaveBeenCalledWith(
|
expect(core.exportVariable).toHaveBeenCalledWith(
|
||||||
"OP_MANAGED_VARIABLES",
|
"MY_SECRET",
|
||||||
"MOCK_SECRET",
|
"resolved-via-connect",
|
||||||
|
);
|
||||||
|
expect(core.exportVariable).toHaveBeenCalledWith(
|
||||||
|
envManagedVariables,
|
||||||
|
"MY_SECRET",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("return early if no env vars with secrets found", async () => {
|
it("return early if no env vars with secrets found", async () => {
|
||||||
(exec.getExecOutput as jest.Mock).mockReturnValueOnce({ stdout: "" });
|
delete process.env.MY_SECRET;
|
||||||
await loadSecrets(true);
|
await loadSecrets(true);
|
||||||
|
|
||||||
expect(exec.getExecOutput).toHaveBeenCalledWith('sh -c "op env ls"');
|
|
||||||
expect(core.exportVariable).not.toHaveBeenCalled();
|
expect(core.exportVariable).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("sets step output when shouldExportEnv is false", async () => {
|
||||||
|
await loadSecrets(false);
|
||||||
|
|
||||||
|
expect(core.setOutput).toHaveBeenCalledWith(
|
||||||
|
"MY_SECRET",
|
||||||
|
"resolved-via-connect",
|
||||||
|
);
|
||||||
|
expect(core.exportVariable).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("masks resolved secret with setSecret", async () => {
|
||||||
|
await loadSecrets(true);
|
||||||
|
|
||||||
|
expect(core.setSecret).toHaveBeenCalledWith("resolved-via-connect");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("calls getVault with vault segment from ref", async () => {
|
||||||
|
process.env.MY_SECRET = "op://my-vault-name/my-item/field";
|
||||||
|
const mockGetVault = jest.fn().mockResolvedValue({ id: "vault-uuid" });
|
||||||
|
const mockGetItem = jest.fn().mockResolvedValue({
|
||||||
|
fields: [{ label: "field", value: "secret-value", section: undefined }],
|
||||||
|
sections: [],
|
||||||
|
});
|
||||||
|
(OnePasswordConnect as jest.Mock).mockReturnValue({
|
||||||
|
getVault: mockGetVault,
|
||||||
|
getItem: mockGetItem,
|
||||||
|
});
|
||||||
|
|
||||||
|
await loadSecrets(false);
|
||||||
|
|
||||||
|
expect(mockGetVault).toHaveBeenCalledWith("my-vault-name");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws when getVault returns vault without id", async () => {
|
||||||
|
const mockGetVault = jest.fn().mockResolvedValue({});
|
||||||
|
(OnePasswordConnect as jest.Mock).mockReturnValue({
|
||||||
|
getVault: mockGetVault,
|
||||||
|
getItem: jest.fn(),
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(loadSecrets(true)).rejects.toThrow(
|
||||||
|
/Could not find valid vault "vault" for ref "op:\/\/vault\/item\/field"/,
|
||||||
|
);
|
||||||
|
expect(mockGetVault).toHaveBeenCalledWith("vault");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("resolves vault by name and uses returned id for getItem", async () => {
|
||||||
|
process.env.MY_SECRET = "op://My Vault/My Item/field";
|
||||||
|
const mockGetVault = jest
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValue({ id: "uuid-for-my-vault" });
|
||||||
|
const mockGetItem = jest.fn().mockResolvedValue({
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: "field",
|
||||||
|
value: "secret-from-named-vault",
|
||||||
|
section: undefined,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
sections: [],
|
||||||
|
});
|
||||||
|
(OnePasswordConnect as jest.Mock).mockReturnValue({
|
||||||
|
getVault: mockGetVault,
|
||||||
|
getItem: mockGetItem,
|
||||||
|
});
|
||||||
|
|
||||||
|
await loadSecrets(true);
|
||||||
|
|
||||||
|
expect(mockGetVault).toHaveBeenCalledWith("My Vault");
|
||||||
|
expect(mockGetItem).toHaveBeenCalledWith("uuid-for-my-vault", "My Item");
|
||||||
|
expect(core.exportVariable).toHaveBeenCalledWith(
|
||||||
|
"MY_SECRET",
|
||||||
|
"secret-from-named-vault",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("calls getItem with vault id from getVault, not ref vault segment", async () => {
|
||||||
|
const mockGetVault = jest
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValue({ id: "resolved-vault-id" });
|
||||||
|
const mockGetItem = jest.fn().mockResolvedValue({
|
||||||
|
fields: [
|
||||||
|
{ label: "field", value: "resolved-via-connect", section: undefined },
|
||||||
|
],
|
||||||
|
sections: [],
|
||||||
|
});
|
||||||
|
(OnePasswordConnect as jest.Mock).mockReturnValue({
|
||||||
|
getVault: mockGetVault,
|
||||||
|
getItem: mockGetItem,
|
||||||
|
});
|
||||||
|
|
||||||
|
await loadSecrets(true);
|
||||||
|
|
||||||
|
expect(mockGetVault).toHaveBeenCalledWith("vault");
|
||||||
|
expect(mockGetItem).toHaveBeenCalledWith("resolved-vault-id", "item");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects when getItem fails", async () => {
|
||||||
|
const mockGetVault = jest.fn().mockResolvedValue({ id: "vault-id-123" });
|
||||||
|
const mockGetItem = jest
|
||||||
|
.fn()
|
||||||
|
.mockRejectedValue(new Error("Item not found"));
|
||||||
|
(OnePasswordConnect as jest.Mock).mockReturnValue({
|
||||||
|
getVault: mockGetVault,
|
||||||
|
getItem: mockGetItem,
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(loadSecrets(true)).rejects.toThrow("Item not found");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("resolves refs in different vaults using each vault id", async () => {
|
||||||
|
delete process.env.MY_SECRET;
|
||||||
|
process.env.SECRET_A = "op://vault-a/item1/field1";
|
||||||
|
process.env.SECRET_B = "op://vault-b/item2/field2";
|
||||||
|
const mockGetVault = jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementation(async (vaultName: string) =>
|
||||||
|
Promise.resolve({
|
||||||
|
id: vaultName === "vault-a" ? "id-a" : "id-b",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const mockGetItem = jest
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
fields: [{ label: "field1", value: "value-a", section: undefined }],
|
||||||
|
sections: [],
|
||||||
|
})
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
fields: [{ label: "field2", value: "value-b", section: undefined }],
|
||||||
|
sections: [],
|
||||||
|
});
|
||||||
|
(OnePasswordConnect as jest.Mock).mockReturnValue({
|
||||||
|
getVault: mockGetVault,
|
||||||
|
getItem: mockGetItem,
|
||||||
|
});
|
||||||
|
|
||||||
|
await loadSecrets(true);
|
||||||
|
|
||||||
|
expect(mockGetVault).toHaveBeenCalledWith("vault-a");
|
||||||
|
expect(mockGetVault).toHaveBeenCalledWith("vault-b");
|
||||||
|
expect(mockGetItem).toHaveBeenNthCalledWith(1, "id-a", "item1");
|
||||||
|
expect(mockGetItem).toHaveBeenNthCalledWith(2, "id-b", "item2");
|
||||||
|
expect(core.exportVariable).toHaveBeenCalledWith("SECRET_A", "value-a");
|
||||||
|
expect(core.exportVariable).toHaveBeenCalledWith("SECRET_B", "value-b");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws on invalid ref before calling Connect", async () => {
|
||||||
|
delete process.env.MY_SECRET;
|
||||||
|
process.env.BAD_REF = "op://x";
|
||||||
|
const mockGetVault = jest.fn();
|
||||||
|
const mockGetItem = jest.fn();
|
||||||
|
(OnePasswordConnect as jest.Mock).mockReturnValue({
|
||||||
|
getVault: mockGetVault,
|
||||||
|
getItem: mockGetItem,
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(loadSecrets(true)).rejects.toThrow(/invalid|reference/i);
|
||||||
|
expect(mockGetVault).not.toHaveBeenCalled();
|
||||||
|
expect(mockGetItem).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
describe("core.exportVariable", () => {
|
describe("core.exportVariable", () => {
|
||||||
it("is called when shouldExportEnv is true", async () => {
|
it("is called when shouldExportEnv is true", async () => {
|
||||||
await loadSecrets(true);
|
await loadSecrets(true);
|
||||||
|
|
||||||
expect(core.exportVariable).toHaveBeenCalledTimes(1);
|
expect(core.exportVariable).toHaveBeenCalledTimes(2);
|
||||||
|
expect(core.exportVariable).toHaveBeenCalledWith(
|
||||||
|
"MY_SECRET",
|
||||||
|
"resolved-via-connect",
|
||||||
|
);
|
||||||
|
expect(core.exportVariable).toHaveBeenCalledWith(
|
||||||
|
envManagedVariables,
|
||||||
|
"MY_SECRET",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("is not called when shouldExportEnv is false", async () => {
|
it("is not called when shouldExportEnv is false", async () => {
|
||||||
@@ -214,11 +339,6 @@ describe("loadSecrets when using Service Account", () => {
|
|||||||
mockResolve.mockResolvedValue("resolved-secret-value");
|
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 () => {
|
it("sets step output with resolved value when export-env is false", async () => {
|
||||||
await loadSecrets(false);
|
await loadSecrets(false);
|
||||||
expect(core.setOutput).toHaveBeenCalledTimes(1);
|
expect(core.setOutput).toHaveBeenCalledTimes(1);
|
||||||
@@ -350,7 +470,7 @@ describe("loadSecrets when using Service Account", () => {
|
|||||||
|
|
||||||
describe("secret reference validation", () => {
|
describe("secret reference validation", () => {
|
||||||
it("fails with clear message when a secret reference is invalid", async () => {
|
it("fails with clear message when a secret reference is invalid", async () => {
|
||||||
process.env.MY_SECRET = "op://x";
|
process.env.MY_SECRET = "op://invalid/ref/form";
|
||||||
(Secrets.validateSecretReference as jest.Mock).mockImplementationOnce(
|
(Secrets.validateSecretReference as jest.Mock).mockImplementationOnce(
|
||||||
() => {
|
() => {
|
||||||
throw new Error("invalid reference format");
|
throw new Error("invalid reference format");
|
||||||
@@ -373,6 +493,7 @@ describe("loadSecrets when using Service Account", () => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
mockResolve.mockResolvedValue("value1");
|
||||||
|
|
||||||
await expect(loadSecrets(false)).rejects.toThrow(
|
await expect(loadSecrets(false)).rejects.toThrow(
|
||||||
"Invalid secret reference(s): OTHER",
|
"Invalid secret reference(s): OTHER",
|
||||||
@@ -398,3 +519,360 @@ describe("unsetPrevious", () => {
|
|||||||
expect(core.exportVariable).toHaveBeenCalledWith("TEST_SECRET", "");
|
expect(core.exportVariable).toHaveBeenCalledWith("TEST_SECRET", "");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("findMatchingFieldAndFile", () => {
|
||||||
|
interface TestField {
|
||||||
|
id?: string;
|
||||||
|
label?: string;
|
||||||
|
value?: string | null;
|
||||||
|
section?: { id: string } | null | undefined;
|
||||||
|
}
|
||||||
|
interface TestFile {
|
||||||
|
id?: string;
|
||||||
|
name?: string;
|
||||||
|
section?: { id: string } | null | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const item = (opts: { fields?: TestField[]; files?: TestFile[] }): FullItem =>
|
||||||
|
({
|
||||||
|
fields: opts.fields ?? [],
|
||||||
|
files: opts.files ?? [],
|
||||||
|
sections: [],
|
||||||
|
}) as unknown as FullItem;
|
||||||
|
|
||||||
|
const find = (
|
||||||
|
opts: { fields?: TestField[]; files?: TestFile[] },
|
||||||
|
sectionIds: string[] = [],
|
||||||
|
) => findMatchingFieldAndFile(item(opts), "password", sectionIds);
|
||||||
|
|
||||||
|
describe("when section filter is used (sectionIds.length > 0)", () => {
|
||||||
|
it.each<{
|
||||||
|
name: string;
|
||||||
|
itemOpts: { fields?: TestField[]; files?: TestFile[] };
|
||||||
|
expected: { fieldValue?: string; fileId?: string };
|
||||||
|
}>([
|
||||||
|
{
|
||||||
|
name: "returns field value when one field matches query and is in ref sections",
|
||||||
|
itemOpts: {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
id: "f1",
|
||||||
|
label: "password",
|
||||||
|
value: "secret123",
|
||||||
|
section: { id: "section-1" },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
expected: { fieldValue: "secret123" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "returns file id when one file matches query and is in ref sections",
|
||||||
|
itemOpts: {
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
id: "file-uuid",
|
||||||
|
name: "password",
|
||||||
|
section: { id: "section-1" },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
expected: { fileId: "file-uuid" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "returns empty object when no field or file matches",
|
||||||
|
itemOpts: {
|
||||||
|
fields: [
|
||||||
|
{ label: "other", value: "x", section: { id: "section-1" } },
|
||||||
|
],
|
||||||
|
files: [],
|
||||||
|
},
|
||||||
|
expected: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "returns field value when field matches by id",
|
||||||
|
itemOpts: {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
id: "password",
|
||||||
|
label: "Password Label",
|
||||||
|
value: "secret-by-id",
|
||||||
|
section: { id: "section-1" },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
expected: { fieldValue: "secret-by-id" },
|
||||||
|
},
|
||||||
|
])("$name", ({ itemOpts, expected }) => {
|
||||||
|
expect(find(itemOpts, ["section-1"])).toEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each<{
|
||||||
|
name: string;
|
||||||
|
itemOpts: { fields?: TestField[]; files?: TestFile[] };
|
||||||
|
error: RegExp;
|
||||||
|
}>([
|
||||||
|
{
|
||||||
|
name: "throws when multiple fields match",
|
||||||
|
itemOpts: {
|
||||||
|
fields: [
|
||||||
|
{ label: "password", value: "a", section: { id: "section-1" } },
|
||||||
|
{ label: "password", value: "b", section: { id: "section-1" } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
error: /Multiple matches/,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "throws when multiple files match",
|
||||||
|
itemOpts: {
|
||||||
|
files: [
|
||||||
|
{ id: "id1", name: "password", section: { id: "section-1" } },
|
||||||
|
{ id: "id2", name: "password", section: { id: "section-1" } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
error: /Multiple matches/,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "throws when both a field and a file match",
|
||||||
|
itemOpts: {
|
||||||
|
fields: [
|
||||||
|
{ label: "password", value: "v", section: { id: "section-1" } },
|
||||||
|
],
|
||||||
|
files: [
|
||||||
|
{ id: "fid", name: "password", section: { id: "section-1" } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
error: /Both a field and a file match/,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "throws when field has no value",
|
||||||
|
itemOpts: {
|
||||||
|
fields: [
|
||||||
|
{ label: "password", value: null, section: { id: "section-1" } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
error: /has no value/,
|
||||||
|
},
|
||||||
|
])("$name", ({ itemOpts, error }) => {
|
||||||
|
expect(() => find(itemOpts, ["section-1"])).toThrow(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when no section filter (sectionIds.length === 0)", () => {
|
||||||
|
const sectionIds: string[] = [];
|
||||||
|
|
||||||
|
it.each<{
|
||||||
|
name: string;
|
||||||
|
itemOpts: { fields?: TestField[]; files?: TestFile[] };
|
||||||
|
expected: { fieldValue?: string; fileId?: string };
|
||||||
|
}>([
|
||||||
|
{
|
||||||
|
name: "returns field value when one field has no section and matches query",
|
||||||
|
itemOpts: {
|
||||||
|
fields: [{ label: "password", value: "secret", section: undefined }],
|
||||||
|
},
|
||||||
|
expected: { fieldValue: "secret" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "returns file id when one file has no section and matches query",
|
||||||
|
itemOpts: {
|
||||||
|
files: [{ id: "file-id", name: "password", section: undefined }],
|
||||||
|
},
|
||||||
|
expected: { fileId: "file-id" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "returns field value from fallback (any section) when no field with no section matches",
|
||||||
|
itemOpts: {
|
||||||
|
fields: [
|
||||||
|
{ label: "other", value: "x", section: undefined },
|
||||||
|
{
|
||||||
|
label: "password",
|
||||||
|
value: "from-any-section",
|
||||||
|
section: { id: "sec" },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
expected: { fieldValue: "from-any-section" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "returns file id from fallback (any section) when no file with no section matches",
|
||||||
|
itemOpts: {
|
||||||
|
files: [
|
||||||
|
{ id: "other", name: "x", section: undefined },
|
||||||
|
{ id: "file-any", name: "password", section: { id: "sec" } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
expected: { fileId: "file-any" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "returns empty object when no match",
|
||||||
|
itemOpts: {
|
||||||
|
fields: [{ label: "other", value: "x", section: undefined }],
|
||||||
|
files: [],
|
||||||
|
},
|
||||||
|
expected: {},
|
||||||
|
},
|
||||||
|
])("$name", ({ itemOpts, expected }) => {
|
||||||
|
expect(find(itemOpts, sectionIds)).toEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each<{
|
||||||
|
name: string;
|
||||||
|
itemOpts: { fields?: TestField[]; files?: TestFile[] };
|
||||||
|
error: RegExp;
|
||||||
|
}>([
|
||||||
|
{
|
||||||
|
name: "throws when multiple fields with no section match",
|
||||||
|
itemOpts: {
|
||||||
|
fields: [
|
||||||
|
{ label: "password", value: "a", section: undefined },
|
||||||
|
{ label: "password", value: "b", section: undefined },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
error: /Multiple matches/,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "throws when multiple files with no section match",
|
||||||
|
itemOpts: {
|
||||||
|
files: [
|
||||||
|
{ id: "1", name: "password", section: undefined },
|
||||||
|
{ id: "2", name: "password", section: undefined },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
error: /Multiple matches/,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "throws when both field and file match",
|
||||||
|
itemOpts: {
|
||||||
|
fields: [{ label: "password", value: "value", section: undefined }],
|
||||||
|
files: [{ id: "fid", name: "password", section: undefined }],
|
||||||
|
},
|
||||||
|
error: /Both a field and a file match/,
|
||||||
|
},
|
||||||
|
])("$name", ({ itemOpts, error }) => {
|
||||||
|
expect(() => find(itemOpts, sectionIds)).toThrow(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("findSectionIdsByQuery", () => {
|
||||||
|
it("throws when sections is empty", () => {
|
||||||
|
expect(() => findSectionIdsByQuery([], "section-1")).toThrow(
|
||||||
|
/section section-1 could not be found/,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws when sections is null/undefined", () => {
|
||||||
|
expect(() =>
|
||||||
|
findSectionIdsByQuery(undefined as unknown as FullItem["sections"], "x"),
|
||||||
|
).toThrow(/could not be found/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns section id when section matches by id", () => {
|
||||||
|
const sections = [{ id: "sec-1", label: "Section 1" }];
|
||||||
|
expect(
|
||||||
|
findSectionIdsByQuery(sections as FullItem["sections"], "sec-1"),
|
||||||
|
).toEqual(["sec-1"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns section id when section matches by label", () => {
|
||||||
|
const sections = [{ id: "sec-1", label: "My Section" }];
|
||||||
|
expect(
|
||||||
|
findSectionIdsByQuery(sections as FullItem["sections"], "My Section"),
|
||||||
|
).toEqual(["sec-1"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws when section query matches no section", () => {
|
||||||
|
const sections = [{ id: "sec-1", label: "Other" }];
|
||||||
|
expect(() =>
|
||||||
|
findSectionIdsByQuery(sections as FullItem["sections"], "nonexistent"),
|
||||||
|
).toThrow(/could not be found/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns multiple ids when multiple sections match", () => {
|
||||||
|
const sections = [
|
||||||
|
{ id: "sec-1", label: "A" },
|
||||||
|
{ id: "sec-2", label: "A" },
|
||||||
|
];
|
||||||
|
expect(
|
||||||
|
findSectionIdsByQuery(sections as FullItem["sections"], "A"),
|
||||||
|
).toEqual(["sec-1", "sec-2"]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("parseOpRef", () => {
|
||||||
|
it("parses 3-segment ref (vault/item/field)", () => {
|
||||||
|
expect(parseOpRef("op://vault/item/field")).toEqual({
|
||||||
|
vault: "vault",
|
||||||
|
item: "item",
|
||||||
|
field: "field",
|
||||||
|
section: undefined,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("parses 4-segment ref (vault/item/section/field)", () => {
|
||||||
|
expect(parseOpRef("op://vault/item/MySection/password")).toEqual({
|
||||||
|
vault: "vault",
|
||||||
|
item: "item",
|
||||||
|
section: "MySection",
|
||||||
|
field: "password",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("decodes URI-encoded segments", () => {
|
||||||
|
expect(parseOpRef("op://my%20vault/my%20item/field")).toEqual({
|
||||||
|
vault: "my vault",
|
||||||
|
item: "my item",
|
||||||
|
field: "field",
|
||||||
|
section: undefined,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws when ref does not start with op://", () => {
|
||||||
|
expect(() => parseOpRef("invalid-ref")).toThrow(
|
||||||
|
/Invalid op reference: invalid-ref/,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws when segment count is invalid", () => {
|
||||||
|
expect(() => parseOpRef("op://vault/item")).toThrow(
|
||||||
|
/use op:\/\/<vault>\/<item>\/<field>/,
|
||||||
|
);
|
||||||
|
expect(() => parseOpRef("op://a/b/c/d/e")).toThrow(
|
||||||
|
/use op:\/\/<vault>\/<item>\/<field>/,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws when vault or item or field is empty", () => {
|
||||||
|
expect(() => parseOpRef("op:///item/field")).toThrow(/vault is required/);
|
||||||
|
expect(() => parseOpRef("op://vault//field")).toThrow(/item is required/);
|
||||||
|
expect(() => parseOpRef("op://vault/item/")).toThrow(/field is required/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws when 4-segment ref has empty section", () => {
|
||||||
|
expect(() => parseOpRef("op://vault/item//field")).toThrow(
|
||||||
|
/section is required when using 4 path segments/,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws when last segment is empty (trailing slash)", () => {
|
||||||
|
expect(() => parseOpRef("op://vault/item/field/")).toThrow(
|
||||||
|
/field is required/,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getEnvVarNamesWithSecretRefs", () => {
|
||||||
|
it("returns only env var names whose value is a string starting with op://", () => {
|
||||||
|
process.env.OP_REF = "op://vault/item/field";
|
||||||
|
process.env.NOT_OP_REF = "https://example.com";
|
||||||
|
process.env.EMPTY_REF = "";
|
||||||
|
process.env.OP_REF_OTHER = "op://other/vault/item/secret";
|
||||||
|
|
||||||
|
const result = getEnvVarNamesWithSecretRefs();
|
||||||
|
|
||||||
|
expect(result).toContain("OP_REF");
|
||||||
|
expect(result).toContain("OP_REF_OTHER");
|
||||||
|
expect(result).not.toContain("NOT_OP_REF");
|
||||||
|
expect(result).not.toContain("EMPTY_REF");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
402
src/utils.ts
402
src/utils.ts
@@ -1,7 +1,6 @@
|
|||||||
import * as core from "@actions/core";
|
import * as core from "@actions/core";
|
||||||
import * as exec from "@actions/exec";
|
|
||||||
import { read, setClientInfo, semverToInt } from "@1password/op-js";
|
|
||||||
import { createClient, Secrets } from "@1password/sdk";
|
import { createClient, Secrets } from "@1password/sdk";
|
||||||
|
import { OnePasswordConnect, FullItem, OPConnect } from "@1password/connect";
|
||||||
import { version } from "../package.json";
|
import { version } from "../package.json";
|
||||||
import {
|
import {
|
||||||
authErr,
|
authErr,
|
||||||
@@ -11,26 +10,250 @@ import {
|
|||||||
envManagedVariables,
|
envManagedVariables,
|
||||||
} from "./constants";
|
} from "./constants";
|
||||||
|
|
||||||
export const validateAuth = (): void => {
|
// #region Op ref parsing
|
||||||
const isConnect = process.env[envConnectHost] && process.env[envConnectToken];
|
interface ParsedOpRef {
|
||||||
const isServiceAccount = process.env[envServiceAccountToken];
|
vault: string;
|
||||||
|
item: string;
|
||||||
|
section: string | undefined;
|
||||||
|
field: string;
|
||||||
|
}
|
||||||
|
|
||||||
if (isConnect && isServiceAccount) {
|
export const parseOpRef = (ref: string): ParsedOpRef => {
|
||||||
core.warning(
|
// Safety check: refs are validated by validateSecretRefs before this runs
|
||||||
"WARNING: Both service account and Connect credentials are provided. Connect credentials will take priority.",
|
// this guards against parseOpRef being called directly with invalid input
|
||||||
|
if (!ref.startsWith("op://")) {
|
||||||
|
throw new Error(`Invalid op reference: ${ref}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const segments = ref
|
||||||
|
.slice("op://".length)
|
||||||
|
.split("/")
|
||||||
|
.map((s) => decodeURIComponent(s));
|
||||||
|
|
||||||
|
if (segments.length < 3 || segments.length > 4) {
|
||||||
|
throw new Error(
|
||||||
|
`Invalid op reference: use op://<vault>/<item>/<field> or op://<vault>/<item>/<section>/<field>. Got: ${ref}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isConnect && !isServiceAccount) {
|
const vault = segments[0] ?? "";
|
||||||
throw new Error(authErr);
|
if (!vault) {
|
||||||
|
throw new Error(`Invalid op reference: vault is required`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const authType = isConnect ? "Connect" : "Service account";
|
const item = segments[1] ?? "";
|
||||||
|
if (!item) {
|
||||||
|
throw new Error(`Invalid op reference: item is required`);
|
||||||
|
}
|
||||||
|
|
||||||
core.info(`Authenticated with ${authType}.`);
|
// Last segment is always the field
|
||||||
|
const field = segments[segments.length - 1] ?? "";
|
||||||
|
if (!field) {
|
||||||
|
throw new Error(`Invalid op reference: field is required`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second to last segment is the section if it exists
|
||||||
|
let section: string | undefined;
|
||||||
|
if (segments.length === 4) {
|
||||||
|
section = segments[2];
|
||||||
|
if (!section) {
|
||||||
|
throw new Error(
|
||||||
|
`Invalid op reference: section is required when using 4 path segments`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
vault,
|
||||||
|
item,
|
||||||
|
field,
|
||||||
|
section,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
// #region Connect item resolution
|
||||||
|
const getSecretFromConnectItem = async (
|
||||||
|
client: OPConnect,
|
||||||
|
item: FullItem,
|
||||||
|
parsed: ParsedOpRef,
|
||||||
|
): Promise<string> => {
|
||||||
|
const sectionIds = parsed.section
|
||||||
|
? findSectionIdsByQuery(item.sections, parsed.section)
|
||||||
|
: [];
|
||||||
|
const { fieldValue, fileId } = findMatchingFieldAndFile(
|
||||||
|
item,
|
||||||
|
parsed.field,
|
||||||
|
sectionIds,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (fieldValue !== undefined) {
|
||||||
|
return fieldValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a file was found, get the content of the file (with retry on 503)
|
||||||
|
if (fileId) {
|
||||||
|
const maxAttempts = 3;
|
||||||
|
const retryDelayMs = 2000;
|
||||||
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
||||||
|
try {
|
||||||
|
const content = await client.getFileContent(
|
||||||
|
parsed.vault,
|
||||||
|
parsed.item,
|
||||||
|
fileId,
|
||||||
|
);
|
||||||
|
return content;
|
||||||
|
} catch (err) {
|
||||||
|
const is503 =
|
||||||
|
err &&
|
||||||
|
typeof err === "object" &&
|
||||||
|
(err as Record<string, unknown>).statusCode === 503;
|
||||||
|
if (is503 && attempt < maxAttempts) {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsed.section) {
|
||||||
|
throw new Error(
|
||||||
|
`could not find field or file ${parsed.field} in section ${parsed.section} on item ${parsed.item} in vault ${parsed.vault}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(
|
||||||
|
`could not find field or file ${parsed.field} on item ${parsed.item} in vault ${parsed.vault}`,
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getEnvVarNamesWithSecretRefs = (): string[] =>
|
export const findSectionIdsByQuery = (
|
||||||
|
sections: FullItem["sections"],
|
||||||
|
sectionQuery: string | undefined,
|
||||||
|
): string[] => {
|
||||||
|
// If no sections were returned with the item throw an error
|
||||||
|
if (!sections || sections.length === 0) {
|
||||||
|
throw new Error(
|
||||||
|
`section ${sectionQuery} could not be found in specified item`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ids = sections
|
||||||
|
.filter((s) => s.id === sectionQuery || s.label === sectionQuery)
|
||||||
|
.flatMap((s) => (s.id ? [s.id] : []));
|
||||||
|
|
||||||
|
// If no sections were found with the given query throw an error
|
||||||
|
if (ids.length === 0) {
|
||||||
|
throw new Error(
|
||||||
|
`section ${sectionQuery} could not be found in specified item`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ids;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const findMatchingFieldAndFile = (
|
||||||
|
item: FullItem,
|
||||||
|
fieldOrFileQuery: string,
|
||||||
|
sectionIds: string[],
|
||||||
|
): { fieldValue?: string; fileId?: string } => {
|
||||||
|
// Get the fields/files from the item and check if the ref has a section filter
|
||||||
|
const fields = item.fields ?? [];
|
||||||
|
const files = item.files ?? [];
|
||||||
|
const sectionFilter = sectionIds.length > 0;
|
||||||
|
|
||||||
|
const fieldMatchesQuery = (f: (typeof fields)[0]) =>
|
||||||
|
f.id === fieldOrFileQuery || f.label === fieldOrFileQuery;
|
||||||
|
const fileMatchesQuery = (f: (typeof files)[0]) =>
|
||||||
|
f.id === fieldOrFileQuery || f.name === fieldOrFileQuery;
|
||||||
|
|
||||||
|
let matchedField: (typeof fields)[0] | undefined;
|
||||||
|
let matchedFile: (typeof files)[0] | undefined;
|
||||||
|
|
||||||
|
if (sectionFilter) {
|
||||||
|
// If the ref has a section filter only accept matches inside the referenced sections
|
||||||
|
const matchingFields = fields.filter((f) => {
|
||||||
|
const sectionId = f.section?.id;
|
||||||
|
const inRefSections =
|
||||||
|
sectionId !== null &&
|
||||||
|
sectionId !== undefined &&
|
||||||
|
sectionIds.includes(sectionId);
|
||||||
|
return fieldMatchesQuery(f) && inRefSections;
|
||||||
|
});
|
||||||
|
matchedField = findSingleMatch(matchingFields);
|
||||||
|
|
||||||
|
const matchingFiles = files.filter((f) => {
|
||||||
|
const sectionId = f.section?.id;
|
||||||
|
const inRefSections =
|
||||||
|
sectionId !== null &&
|
||||||
|
sectionId !== undefined &&
|
||||||
|
sectionIds.includes(sectionId);
|
||||||
|
return fileMatchesQuery(f) && inRefSections;
|
||||||
|
});
|
||||||
|
matchedFile = findSingleMatch(matchingFiles);
|
||||||
|
} else {
|
||||||
|
// If the ref has no section filter search for matches with no section
|
||||||
|
const matchingFields = fields.filter((f) => {
|
||||||
|
const hasNoSection =
|
||||||
|
f.section?.id === null || f.section?.id === undefined;
|
||||||
|
return fieldMatchesQuery(f) && hasNoSection;
|
||||||
|
});
|
||||||
|
matchedField = findSingleMatch(matchingFields);
|
||||||
|
|
||||||
|
// If no matches were found with no section, search for matches in any section
|
||||||
|
if (!matchedField) {
|
||||||
|
const matchingFieldsInAnySection = fields.filter(fieldMatchesQuery);
|
||||||
|
matchedField = findSingleMatch(matchingFieldsInAnySection);
|
||||||
|
}
|
||||||
|
|
||||||
|
const matchingFiles = files.filter((f) => {
|
||||||
|
const hasNoSection =
|
||||||
|
f.section?.id === null || f.section?.id === undefined;
|
||||||
|
return fileMatchesQuery(f) && hasNoSection;
|
||||||
|
});
|
||||||
|
matchedFile = findSingleMatch(matchingFiles);
|
||||||
|
|
||||||
|
if (!matchedFile) {
|
||||||
|
const matchingFilesInAnySection = files.filter(fileMatchesQuery);
|
||||||
|
matchedFile = findSingleMatch(matchingFilesInAnySection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchedField && matchedFile) {
|
||||||
|
throw new Error(
|
||||||
|
`Both a field and a file match "${fieldOrFileQuery}". Rename one or use the ID in your op:// reference.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchedField) {
|
||||||
|
if (matchedField.value === undefined || matchedField.value === null) {
|
||||||
|
throw new Error(
|
||||||
|
`field ${fieldOrFileQuery} has no value in specified item`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return { fieldValue: matchedField.value };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchedFile?.id) {
|
||||||
|
return { fileId: matchedFile.id };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
|
||||||
|
const findSingleMatch = <T>(matches: T[]): T | undefined => {
|
||||||
|
if (matches.length > 1) {
|
||||||
|
throw new Error(
|
||||||
|
"Multiple matches found. Rename one or use an ID in your op:// reference.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return matches[0];
|
||||||
|
};
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
// #region Shared helpers and auth
|
||||||
|
export const getEnvVarNamesWithSecretRefs = (): string[] =>
|
||||||
Object.keys(process.env).filter(
|
Object.keys(process.env).filter(
|
||||||
(key) =>
|
(key) =>
|
||||||
typeof process.env[key] === "string" &&
|
typeof process.env[key] === "string" &&
|
||||||
@@ -38,7 +261,7 @@ const getEnvVarNamesWithSecretRefs = (): string[] =>
|
|||||||
);
|
);
|
||||||
|
|
||||||
const validateSecretRefs = (envNames: string[]): void => {
|
const validateSecretRefs = (envNames: string[]): void => {
|
||||||
const invalid: { name: string; message: string }[] = [];
|
const invalid: string[] = [];
|
||||||
|
|
||||||
for (const envName of envNames) {
|
for (const envName of envNames) {
|
||||||
const ref = process.env[envName];
|
const ref = process.env[envName];
|
||||||
@@ -48,18 +271,15 @@ const validateSecretRefs = (envNames: string[]): void => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
Secrets.validateSecretReference(ref);
|
Secrets.validateSecretReference(ref);
|
||||||
} catch (err) {
|
} catch {
|
||||||
const message = err instanceof Error ? err.message : String(err);
|
invalid.push(envName);
|
||||||
invalid.push({ name: envName, message });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Throw an error if any secret references are invalid
|
// Throw an error if any secret references are invalid
|
||||||
if (invalid.length > 0) {
|
if (invalid.length > 0) {
|
||||||
const details = invalid
|
const names = invalid.join(", ");
|
||||||
.map(({ name, message }) => `${name}: ${message}`)
|
throw new Error(`Invalid secret reference(s): ${names}`);
|
||||||
.join("; ");
|
|
||||||
throw new Error(`Invalid secret reference(s): ${details}`);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -80,46 +300,120 @@ const setResolvedSecret = (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const extractSecret = (
|
export const validateAuth = (): void => {
|
||||||
envName: string,
|
const isConnect = process.env[envConnectHost] && process.env[envConnectToken];
|
||||||
shouldExportEnv: boolean,
|
const isServiceAccount = process.env[envServiceAccountToken];
|
||||||
): void => {
|
|
||||||
const ref = process.env[envName];
|
if (isConnect && isServiceAccount) {
|
||||||
if (!ref) {
|
core.warning(
|
||||||
return;
|
"WARNING: Both service account and Connect credentials are provided. Connect credentials will take priority.",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const secretValue = read.parse(ref);
|
if (!isConnect && !isServiceAccount) {
|
||||||
if (secretValue === null || secretValue === undefined) {
|
throw new Error(authErr);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setResolvedSecret(envName, secretValue, shouldExportEnv);
|
const authType = isConnect ? "Connect" : "Service account";
|
||||||
|
|
||||||
|
core.info(`Authenticated with ${authType}.`);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Connect loads secrets via the 1Password CLI
|
export const unsetPrevious = (): void => {
|
||||||
|
if (process.env[envManagedVariables]) {
|
||||||
|
core.info("Unsetting previous values ...");
|
||||||
|
const managedEnvs = process.env[envManagedVariables].split(",");
|
||||||
|
for (const envName of managedEnvs) {
|
||||||
|
core.info(`Unsetting ${envName}`);
|
||||||
|
core.exportVariable(envName, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchVaultId = async (
|
||||||
|
client: OPConnect,
|
||||||
|
vaultQuery: string,
|
||||||
|
ref: string,
|
||||||
|
cache: Map<string, string>,
|
||||||
|
): Promise<string> => {
|
||||||
|
// Check if the vault ID is already cached
|
||||||
|
const cached = cache.get(vaultQuery);
|
||||||
|
if (cached !== undefined) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
const vault = await client.getVault(vaultQuery);
|
||||||
|
if (!vault.id) {
|
||||||
|
throw new Error(
|
||||||
|
`Could not find valid vault "${vaultQuery}" for ref "${ref}"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.set(vaultQuery, vault.id);
|
||||||
|
return vault.id;
|
||||||
|
};
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
// #region Load secrets
|
||||||
|
// Connect loads secrets via the Connect JS SDK
|
||||||
const loadSecretsViaConnect = async (
|
const loadSecretsViaConnect = async (
|
||||||
shouldExportEnv: boolean,
|
shouldExportEnv: boolean,
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
setClientInfo({
|
const envs = getEnvVarNamesWithSecretRefs();
|
||||||
name: "1Password GitHub Action",
|
if (envs.length === 0) {
|
||||||
id: "GHA",
|
|
||||||
build: semverToInt(version),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Load secrets from environment variables using 1Password CLI.
|
|
||||||
// Iterate over them to find 1Password references, extract the secret values,
|
|
||||||
// and make them available in the next steps either as step outputs or as environment variables.
|
|
||||||
const res = await exec.getExecOutput(`sh -c "op env ls"`);
|
|
||||||
|
|
||||||
if (res.stdout === "") {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const envs = res.stdout.replace(/\n+$/g, "").split(/\r?\n/);
|
validateSecretRefs(envs);
|
||||||
for (const envName of envs) {
|
|
||||||
extractSecret(envName, shouldExportEnv);
|
const host = process.env[envConnectHost];
|
||||||
|
const token = process.env[envConnectToken];
|
||||||
|
if (!host || !token) {
|
||||||
|
throw new Error(authErr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Authenticate with the Connect SDK
|
||||||
|
let client;
|
||||||
|
try {
|
||||||
|
client = OnePasswordConnect({
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
serverURL: host,
|
||||||
|
token,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
const message = err instanceof Error ? err.message : String(err);
|
||||||
|
throw new Error(`Connect authentication failed: ${message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const vaultIdByQuery = new Map<string, string>();
|
||||||
|
|
||||||
|
for (const envName of envs) {
|
||||||
|
const ref = process.env[envName];
|
||||||
|
if (!ref) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Parse the op ref and get the item from the Connect SDK
|
||||||
|
const parsed = parseOpRef(ref);
|
||||||
|
|
||||||
|
const vaultId = await fetchVaultId(
|
||||||
|
client,
|
||||||
|
parsed.vault,
|
||||||
|
ref,
|
||||||
|
vaultIdByQuery,
|
||||||
|
);
|
||||||
|
const item = await client.getItem(vaultId, parsed.item);
|
||||||
|
|
||||||
|
// Get the secret value from the item as Connect returns a full item object
|
||||||
|
const secretValue = await getSecretFromConnectItem(client, item, parsed);
|
||||||
|
setResolvedSecret(envName, secretValue, shouldExportEnv);
|
||||||
|
} catch (err) {
|
||||||
|
const msg = err instanceof Error ? err.message : String(err);
|
||||||
|
throw new Error(`Failed to load ref "${ref}": ${msg}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (shouldExportEnv) {
|
if (shouldExportEnv) {
|
||||||
core.exportVariable(envManagedVariables, envs.join());
|
core.exportVariable(envManagedVariables, envs.join());
|
||||||
}
|
}
|
||||||
@@ -181,14 +475,4 @@ export const loadSecrets = async (shouldExportEnv: boolean): Promise<void> => {
|
|||||||
|
|
||||||
await loadSecretsViaServiceAccount(shouldExportEnv);
|
await loadSecretsViaServiceAccount(shouldExportEnv);
|
||||||
};
|
};
|
||||||
|
// #endregion
|
||||||
export const unsetPrevious = (): void => {
|
|
||||||
if (process.env[envManagedVariables]) {
|
|
||||||
core.info("Unsetting previous values ...");
|
|
||||||
const managedEnvs = process.env[envManagedVariables].split(",");
|
|
||||||
for (const envName of managedEnvs) {
|
|
||||||
core.info(`Unsetting ${envName}`);
|
|
||||||
core.exportVariable(envName, "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ assert_env_equals() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
readonly SECRET="RGVhciBzZWN1cml0eSByZXNlYXJjaGVyLCB0aGlzIGlzIGp1c3QgYSBkdW1teSBzZWNyZXQuIFBsZWFzZSBkb24ndCByZXBvcnQgaXQu"
|
readonly SECRET="RGVhciBzZWN1cml0eSByZXNlYXJjaGVyLCB0aGlzIGlzIGp1c3QgYSBkdW1teSBzZWNyZXQuIFBsZWFzZSBkb24ndCByZXBvcnQgaXQu"
|
||||||
|
readonly FILE_SECRET_CONTENT="This is a test"
|
||||||
|
readonly DOUBLE_SECTION_SECRET_CONTENT="test-password"
|
||||||
|
|
||||||
MULTILINE_SECRET="$(cat << EOF
|
MULTILINE_SECRET="$(cat << EOF
|
||||||
-----BEGIN PRIVATE KEY-----
|
-----BEGIN PRIVATE KEY-----
|
||||||
RGVhciBzZWN1cml0eSByZXNlYXJjaGVyLApXaGls
|
RGVhciBzZWN1cml0eSByZXNlYXJjaGVyLApXaGls
|
||||||
@@ -34,4 +37,9 @@ assert_env_equals "SECRET_IN_SECTION" "${SECRET}"
|
|||||||
assert_env_equals "FILE_SECRET_IN_SECTION" "${SECRET}"
|
assert_env_equals "FILE_SECRET_IN_SECTION" "${SECRET}"
|
||||||
|
|
||||||
assert_env_equals "MULTILINE_SECRET" "${MULTILINE_SECRET}"
|
assert_env_equals "MULTILINE_SECRET" "${MULTILINE_SECRET}"
|
||||||
assert_env_equals "FILE_MULTILINE_SECRET" "${MULTILINE_SECRET}"
|
assert_env_equals "FILE_MULTILINE_SECRET" "${MULTILINE_SECRET}"
|
||||||
|
|
||||||
|
assert_env_equals "SECRET_WITH_FILE" "${FILE_SECRET_CONTENT}"
|
||||||
|
assert_env_equals "SECRET_WITH_FILE_IN_SECTION" "${FILE_SECRET_CONTENT}"
|
||||||
|
|
||||||
|
assert_env_equals "DOUBLE_SECTION_SECRET" "${DOUBLE_SECTION_SECRET_CONTENT}"
|
||||||
|
|||||||
@@ -17,3 +17,7 @@ assert_env_unset "FILE_SECRET_IN_SECTION"
|
|||||||
|
|
||||||
assert_env_unset "MULTILINE_SECRET"
|
assert_env_unset "MULTILINE_SECRET"
|
||||||
assert_env_unset "FILE_MULTILINE_SECRET"
|
assert_env_unset "FILE_MULTILINE_SECRET"
|
||||||
|
|
||||||
|
assert_env_unset "SECRET_WITH_FILE"
|
||||||
|
assert_env_unset "SECRET_WITH_FILE_IN_SECTION"
|
||||||
|
assert_env_unset "DOUBLE_SECTION_SECRET"
|
||||||
|
|||||||
Reference in New Issue
Block a user