mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-09 22:24:05 -08:00
feat: Introduce Google Cloud Platform as external secrets provider (#10146)
This commit is contained in:
parent
af695ebf93
commit
3ccb9df2f9
|
@ -83,6 +83,7 @@
|
|||
"dependencies": {
|
||||
"@azure/identity": "^4.3.0",
|
||||
"@azure/keyvault-secrets": "^4.8.0",
|
||||
"@google-cloud/secret-manager": "^5.6.0",
|
||||
"@n8n/client-oauth2": "workspace:*",
|
||||
"@n8n/config": "workspace:*",
|
||||
"@n8n/localtunnel": "2.1.0",
|
||||
|
|
|
@ -4,6 +4,7 @@ import { InfisicalProvider } from './providers/infisical';
|
|||
import { VaultProvider } from './providers/vault';
|
||||
import { AwsSecretsManager } from './providers/aws-secrets/aws-secrets-manager';
|
||||
import { AzureKeyVault } from './providers/azure-key-vault/azure-key-vault';
|
||||
import { GcpSecretsManager } from './providers/gcp-secrets-manager/gcp-secrets-manager';
|
||||
|
||||
@Service()
|
||||
export class ExternalSecretsProviders {
|
||||
|
@ -12,6 +13,7 @@ export class ExternalSecretsProviders {
|
|||
infisical: InfisicalProvider,
|
||||
vault: VaultProvider,
|
||||
azureKeyVault: AzureKeyVault,
|
||||
gcpSecretsManager: GcpSecretsManager,
|
||||
};
|
||||
|
||||
getProvider(name: string): { new (): SecretsProvider } | null {
|
||||
|
|
|
@ -37,7 +37,7 @@ describe('AzureKeyVault', () => {
|
|||
yield { name: 'secret1' };
|
||||
yield { name: 'secret2' };
|
||||
yield { name: 'secret3' }; // no value
|
||||
yield { name: '#@&' }; // invalid name
|
||||
yield { name: '#@&' }; // unsupported name
|
||||
},
|
||||
}));
|
||||
|
||||
|
@ -65,6 +65,6 @@ describe('AzureKeyVault', () => {
|
|||
expect(azureKeyVault.getSecret('secret1')).toBe('value1');
|
||||
expect(azureKeyVault.getSecret('secret2')).toBe('value2');
|
||||
expect(azureKeyVault.getSecret('secret3')).toBeUndefined(); // no value
|
||||
expect(azureKeyVault.getSecret('#@&')).toBeUndefined(); // invalid name
|
||||
expect(azureKeyVault.getSecret('#@&')).toBeUndefined(); // unsupported name
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
import { mock } from 'jest-mock-extended';
|
||||
import { GcpSecretsManager } from '../gcp-secrets-manager/gcp-secrets-manager';
|
||||
import type { GcpSecretsManagerContext } from '../gcp-secrets-manager/types';
|
||||
import { SecretManagerServiceClient } from '@google-cloud/secret-manager';
|
||||
import type { google } from '@google-cloud/secret-manager/build/protos/protos';
|
||||
|
||||
jest.mock('@google-cloud/secret-manager');
|
||||
|
||||
type GcpSecretVersionResponse = google.cloud.secretmanager.v1.IAccessSecretVersionResponse;
|
||||
|
||||
describe('GCP Secrets Manager', () => {
|
||||
const gcpSecretsManager = new GcpSecretsManager();
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should update cached secrets', async () => {
|
||||
/**
|
||||
* Arrange
|
||||
*/
|
||||
const PROJECT_ID = 'my-project-id';
|
||||
|
||||
const SECRETS: Record<string, string> = {
|
||||
secret1: 'value1',
|
||||
secret2: 'value2',
|
||||
secret3: '', // no value
|
||||
'#@&': 'value', // unsupported name
|
||||
};
|
||||
|
||||
await gcpSecretsManager.init(
|
||||
mock<GcpSecretsManagerContext>({
|
||||
settings: { serviceAccountKey: `{ "project_id": "${PROJECT_ID}" }` },
|
||||
}),
|
||||
);
|
||||
|
||||
const listSpy = jest
|
||||
.spyOn(SecretManagerServiceClient.prototype, 'listSecrets')
|
||||
// @ts-expect-error Partial mock
|
||||
.mockResolvedValue([
|
||||
[
|
||||
{ name: `projects/${PROJECT_ID}/secrets/secret1` },
|
||||
{ name: `projects/${PROJECT_ID}/secrets/secret2` },
|
||||
{ name: `projects/${PROJECT_ID}/secrets/secret3` },
|
||||
{ name: `projects/${PROJECT_ID}/secrets/#@&` },
|
||||
],
|
||||
]);
|
||||
|
||||
const getSpy = jest
|
||||
.spyOn(SecretManagerServiceClient.prototype, 'accessSecretVersion')
|
||||
.mockImplementation(async ({ name }: { name: string }) => {
|
||||
const secretName = name.split('/')[3];
|
||||
return [
|
||||
{ payload: { data: Buffer.from(SECRETS[secretName]) } },
|
||||
] as GcpSecretVersionResponse[];
|
||||
});
|
||||
|
||||
/**
|
||||
* Act
|
||||
*/
|
||||
await gcpSecretsManager.connect();
|
||||
await gcpSecretsManager.update();
|
||||
|
||||
/**
|
||||
* Assert
|
||||
*/
|
||||
expect(listSpy).toHaveBeenCalled();
|
||||
|
||||
expect(getSpy).toHaveBeenCalledWith({
|
||||
name: `projects/${PROJECT_ID}/secrets/secret1/versions/latest`,
|
||||
});
|
||||
expect(getSpy).toHaveBeenCalledWith({
|
||||
name: `projects/${PROJECT_ID}/secrets/secret2/versions/latest`,
|
||||
});
|
||||
expect(getSpy).toHaveBeenCalledWith({
|
||||
name: `projects/${PROJECT_ID}/secrets/secret3/versions/latest`,
|
||||
});
|
||||
expect(getSpy).not.toHaveBeenCalledWith({
|
||||
name: `projects/${PROJECT_ID}/secrets/#@&/versions/latest`,
|
||||
});
|
||||
|
||||
expect(gcpSecretsManager.getSecret('secret1')).toBe('value1');
|
||||
expect(gcpSecretsManager.getSecret('secret2')).toBe('value2');
|
||||
expect(gcpSecretsManager.getSecret('secret3')).toBeUndefined(); // no value
|
||||
expect(gcpSecretsManager.getSecret('#@&')).toBeUndefined(); // unsupported name
|
||||
});
|
||||
});
|
|
@ -74,7 +74,7 @@ export class AzureKeyVault implements SecretsProvider {
|
|||
const credential = new ClientSecretCredential(tenantId, clientId, clientSecret);
|
||||
this.client = new SecretClient(`https://${vaultName}.vault.azure.net/`, credential);
|
||||
this.state = 'connected';
|
||||
} catch (error) {
|
||||
} catch {
|
||||
this.state = 'error';
|
||||
}
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ export class AzureKeyVault implements SecretsProvider {
|
|||
await this.client.listPropertiesOfSecrets().next();
|
||||
return [true];
|
||||
} catch (error: unknown) {
|
||||
return [false, error instanceof Error ? error.message : 'unknown error'];
|
||||
return [false, error instanceof Error ? error.message : 'Unknown error'];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
import { SecretManagerServiceClient as GcpClient } from '@google-cloud/secret-manager';
|
||||
import { DOCS_HELP_NOTICE, EXTERNAL_SECRETS_NAME_REGEX } from '@/ExternalSecrets/constants';
|
||||
import type { SecretsProvider, SecretsProviderState } from '@/Interfaces';
|
||||
import { jsonParse, type INodeProperties } from 'n8n-workflow';
|
||||
import type {
|
||||
GcpSecretsManagerContext,
|
||||
GcpSecretAccountKey,
|
||||
RawGcpSecretAccountKey,
|
||||
} from './types';
|
||||
|
||||
export class GcpSecretsManager implements SecretsProvider {
|
||||
name = 'gcpSecretsManager';
|
||||
|
||||
displayName = 'GCP Secrets Manager';
|
||||
|
||||
state: SecretsProviderState = 'initializing';
|
||||
|
||||
properties: INodeProperties[] = [
|
||||
DOCS_HELP_NOTICE,
|
||||
{
|
||||
displayName: 'Service Account Key',
|
||||
name: 'serviceAccountKey',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
typeOptions: { password: true },
|
||||
placeholder: 'e.g. { "type": "service_account", "project_id": "gcp-secrets-store", ... }',
|
||||
hint: 'Content of JSON file downloaded from Google Cloud Console.',
|
||||
noDataExpression: true,
|
||||
},
|
||||
];
|
||||
|
||||
private cachedSecrets: Record<string, string> = {};
|
||||
|
||||
private client: GcpClient;
|
||||
|
||||
private settings: GcpSecretAccountKey;
|
||||
|
||||
async init(context: GcpSecretsManagerContext) {
|
||||
this.settings = this.parseSecretAccountKey(context.settings.serviceAccountKey);
|
||||
}
|
||||
|
||||
async connect() {
|
||||
const { projectId, privateKey, clientEmail } = this.settings;
|
||||
|
||||
try {
|
||||
this.client = new GcpClient({
|
||||
credentials: { client_email: clientEmail, private_key: privateKey },
|
||||
projectId,
|
||||
});
|
||||
this.state = 'connected';
|
||||
} catch {
|
||||
this.state = 'error';
|
||||
}
|
||||
}
|
||||
|
||||
async test(): Promise<[boolean] | [boolean, string]> {
|
||||
if (!this.client) return [false, 'Failed to connect to GCP Secrets Manager'];
|
||||
|
||||
try {
|
||||
await this.client.initialize();
|
||||
return [true];
|
||||
} catch (error: unknown) {
|
||||
return [false, error instanceof Error ? error.message : 'Unknown error'];
|
||||
}
|
||||
}
|
||||
|
||||
async disconnect() {
|
||||
// unused
|
||||
}
|
||||
|
||||
async update() {
|
||||
const { projectId } = this.settings;
|
||||
|
||||
const [rawSecretNames] = await this.client.listSecrets({
|
||||
parent: `projects/${projectId}`,
|
||||
});
|
||||
|
||||
const secretNames = rawSecretNames.reduce<string[]>((acc, cur) => {
|
||||
if (!cur.name || !EXTERNAL_SECRETS_NAME_REGEX.test(cur.name)) return acc;
|
||||
|
||||
const secretName = cur.name.split('/').pop();
|
||||
|
||||
if (secretName) acc.push(secretName);
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
const promises = secretNames.map(async (name) => {
|
||||
const versions = await this.client.accessSecretVersion({
|
||||
name: `projects/${projectId}/secrets/${name}/versions/latest`,
|
||||
});
|
||||
|
||||
if (!Array.isArray(versions) || !versions.length) return null;
|
||||
|
||||
const [latestVersion] = versions;
|
||||
|
||||
if (!latestVersion.payload?.data) return null;
|
||||
|
||||
const value = latestVersion.payload.data.toString();
|
||||
|
||||
if (!value) return null;
|
||||
|
||||
return { name, value };
|
||||
});
|
||||
|
||||
const results = await Promise.all(promises);
|
||||
|
||||
this.cachedSecrets = results.reduce<Record<string, string>>((acc, cur) => {
|
||||
if (cur) acc[cur.name] = cur.value;
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
getSecret(name: string) {
|
||||
return this.cachedSecrets[name];
|
||||
}
|
||||
|
||||
hasSecret(name: string) {
|
||||
return name in this.cachedSecrets;
|
||||
}
|
||||
|
||||
getSecretNames() {
|
||||
return Object.keys(this.cachedSecrets);
|
||||
}
|
||||
|
||||
private parseSecretAccountKey(privateKey: string): GcpSecretAccountKey {
|
||||
const parsed = jsonParse<RawGcpSecretAccountKey>(privateKey, { fallbackValue: {} });
|
||||
|
||||
return {
|
||||
projectId: parsed?.project_id ?? '',
|
||||
clientEmail: parsed?.client_email ?? '',
|
||||
privateKey: parsed?.private_key ?? '',
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
import type { SecretsProviderSettings } from '@/Interfaces';
|
||||
|
||||
type JsonString = string;
|
||||
|
||||
export type GcpSecretsManagerContext = SecretsProviderSettings<{
|
||||
serviceAccountKey: JsonString;
|
||||
}>;
|
||||
|
||||
export type RawGcpSecretAccountKey = {
|
||||
project_id?: string;
|
||||
private_key?: string;
|
||||
client_email?: string;
|
||||
};
|
||||
|
||||
export type GcpSecretAccountKey = {
|
||||
projectId: string;
|
||||
clientEmail: string;
|
||||
privateKey: string;
|
||||
};
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg width="800px" height="800px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g transform="translate(2.000000, 6.000000)" fill="#4285F4" fill-rule="nonzero">
|
||||
<path d="M20,9.41469125e-14 L20,11.8867925 L16.0377358,11.8867925 C15.9335312,11.8867925 15.8490566,11.8023179 15.8490566,11.6981132 L15.8490566,10.5660377 C15.8490566,10.4618331 15.9335312,10.3773585 16.0377358,10.3773585 L18.4900566,10.377 L18.4900566,1.509 L16.0377358,1.50943396 C15.9335312,1.50943396 15.8490566,1.42495939 15.8490566,1.32075472 L15.8490566,0.188679245 C15.8490566,0.0844745755 15.9335312,9.41105434e-14 16.0377358,9.41469125e-14 L20,9.41469125e-14 Z M4.309101,9.41469125e-14 C4.41330567,9.3877869e-14 4.49778024,0.0844745755 4.49778024,0.188679245 L4.49778024,1.32075472 C4.49778024,1.42495939 4.41330567,1.50943396 4.309101,1.50943396 L1.509,1.509 L1.509,10.377 L4.29245283,10.3773585 C4.3966575,10.3773585 4.48113208,10.4618331 4.48113208,10.5660377 L4.48113208,11.6981132 C4.48113208,11.8023179 4.3966575,11.8867925 4.29245283,11.8867925 L2.14050999e-13,11.8867925 L2.14050999e-13,9.41469125e-14 L4.309101,9.41469125e-14 Z M15.4271098,3.86792453 L15.4271098,5.3240566 L15.4879305,5.34852941 L16.8381494,4.87130966 L17.1179245,5.69114872 L15.7555414,6.15613208 L15.719049,6.25402331 L16.6556874,7.51437292 L15.9501676,8.02830189 L15.0378575,6.76795228 L14.9527085,6.76795228 L14.0403984,8.02830189 L13.3348786,7.51437292 L14.2593529,6.25402331 L14.2350246,6.15613208 L12.8726415,5.69114872 L13.1524166,4.87130966 L14.4904714,5.34852941 L14.5634562,5.3240566 L14.5634562,3.86792453 L15.4271098,3.86792453 Z M5.19597773,3.86792453 L5.19597773,5.30963945 L5.2567984,5.33386996 L6.60701735,4.86137515 L6.88679245,5.673097 L5.52440936,6.13347656 L5.48791696,6.23039857 L6.42455533,7.47826947 L5.71903552,7.98711003 L4.80672541,6.73923913 L4.72157647,6.73923913 L3.80926637,7.98711003 L3.10374655,7.47826947 L4.02822079,6.23039857 L4.00389252,6.13347656 L2.64150943,5.673097 L2.92128453,4.86137515 L4.25933935,5.33386996 L4.33232416,5.30963945 L4.33232416,3.86792453 L5.19597773,3.86792453 Z M10.2903173,3.86792453 L10.2903173,5.30963945 L10.351138,5.33386996 L11.701357,4.86137515 L11.9811321,5.673097 L10.618749,6.13347656 L10.5822566,6.23039857 L11.518895,7.47826947 L10.8133751,7.98711003 L9.90106504,6.73923913 L9.81591609,6.73923913 L8.90360599,7.98711003 L8.19808618,7.47826947 L9.12256042,6.23039857 L9.09823215,6.13347656 L7.73584906,5.673097 L8.01562416,4.86137515 L9.35367897,5.33386996 L9.42666378,5.30963945 L9.42666378,3.86792453 L10.2903173,3.86792453 Z" >
|
||||
</path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.7 KiB |
|
@ -7,6 +7,7 @@ import doppler from '../assets/images/doppler.webp';
|
|||
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';
|
||||
|
||||
const props = defineProps<{
|
||||
provider: ExternalSecretsProvider;
|
||||
|
@ -20,6 +21,7 @@ const image = computed(
|
|||
vault,
|
||||
awsSecretsManager,
|
||||
azureKeyVault,
|
||||
gcpSecretsManager,
|
||||
})[props.provider.name],
|
||||
);
|
||||
</script>
|
||||
|
|
|
@ -608,6 +608,9 @@ importers:
|
|||
'@azure/keyvault-secrets':
|
||||
specifier: ^4.8.0
|
||||
version: 4.8.0
|
||||
'@google-cloud/secret-manager':
|
||||
specifier: ^5.6.0
|
||||
version: 5.6.0(encoding@0.1.13)
|
||||
'@n8n/client-oauth2':
|
||||
specifier: workspace:*
|
||||
version: link:../@n8n/client-oauth2
|
||||
|
@ -3360,6 +3363,10 @@ packages:
|
|||
resolution: {integrity: sha512-uWJJf6S2PJL7oZ4ezv16aZl9+IJqPo5GzUv1pZ3/qRiMj13p0ylEgX1+LxBpX71eEPKTwMHoJV2IBBe3EAq7Xw==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
'@google-cloud/secret-manager@5.6.0':
|
||||
resolution: {integrity: sha512-0daW/OXQEVc6VQKPyJTQNyD+563I/TYQ7GCQJx4dq3lB666R9FUPvqHx9b/o/qQtZ5pfuoCbGZl3krpxgTSW8Q==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
'@google-cloud/storage@6.11.0':
|
||||
resolution: {integrity: sha512-p5VX5K2zLTrMXlKdS1CiQNkKpygyn7CBFm5ZvfhVj6+7QUsjWvYx9YDMkYXdarZ6JDt4cxiu451y9QUIH82ZTw==}
|
||||
engines: {node: '>=12'}
|
||||
|
@ -3376,11 +3383,6 @@ packages:
|
|||
resolution: {integrity: sha512-vYVqYzHicDqyKB+NQhAc54I1QWCBLCrYG6unqOIcBTHx+7x8C9lcoLj3KVJXs2VB4lUbpWY+Kk9NipcbXYWmvg==}
|
||||
engines: {node: '>=12.10.0'}
|
||||
|
||||
'@grpc/proto-loader@0.7.10':
|
||||
resolution: {integrity: sha512-CAqDfoaQ8ykFd9zqBDn4k6iWT9loLAlc2ETmDFS9JCD70gDcnA4L3AFEo2iV7KyAtAAHFW9ftq1Fz+Vsgq80RQ==}
|
||||
engines: {node: '>=6'}
|
||||
hasBin: true
|
||||
|
||||
'@grpc/proto-loader@0.7.13':
|
||||
resolution: {integrity: sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==}
|
||||
engines: {node: '>=6'}
|
||||
|
@ -15843,6 +15845,13 @@ snapshots:
|
|||
- encoding
|
||||
- supports-color
|
||||
|
||||
'@google-cloud/secret-manager@5.6.0(encoding@0.1.13)':
|
||||
dependencies:
|
||||
google-gax: 4.3.4(encoding@0.1.13)
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
- supports-color
|
||||
|
||||
'@google-cloud/storage@6.11.0(encoding@0.1.13)':
|
||||
dependencies:
|
||||
'@google-cloud/paginator': 3.0.7
|
||||
|
@ -15875,13 +15884,6 @@ snapshots:
|
|||
'@grpc/proto-loader': 0.7.13
|
||||
'@js-sdsl/ordered-map': 4.4.2
|
||||
|
||||
'@grpc/proto-loader@0.7.10':
|
||||
dependencies:
|
||||
lodash.camelcase: 4.3.0
|
||||
long: 5.2.3
|
||||
protobufjs: 7.3.0
|
||||
yargs: 17.7.2
|
||||
|
||||
'@grpc/proto-loader@0.7.13':
|
||||
dependencies:
|
||||
lodash.camelcase: 4.3.0
|
||||
|
@ -21305,7 +21307,7 @@ snapshots:
|
|||
|
||||
eslint-import-resolver-node@0.3.9:
|
||||
dependencies:
|
||||
debug: 3.2.7(supports-color@5.5.0)
|
||||
debug: 3.2.7(supports-color@8.1.1)
|
||||
is-core-module: 2.13.1
|
||||
resolve: 1.22.8
|
||||
transitivePeerDependencies:
|
||||
|
@ -21330,7 +21332,7 @@ snapshots:
|
|||
|
||||
eslint-module-utils@2.8.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.2))(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0):
|
||||
dependencies:
|
||||
debug: 3.2.7(supports-color@5.5.0)
|
||||
debug: 3.2.7(supports-color@8.1.1)
|
||||
optionalDependencies:
|
||||
'@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@5.5.2)
|
||||
eslint: 8.57.0
|
||||
|
@ -21350,7 +21352,7 @@ snapshots:
|
|||
array.prototype.findlastindex: 1.2.3
|
||||
array.prototype.flat: 1.3.2
|
||||
array.prototype.flatmap: 1.3.2
|
||||
debug: 3.2.7(supports-color@5.5.0)
|
||||
debug: 3.2.7(supports-color@8.1.1)
|
||||
doctrine: 2.1.0
|
||||
eslint: 8.57.0
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
|
@ -21880,7 +21882,7 @@ snapshots:
|
|||
|
||||
follow-redirects@1.15.6(debug@3.2.7):
|
||||
optionalDependencies:
|
||||
debug: 3.2.7(supports-color@5.5.0)
|
||||
debug: 3.2.7(supports-color@8.1.1)
|
||||
|
||||
follow-redirects@1.15.6(debug@4.3.4):
|
||||
optionalDependencies:
|
||||
|
@ -22221,7 +22223,7 @@ snapshots:
|
|||
array-parallel: 0.1.3
|
||||
array-series: 0.1.5
|
||||
cross-spawn: 4.0.2
|
||||
debug: 3.2.7(supports-color@5.5.0)
|
||||
debug: 3.2.7(supports-color@8.1.1)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
|
@ -22255,7 +22257,7 @@ snapshots:
|
|||
google-gax@4.3.4(encoding@0.1.13):
|
||||
dependencies:
|
||||
'@grpc/grpc-js': 1.10.8
|
||||
'@grpc/proto-loader': 0.7.10
|
||||
'@grpc/proto-loader': 0.7.13
|
||||
'@types/long': 4.0.2
|
||||
abort-controller: 3.0.0
|
||||
duplexify: 4.1.2
|
||||
|
@ -24914,7 +24916,7 @@ snapshots:
|
|||
|
||||
pdf-parse@1.1.1:
|
||||
dependencies:
|
||||
debug: 3.2.7(supports-color@5.5.0)
|
||||
debug: 3.2.7(supports-color@8.1.1)
|
||||
node-ensure: 0.0.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
@ -25826,7 +25828,7 @@ snapshots:
|
|||
|
||||
rhea@1.0.24:
|
||||
dependencies:
|
||||
debug: 3.2.7(supports-color@5.5.0)
|
||||
debug: 3.2.7(supports-color@8.1.1)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
|
@ -26200,7 +26202,7 @@ snapshots:
|
|||
binascii: 0.0.2
|
||||
bn.js: 5.2.1
|
||||
browser-request: 0.3.3
|
||||
debug: 3.2.7(supports-color@5.5.0)
|
||||
debug: 3.2.7(supports-color@8.1.1)
|
||||
expand-tilde: 2.0.2
|
||||
extend: 3.0.2
|
||||
fast-xml-parser: 4.2.7
|
||||
|
|
Loading…
Reference in a new issue