mirror of
https://github.com/n8n-io/n8n
synced 2026-04-21 15:47:20 +00:00
feat(core): Add 1Password external secrets provider (#26307)
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
2e35bb322e
commit
1f1021e707
9 changed files with 468 additions and 0 deletions
|
|
@ -10,6 +10,7 @@ export const secretsProviderTypeSchema = z.enum([
|
|||
'vault',
|
||||
'azureKeyVault',
|
||||
'infisical',
|
||||
'onePassword',
|
||||
]);
|
||||
export type SecretsProviderType = z.infer<typeof secretsProviderTypeSchema>;
|
||||
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@
|
|||
"@aws-sdk/client-secrets-manager": "3.808.0",
|
||||
"@azure/identity": "catalog:",
|
||||
"@azure/keyvault-secrets": "4.8.0",
|
||||
"@1password/connect": "1.4.2",
|
||||
"@google-cloud/secret-manager": "5.6.0",
|
||||
"@n8n/ai-utilities": "workspace:*",
|
||||
"@n8n/ai-workflow-builder": "workspace:*",
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { AwsSecretsManager } from './providers/aws-secrets-manager';
|
|||
import { AzureKeyVault } from './providers/azure-key-vault/azure-key-vault';
|
||||
import { GcpSecretsManager } from './providers/gcp-secrets-manager/gcp-secrets-manager';
|
||||
import { InfisicalProvider } from './providers/infisical';
|
||||
import { OnePasswordProvider } from './providers/one-password';
|
||||
import { VaultProvider } from './providers/vault';
|
||||
import type { SecretsProvider } from './types';
|
||||
|
||||
|
|
@ -16,6 +17,7 @@ export class ExternalSecretsProviders {
|
|||
vault: VaultProvider,
|
||||
azureKeyVault: AzureKeyVault,
|
||||
gcpSecretsManager: GcpSecretsManager,
|
||||
onePassword: OnePasswordProvider,
|
||||
};
|
||||
|
||||
getProvider(name: string): { new (): SecretsProvider } {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,285 @@
|
|||
import { UserError } from 'n8n-workflow';
|
||||
import { mock } from 'jest-mock-extended';
|
||||
|
||||
import { OnePasswordProvider } from '../one-password';
|
||||
import type { OnePasswordContext } from '../one-password';
|
||||
|
||||
const mockListVaults = jest.fn();
|
||||
const mockListItems = jest.fn();
|
||||
const mockGetItemById = jest.fn();
|
||||
|
||||
jest.mock('@1password/connect', () => ({
|
||||
OnePasswordConnect: jest.fn(() => ({
|
||||
listVaults: mockListVaults,
|
||||
listItems: mockListItems,
|
||||
getItemById: mockGetItemById,
|
||||
})),
|
||||
}));
|
||||
|
||||
describe('OnePasswordProvider', () => {
|
||||
const provider = new OnePasswordProvider();
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('init validation', () => {
|
||||
it('should throw UserError when serverUrl is empty', async () => {
|
||||
const settings = { serverUrl: '', accessToken: 'test-token' };
|
||||
await expect(provider.init(mock<OnePasswordContext>({ settings }))).rejects.toThrow(
|
||||
UserError,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw UserError when serverUrl is whitespace', async () => {
|
||||
const settings = { serverUrl: ' ', accessToken: 'test-token' };
|
||||
await expect(provider.init(mock<OnePasswordContext>({ settings }))).rejects.toThrow(
|
||||
UserError,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw UserError when accessToken is empty', async () => {
|
||||
const settings = { serverUrl: 'http://localhost:8080', accessToken: '' };
|
||||
await expect(provider.init(mock<OnePasswordContext>({ settings }))).rejects.toThrow(
|
||||
UserError,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw UserError when accessToken is whitespace', async () => {
|
||||
const settings = { serverUrl: 'http://localhost:8080', accessToken: ' ' };
|
||||
await expect(provider.init(mock<OnePasswordContext>({ settings }))).rejects.toThrow(
|
||||
UserError,
|
||||
);
|
||||
});
|
||||
|
||||
it('should succeed with valid settings', async () => {
|
||||
const settings = { serverUrl: 'http://localhost:8080', accessToken: 'test-token' };
|
||||
await expect(provider.init(mock<OnePasswordContext>({ settings }))).resolves.not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('connect', () => {
|
||||
it('should connect successfully when listVaults succeeds', async () => {
|
||||
await provider.init(
|
||||
mock<OnePasswordContext>({
|
||||
settings: { serverUrl: 'http://localhost:8080', accessToken: 'test-token' },
|
||||
}),
|
||||
);
|
||||
|
||||
mockListVaults.mockResolvedValue([{ id: 'vault-1', name: 'My Vault' }]);
|
||||
|
||||
await provider.connect();
|
||||
|
||||
expect(provider.state).toBe('connected');
|
||||
});
|
||||
|
||||
it('should transition to error state when connection fails', async () => {
|
||||
await provider.init(
|
||||
mock<OnePasswordContext>({
|
||||
settings: { serverUrl: 'http://localhost:8080', accessToken: 'bad-token' },
|
||||
}),
|
||||
);
|
||||
|
||||
mockListVaults.mockRejectedValue(new Error('Unauthorized'));
|
||||
|
||||
await provider.connect();
|
||||
|
||||
expect(provider.state).toBe('error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('test', () => {
|
||||
it('should return [true] when listVaults succeeds', async () => {
|
||||
await provider.init(
|
||||
mock<OnePasswordContext>({
|
||||
settings: { serverUrl: 'http://localhost:8080', accessToken: 'test-token' },
|
||||
}),
|
||||
);
|
||||
|
||||
mockListVaults.mockResolvedValue([]);
|
||||
await provider.connect();
|
||||
|
||||
mockListVaults.mockResolvedValue([{ id: 'vault-1' }]);
|
||||
const result = await provider.test();
|
||||
|
||||
expect(result).toEqual([true]);
|
||||
});
|
||||
|
||||
it('should return [false, "Connection refused"] when listVaults fails', async () => {
|
||||
await provider.init(
|
||||
mock<OnePasswordContext>({
|
||||
settings: { serverUrl: 'http://localhost:8080', accessToken: 'test-token' },
|
||||
}),
|
||||
);
|
||||
|
||||
mockListVaults.mockResolvedValue([]);
|
||||
await provider.connect();
|
||||
|
||||
mockListVaults.mockRejectedValue(new Error('Connection refused'));
|
||||
const result = await provider.test();
|
||||
|
||||
expect(result).toEqual([false, 'Connection refused']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
beforeEach(async () => {
|
||||
await provider.init(
|
||||
mock<OnePasswordContext>({
|
||||
settings: { serverUrl: 'http://localhost:8080', accessToken: 'test-token' },
|
||||
}),
|
||||
);
|
||||
|
||||
mockListVaults.mockResolvedValue([{ id: 'vault-1', name: 'My Vault' }]);
|
||||
await provider.connect();
|
||||
});
|
||||
|
||||
it('should fetch and cache secrets from all vaults', async () => {
|
||||
mockListVaults.mockResolvedValue([
|
||||
{ id: 'vault-1', name: 'Vault One' },
|
||||
{ id: 'vault-2', name: 'Vault Two' },
|
||||
]);
|
||||
|
||||
mockListItems
|
||||
.mockResolvedValueOnce([
|
||||
{ id: 'item-1', title: 'Database Credentials' },
|
||||
{ id: 'item-2', title: 'API Key' },
|
||||
])
|
||||
.mockResolvedValueOnce([{ id: 'item-3', title: 'SSH Key' }]);
|
||||
|
||||
mockGetItemById
|
||||
.mockResolvedValueOnce({
|
||||
fields: [
|
||||
{ label: 'username', value: 'admin' },
|
||||
{ label: 'password', value: 'secret123' },
|
||||
],
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
fields: [{ label: 'key', value: 'sk-abc123' }],
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
fields: [{ label: 'private_key', value: 'ssh-rsa AAAA...' }],
|
||||
});
|
||||
|
||||
await provider.update();
|
||||
|
||||
expect(mockListItems).toHaveBeenCalledWith('vault-1');
|
||||
expect(mockListItems).toHaveBeenCalledWith('vault-2');
|
||||
|
||||
expect(mockGetItemById).toHaveBeenCalledWith('vault-1', 'item-1');
|
||||
expect(mockGetItemById).toHaveBeenCalledWith('vault-1', 'item-2');
|
||||
expect(mockGetItemById).toHaveBeenCalledWith('vault-2', 'item-3');
|
||||
|
||||
expect(provider.getSecret('Database Credentials')).toEqual({
|
||||
username: 'admin',
|
||||
password: 'secret123',
|
||||
});
|
||||
expect(provider.getSecret('API Key')).toEqual({ key: 'sk-abc123' });
|
||||
expect(provider.getSecret('SSH Key')).toEqual({ private_key: 'ssh-rsa AAAA...' });
|
||||
expect(provider.getSecretNames()).toHaveLength(3);
|
||||
});
|
||||
|
||||
it('should skip items without fields', async () => {
|
||||
mockListVaults.mockResolvedValue([{ id: 'vault-1', name: 'Vault' }]);
|
||||
|
||||
mockListItems.mockResolvedValue([
|
||||
{ id: 'item-1', title: 'Empty Item' },
|
||||
{ id: 'item-2', title: 'Valid Item' },
|
||||
]);
|
||||
|
||||
mockGetItemById.mockResolvedValueOnce({ fields: [] }).mockResolvedValueOnce({
|
||||
fields: [{ label: 'key', value: 'value' }],
|
||||
});
|
||||
|
||||
await provider.update();
|
||||
|
||||
expect(provider.hasSecret('Empty Item')).toBe(false);
|
||||
expect(provider.hasSecret('Valid Item')).toBe(true);
|
||||
});
|
||||
|
||||
it('should skip fields without labels or values', async () => {
|
||||
mockListVaults.mockResolvedValue([{ id: 'vault-1', name: 'Vault' }]);
|
||||
|
||||
mockListItems.mockResolvedValue([{ id: 'item-1', title: 'Mixed Item' }]);
|
||||
|
||||
mockGetItemById.mockResolvedValue({
|
||||
fields: [
|
||||
{ label: 'valid', value: 'data' },
|
||||
{ label: '', value: 'no-label' },
|
||||
{ label: 'no-value', value: '' },
|
||||
{ label: undefined, value: 'undefined-label' },
|
||||
{ label: 'null-value', value: undefined },
|
||||
],
|
||||
});
|
||||
|
||||
await provider.update();
|
||||
|
||||
expect(provider.getSecret('Mixed Item')).toEqual({ valid: 'data' });
|
||||
});
|
||||
|
||||
it('should skip items without id or title', async () => {
|
||||
mockListVaults.mockResolvedValue([{ id: 'vault-1', name: 'Vault' }]);
|
||||
|
||||
mockListItems.mockResolvedValue([
|
||||
{ id: undefined, title: 'No ID' },
|
||||
{ id: 'item-1', title: undefined },
|
||||
{ id: 'item-2', title: 'Valid' },
|
||||
]);
|
||||
|
||||
mockGetItemById.mockResolvedValue({
|
||||
fields: [{ label: 'key', value: 'value' }],
|
||||
});
|
||||
|
||||
await provider.update();
|
||||
|
||||
expect(mockGetItemById).toHaveBeenCalledTimes(1);
|
||||
expect(mockGetItemById).toHaveBeenCalledWith('vault-1', 'item-2');
|
||||
expect(provider.hasSecret('Valid')).toBe(true);
|
||||
});
|
||||
|
||||
it('should skip vaults without id', async () => {
|
||||
mockListVaults.mockResolvedValue([
|
||||
{ id: undefined, name: 'No ID Vault' },
|
||||
{ id: 'vault-1', name: 'Valid Vault' },
|
||||
]);
|
||||
|
||||
mockListItems.mockResolvedValue([{ id: 'item-1', title: 'Secret' }]);
|
||||
|
||||
mockGetItemById.mockResolvedValue({
|
||||
fields: [{ label: 'key', value: 'value' }],
|
||||
});
|
||||
|
||||
await provider.update();
|
||||
|
||||
expect(mockListItems).toHaveBeenCalledTimes(1);
|
||||
expect(mockListItems).toHaveBeenCalledWith('vault-1');
|
||||
});
|
||||
|
||||
it('should skip items where all fields lack values', async () => {
|
||||
mockListVaults.mockResolvedValue([{ id: 'vault-1', name: 'Vault' }]);
|
||||
|
||||
mockListItems.mockResolvedValue([{ id: 'item-1', title: 'Empty Fields' }]);
|
||||
|
||||
mockGetItemById.mockResolvedValue({
|
||||
fields: [
|
||||
{ label: 'field1', value: '' },
|
||||
{ label: 'field2', value: undefined },
|
||||
],
|
||||
});
|
||||
|
||||
await provider.update();
|
||||
|
||||
expect(provider.hasSecret('Empty Fields')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSecret / hasSecret / getSecretNames', () => {
|
||||
it('should return undefined for non-existent secrets', () => {
|
||||
expect(provider.getSecret('non-existent')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return false for non-existent secrets', () => {
|
||||
expect(provider.hasSecret('non-existent')).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
import type { OPConnect } from '@1password/connect';
|
||||
import { Logger } from '@n8n/backend-common';
|
||||
import { Container } from '@n8n/di';
|
||||
import { UserError, type IDataObject, type INodeProperties } from 'n8n-workflow';
|
||||
|
||||
import { DOCS_HELP_NOTICE } from '../constants';
|
||||
import { SecretsProvider, type SecretsProviderSettings } from '../types';
|
||||
|
||||
export type OnePasswordContext = SecretsProviderSettings<{
|
||||
serverUrl: string;
|
||||
accessToken: string;
|
||||
}>;
|
||||
|
||||
export class OnePasswordProvider extends SecretsProvider {
|
||||
name = 'onePassword';
|
||||
|
||||
displayName = '1Password';
|
||||
|
||||
properties: INodeProperties[] = [
|
||||
DOCS_HELP_NOTICE,
|
||||
{
|
||||
displayName: 'Connect Server URL',
|
||||
name: 'serverUrl',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
placeholder: 'e.g. http://localhost:8080',
|
||||
hint: 'URL of your <a href="https://developer.1password.com/docs/connect/get-started/" target="_blank">1Password Connect Server</a>.',
|
||||
noDataExpression: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Access Token',
|
||||
name: 'accessToken',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
typeOptions: { password: true },
|
||||
placeholder: 'e.g. eyJhbGciOiJFUzI1NiIsImtpZCI6...',
|
||||
hint: 'Token created for your Connect Server integration.',
|
||||
noDataExpression: true,
|
||||
},
|
||||
];
|
||||
|
||||
private cachedSecrets: Record<string, IDataObject> = {};
|
||||
|
||||
private client: OPConnect;
|
||||
|
||||
private settings: { serverUrl: string; accessToken: string };
|
||||
|
||||
constructor(private readonly logger = Container.get(Logger)) {
|
||||
super();
|
||||
this.logger = this.logger.scoped('external-secrets');
|
||||
}
|
||||
|
||||
async init(context: OnePasswordContext) {
|
||||
const trimmedServerUrl = context.settings.serverUrl?.trim();
|
||||
const trimmedAccessToken = context.settings.accessToken?.trim();
|
||||
|
||||
if (!trimmedServerUrl) {
|
||||
throw new UserError('Connect Server URL is required.');
|
||||
}
|
||||
|
||||
if (!trimmedAccessToken) {
|
||||
throw new UserError('Access Token is required.');
|
||||
}
|
||||
|
||||
this.settings = {
|
||||
serverUrl: trimmedServerUrl,
|
||||
accessToken: trimmedAccessToken,
|
||||
};
|
||||
}
|
||||
|
||||
protected async doConnect(): Promise<void> {
|
||||
const { OnePasswordConnect } = await import('@1password/connect');
|
||||
|
||||
this.client = OnePasswordConnect({
|
||||
serverURL: this.settings.serverUrl,
|
||||
token: this.settings.accessToken,
|
||||
keepAlive: true,
|
||||
});
|
||||
|
||||
const [wasSuccessful, errorMessage] = await this.test();
|
||||
|
||||
if (!wasSuccessful) {
|
||||
throw new Error(errorMessage || 'Connection failed');
|
||||
}
|
||||
|
||||
this.logger.debug('1Password provider connected');
|
||||
}
|
||||
|
||||
async test(): Promise<[boolean] | [boolean, string]> {
|
||||
if (!this.client) return [false, 'Client not initialized'];
|
||||
|
||||
try {
|
||||
await this.client.listVaults();
|
||||
return [true];
|
||||
} catch (error: unknown) {
|
||||
return [false, error instanceof Error ? error.message : 'Unknown error'];
|
||||
}
|
||||
}
|
||||
|
||||
async disconnect() {
|
||||
// Stateless HTTP client — nothing to disconnect
|
||||
}
|
||||
|
||||
async update() {
|
||||
const vaults = await this.client.listVaults();
|
||||
|
||||
const secrets: Record<string, IDataObject> = {};
|
||||
|
||||
for (const vault of vaults) {
|
||||
if (!vault.id) continue;
|
||||
|
||||
const items = await this.client.listItems(vault.id);
|
||||
|
||||
for (const item of items) {
|
||||
if (!item.id || !item.title) continue;
|
||||
|
||||
const fullItem = await this.client.getItemById(vault.id, item.id);
|
||||
|
||||
if (!fullItem.fields?.length) continue;
|
||||
|
||||
const fieldValues: IDataObject = {};
|
||||
for (const field of fullItem.fields) {
|
||||
if (field.label && field.value) {
|
||||
fieldValues[field.label] = field.value;
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(fieldValues).length === 0) continue;
|
||||
|
||||
secrets[item.title] = fieldValues;
|
||||
}
|
||||
}
|
||||
|
||||
this.cachedSecrets = secrets;
|
||||
|
||||
this.logger.debug('1Password provider secrets updated');
|
||||
}
|
||||
|
||||
getSecret(name: string): IDataObject {
|
||||
return this.cachedSecrets[name];
|
||||
}
|
||||
|
||||
hasSecret(name: string) {
|
||||
return name in this.cachedSecrets;
|
||||
}
|
||||
|
||||
getSecretNames() {
|
||||
return Object.keys(this.cachedSecrets);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="#0572EC" d="M12 .007C5.373.007 0 5.376 0 11.999c0 6.624 5.373 11.994 12 11.994S24 18.623 24 12C24 5.376 18.627.007 12 .007Zm-.895 4.857h1.788c.484 0 .729.002.914.096a.86.86 0 0 1 .377.377c.094.185.095.428.095.912v6.016c0 .12 0 .182-.015.238a.427.427 0 0 1-.067.137.923.923 0 0 1-.174.162l-.695.564c-.113.092-.17.138-.191.194a.216.216 0 0 0 0 .15c.02.055.078.101.191.193l.695.565c.094.076.14.115.174.162.03.042.053.087.067.137a.936.936 0 0 1 .015.238v2.746c0 .484-.001.727-.095.912a.86.86 0 0 1-.377.377c-.185.094-.43.096-.914.096h-1.788c-.484 0-.726-.002-.912-.096a.86.86 0 0 1-.377-.377c-.094-.185-.095-.428-.095-.912v-6.016c0-.12 0-.182.015-.238a.437.437 0 0 1 .067-.139c.034-.047.08-.083.174-.16l.695-.564c.113-.092.17-.138.191-.194a.216.216 0 0 0 0-.15c-.02-.055-.078-.101-.191-.193l-.695-.565a.92.92 0 0 1-.174-.162.437.437 0 0 1-.067-.139.92.92 0 0 1-.015-.236V6.25c0-.484.001-.727.095-.912a.86.86 0 0 1 .377-.377c.186-.094.428-.096.912-.096z"/></svg>
|
||||
|
After Width: | Height: | Size: 1 KiB |
|
|
@ -8,6 +8,7 @@ import vault from '../assets/images/hashicorp.webp';
|
|||
import AwsSecretsManager from '../assets/images/aws-secrets-manager.svg';
|
||||
import AzureKeyVault from '../assets/images/azure-key-vault.svg';
|
||||
import GcpSecretsManager from '../assets/images/gcp-secrets-manager.svg';
|
||||
import OnePassword from '../assets/images/one-password.svg';
|
||||
|
||||
const { provider } = defineProps<{
|
||||
provider: ExternalSecretsProvider;
|
||||
|
|
@ -20,5 +21,6 @@ const image = computed(() => ({ doppler, infisical, vault })[provider.name]);
|
|||
<AwsSecretsManager v-if="provider.name === 'awsSecretsManager'" />
|
||||
<AzureKeyVault v-else-if="provider.name === 'azureKeyVault'" />
|
||||
<GcpSecretsManager v-else-if="provider.name === 'gcpSecretsManager'" />
|
||||
<OnePassword v-else-if="provider.name === 'onePassword'" />
|
||||
<img v-else :src="image" :alt="provider.displayName" width="28" height="28" />
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import vault from '../../externalSecrets.ee/assets/images/hashicorp.webp';
|
|||
import AwsSecretsManager from '../../externalSecrets.ee/assets/images/aws-secrets-manager.svg';
|
||||
import AzureKeyVault from '../../externalSecrets.ee/assets/images/azure-key-vault.svg';
|
||||
import GcpSecretsManager from '../../externalSecrets.ee/assets/images/gcp-secrets-manager.svg';
|
||||
import OnePassword from '../../externalSecrets.ee/assets/images/one-password.svg';
|
||||
|
||||
const { provider } = defineProps<{
|
||||
provider: SecretProviderTypeResponse;
|
||||
|
|
@ -23,5 +24,6 @@ const image = computed(() => {
|
|||
<AwsSecretsManager v-if="provider.type === 'awsSecretsManager'" />
|
||||
<AzureKeyVault v-else-if="provider.type === 'azureKeyVault'" />
|
||||
<GcpSecretsManager v-else-if="provider.type === 'gcpSecretsManager'" />
|
||||
<OnePassword v-else-if="provider.type === 'onePassword'" />
|
||||
<img v-else :src="image" :alt="provider.displayName" width="28" height="28" />
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1918,6 +1918,9 @@ importers:
|
|||
|
||||
packages/cli:
|
||||
dependencies:
|
||||
'@1password/connect':
|
||||
specifier: 1.4.2
|
||||
version: 1.4.2
|
||||
'@aws-sdk/client-secrets-manager':
|
||||
specifier: 3.808.0
|
||||
version: 3.808.0
|
||||
|
|
@ -4005,6 +4008,9 @@ importers:
|
|||
|
||||
packages:
|
||||
|
||||
'@1password/connect@1.4.2':
|
||||
resolution: {integrity: sha512-CxcDQIr76nloWwGWRrmz/U7DuU65WKrN/yarq45LrC3L6b/pC7bZyskvougadG32fRwBieLJX143lTI8T1bAtQ==}
|
||||
|
||||
'@aashutoshrathi/word-wrap@1.2.6':
|
||||
resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
|
@ -17312,6 +17318,10 @@ packages:
|
|||
resolution: {integrity: sha512-tf+h5W1IrjNm/9rKKj0JU2MDMruiopx0jjVA5zCdBtcGjfp0+c5rHw/zADLC3IeKlGHtVbHtpfzvYA0OYT+HKg==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
|
||||
slugify@1.6.6:
|
||||
resolution: {integrity: sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
|
||||
smart-buffer@4.2.0:
|
||||
resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
|
||||
engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
|
||||
|
|
@ -19380,6 +19390,16 @@ packages:
|
|||
|
||||
snapshots:
|
||||
|
||||
'@1password/connect@1.4.2':
|
||||
dependencies:
|
||||
axios: 1.13.5(debug@4.4.3)
|
||||
debug: 4.4.3(supports-color@8.1.1)
|
||||
lodash.clonedeep: 4.5.0
|
||||
slugify: 1.6.6
|
||||
uuid: 9.0.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@aashutoshrathi/word-wrap@1.2.6': {}
|
||||
|
||||
'@actions/core@1.11.1':
|
||||
|
|
@ -36873,6 +36893,8 @@ snapshots:
|
|||
|
||||
slugify@1.4.7: {}
|
||||
|
||||
slugify@1.6.6: {}
|
||||
|
||||
smart-buffer@4.2.0:
|
||||
optional: true
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue